Docker 多容器应用 Multi-Container Apps
zr - 2024-02-04 22:52:41 - 所属文档:Docker 快速入门文档
到目前为止,我们一直在使用单个容器应用程序。但是,我们现在想将 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` 这两个容器放在同一个网络中:
1. 创建网络
```
docker network create todo-app
```
2. 启动 MySQL 容器并为它指定网络。我们还设置了一些环境变量的值,MySQL 将使用这些变量来初始化数据库,它支持的所有环境变量可以查看 [MySQL 镜像的说明](https://hub.docker.com/_/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`,并自动为我们创建了一个。
3. 为了确认数据库是否已经启动成功,可以使用以下命令连接到数据库容器,验证是否能连接成功。
```
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](https://github.com/nicolaka/netshoot) 容器, 它内置*大量*可用于对网络问题进行故障排除或调试的工具。
1. 使用 nicolaka/netshoot 镜像启动一个新容器。确保将它连接到同一个网络上
```
docker run -it --network todo-app nicolaka/netshoot
```
2. 在容器内部,我们将使用 `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,[撰写了一篇精彩的博客文章](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/) 详细解释了反对的原因。
> 一种更安全的机制是使用 `container orchestration framework` 提供的安全支持。在大多数情况下,这些密码会作为文件挂载到正在运行的容器中。你会看到许多应用程序(包括 MySQL 镜像和 我们自己的 todo-app 应用程序)也支持带有 `_FILE` 后缀的环境变量以指向包含真正环境变量的文件。 例如,设置 `MYSQL_PASSWORD_FILE` 变量,这个变量指向的文件内容将被作为 `MYSQL_PASSWORD` 的值,即,真正的连接密码。Docker 默认不支持这种环境变量,你的应用需要自己清楚如何寻找变量并获取文件内容。
在解释了所有这些内容之后,让我们利用刚刚掌握的知识启动一个新容器:
1. 我们将指定 `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"
```
2. 通过查看容器的日志(`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
```
3. 访问我们的应用程序,然后添加一些待办事项
4. 连接到 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 可以以一种更简单的方式分享我们的应用,并允许其他人只使用一个简单的命令就可以将它们运行起来!
> 原始资料:[Multi-Container Applications](https://docs.docker.com/get-started/07_multi_container/)