Docker 多容器应用 Multi-Container Apps

zr - 2024-02-04 22:52:41 - 所属文档:Docker 快速入门文档

到目前为止,我们一直在使用单个容器应用程序。但是,我们现在想将 MySQL 添加到应用程序架构中。那么,问题来了:MySQL 将在哪里运行?是将它安装在同一容器中还是在另外的单独容器中运行? 一般来说,**一个容器应该只做一件事。** 原因: * 虽然你可以在开发阶段使用本地数据库,但是很可能要在生产环境中使用云数据库。所以,将数据库与应用程序运行在同一个容器中不太合适。 * 一般情况下,一个容器应该仅启动一个进程,如果在同一个容器中运行多个进程会增加容器启动/关闭/服务运行情况监听的复杂性。 还有其他更多原因。因此,我们会新启动一个容器运行 MySQL 服务,结构如下: ![Todo App connected to MySQL container](https://bianxuebianzuo.oss-cn-shenzhen.aliyuncs.com/uploads/photos/1/2021/08/07/OZc3z1628305632_Todo_App_connected_to_MySQL_container1617270064.png) ## 容器 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 Dashboard showing two ungrouped app containers](https://bianxuebianzuo.oss-cn-shenzhen.aliyuncs.com/uploads/photos/1/2021/08/07/NQEE81628305632_Docker_Dashboard_showing_two_ungrouped_app_containers1617270065.png) ## 回顾 至此,我们有了一个应用程序,它现在将其数据存储在单独运行的数据库容器中。 但是,要启动整个程序有点麻烦,我们必须创建一个网络,启动多个容器,指定所有环境变量,映射端口等等!这样复杂的步骤既不利于自己使用也不方便分享给其他人使用。 在下一节中,我们将讨论 Docker Compose。借助 Docker Compose 可以以一种更简单的方式分享我们的应用,并允许其他人只使用一个简单的命令就可以将它们运行起来! > 原始资料:[Multi-Container Applications](https://docs.docker.com/get-started/07_multi_container/)