多容器应用 Multi-Container Apps
到目前为止,我们一直在使用单个容器应用程序。但是,我们现在想将 MySQL 添加到应用程序架构中。那么,问题来了:MySQL 将在哪里运行?是将它安装在同一容器中还是在另外的单独容器中运行?
一般来说,一个容器应该只做一件事。 原因:
- 虽然你可以在开发阶段使用本地数据库,但是很可能要在生产环境中使用云数据库。所以,将数据库与应用程序运行在同一个容器中不太合适。
- 一般情况下,一个容器应该仅启动一个进程,如果在同一个容器中运行多个进程会增加容器启动/关闭/服务运行情况监听的复杂性。
还有其他更多原因。因此,我们会新启动一个容器运行 MySQL 服务,结构如下:
容器 Networking
默认情况下,容器是独立运行的,并且对同一台主机上的其他进程或容器一无所知。So, how do we allow one container to talk to another? The answer is networking. Now, you don’t have to be a network engineer (hooray!). Simply remember this rule…那么,我们如何允许一个容器与另一个容器对话?答案是通过 networking - 网络。你不必具备网络工程师的知识,只需要记住一个最简单的规则就够用了:如果两个容器在同一个网络中,它们就可以相互通信。否则,就不行
启动 MySQL
接着就让我们将 todo-app
与 MySQL
这两个容器放在同一个网络中:
创建网络
docker network create todo-app
启动 MySQL 容器并为它指定网络。我们还设置了一些环境变量的值,MySQL 将使用这些变量来初始化数据库,它支持的所有环境变量可以查看 MySQL 镜像的说明
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7
如果使用的是 PowerShell,执行下面这条命令(只是
多行输入
的分隔符不一样而已)docker run -d ` --network todo-app --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:5.7
你应该看到了,我们增加了
--network-alias
标记,稍后再讨论它。备注:
这条命令还通过-v
标记使用了名字为todo-mysql-data
的volume
并将它挂载到/var/lib/mysql
路径下,也就是 MySQL 真正存储数据的地方。虽然我们没有先执行docker volume create
命令提前创建volume
但是 Docker 识别到我们要用named volume
,并自动为我们创建了一个。为了确认数据库是否已经启动成功,可以使用以下命令连接到数据库容器,验证是否能连接成功。
docker exec -it <mysql-container-id> mysql -p
出现输入密码的界面时,输入上一条命令指定的 MYSQL_ROOT_PASSWORD 环境变量的值
secret
成功进入 MySQL 之后,执行以下命令列出所有数据库mysql> SHOW DATABASES;
你应该能看到如下所示的输出:
+--------------------+ | Database | +--------------------+ | information_schema | | MySQL | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
连接到 MySQL
MySQL 已启动成功,让我们使用它吧!但是,问题是…怎么用?如果我们在同一个网络上运行另一个容器,如何找到该容器?
为了弄清楚这一点,我们将使用 nicolaka/netshoot 容器, 它内置大量可用于对网络问题进行故障排除或调试的工具。
使用 nicolaka/netshoot 镜像启动一个新容器。确保将它连接到同一个网络上
docker run -it --network todo-app nicolaka/netshoot
在容器内部,我们将使用
dig
命令,这是一个有用的 DNS 工具。我们将查找主机名mysql
的 IP 地址dig mysql
将会看到类似如下的输出…
; <<>> DiG 9.14.1 <<>> mysql ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.18.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Wed Jan 13 05:21:49 UTC 2021 ;; MSG SIZE rcvd: 44
在 “ANSWER SECTION” 应答部分,会看到
mysql
的 A 记录,解析为 172.18.0.2(你看到的 IP 地址很可能跟我不一样)。虽然mysql
通常不是有效的主机名,但 Docker 能够将其解析为具有该网络别名的容器的 IP 地址(还记得我们之前在启动 MySQL 容器时使用的--network-alias
标志吗?它的作用就是为这个容器主机取一个别名)。这意味着…我们的应用程序仅需连接到名为mysql
的主机,就能跟数据库对话了,就是这么简单!
使用 MySQL 运行我们的应用
我们的 todo-app 应用程序支持一些环境变量的设置,以指定 MySQL 连接设置。它们是:
MYSQL_HOST
- MySQL 服务器的主机名MYSQL_USER
- 用于连接的用户名MYSQL_PASSWORD
- 用于连接的密码MYSQL_DB
- 连接后要使用的数据库
重要提醒:
直接通过在命令行中设置环境变量的做法,在本地开发模式下没什么问题,但是强烈反对
在生产环境中使用。Docker 前安全负责人 Diogo Monica,撰写了一篇精彩的博客文章 详细解释了反对的原因。
一种更安全的机制是使用container orchestration framework
提供的安全支持。在大多数情况下,这些密码会作为文件挂载到正在运行的容器中。你会看到许多应用程序(包括 MySQL 镜像和 我们自己的 todo-app 应用程序)也支持带有_FILE
后缀的环境变量以指向包含真正环境变量的文件。 例如,设置MYSQL_PASSWORD_FILE
变量,这个变量指向的文件内容将被作为MYSQL_PASSWORD
的值,即,真正的连接密码。Docker 默认不支持这种环境变量,你的应用需要自己清楚如何寻找变量并获取文件内容。
在解释了所有这些内容之后,让我们利用刚刚掌握的知识启动一个新容器:
我们将指定
todo-app
应用程序需要用到的几个环境变量,并将容器连接到我们的应用程序网络上# 确保在本机的 app 目录下执行以下命令 docker run -dp 3000:3000 \ -w /app \ -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"
如果使用的是 PowerShell,执行下面这条命令(只是
多行输入
的分隔符不一样而已)docker run -dp 3000:3000 ` -w /app ` -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"
通过查看容器的日志(
docker logs <container-id>
),应该能看到类似Connected to MySQL db at host mysql
的日志输出,代表我们的应用程序现在就是使用 MySQL 数据库来保存数据了。# 省略之前的日志消息 ... $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Connected to mysql db at host mysql Listening on port 3000
访问我们的应用程序,然后添加一些待办事项
连接到 MySQL 数据库并证明这些待办事项已被写入到 MySQL 数据库。记住,密码是 secret
docker exec -it <mysql-container-id> mysql -p todos
进入 MySQL 之后执行以下命令:
mysql> select * from todo_items; +--------------------------------------+--------------------+-----------+ | id | name | completed | +--------------------------------------+--------------------+-----------+ | c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 | | 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 | +--------------------------------------+--------------------+-----------+
很显然,你的表数据跟我的不一样,因为它保存的是你刚刚输入的待办事项。但是,你应该能看到它们被存储在这里了!
如果你现在打开 Docker Dashboard 将会看到有两个正在运行的应用程序容器,其中一个是 MySQL 容器,另一个是我们的 todo-app 应用。但是,它们现在看起来好像没有任何关系。
回顾
至此,我们有了一个应用程序,它现在将其数据存储在单独运行的数据库容器中。
但是,要启动整个程序有点麻烦,我们必须创建一个网络,启动多个容器,指定所有环境变量,映射端口等等!这样复杂的步骤既不利于自己使用也不方便分享给其他人使用。
在下一节中,我们将讨论 Docker Compose。借助 Docker Compose 可以以一种更简单的方式分享我们的应用,并允许其他人只使用一个简单的命令就可以将它们运行起来!
推荐文章: