多容器应用
到目前为止,您一直在使用单容器应用。但是,现在您将把 MySQL 添加到应用堆栈中。一个常见的问题随之而来——“MySQL 将在哪里运行?安装在同一个容器中还是单独运行?”一般来说,每个容器都应该做好一件事情。以下是单独运行容器的几个原因:
- 您很有可能需要以不同于数据库的方式来扩展 API 和前端。
- 单独的容器允许您独立地进行版本控制和更新。
- 虽然您可以在本地使用容器来运行数据库,但在生产环境中可能希望使用托管数据库服务。届时您就不希望将数据库引擎与您的应用一起打包。
- 运行多个进程将需要一个进程管理器(容器只启动一个进程),这增加了容器启动/停止的复杂性。
还有更多原因。因此,如下图所示,最好在多个容器中运行您的应用。


容器网络
请记住,容器默认是隔离运行的,并且不知道同一台机器上的其他进程或容器的任何信息。那么,如何让一个容器与另一个容器通信呢?答案是网络。如果您将这两个容器放在同一个网络上,它们就可以相互通信。
启动 MySQL
有两种方法可以将容器连接到网络:
- 在启动容器时分配网络。
- 将已运行的容器连接到网络。
在接下来的步骤中,您将首先创建网络,然后在启动时将 MySQL 容器连接到该网络。
创建网络。
$ docker network create todo-app
启动一个 MySQL 容器并将其连接到网络。您还将定义一些数据库将用于初始化数据库的环境变量。要了解有关 MySQL 环境变量的更多信息,请参阅 MySQL Docker Hub 列表中的“环境变量”部分。
$ 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:8.0
$ 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:8.0
$ 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:8.0
在前面的命令中,您可以看到 `--network-alias` 标志。在后面的章节中,您将了解更多关于此标志的信息。
提示
您会注意到上述命令中有一个名为 `todo-mysql-data` 的卷,它被挂载到 `/var/lib/mysql`,这是 MySQL 存储数据的地方。然而,您从未运行过 `docker volume create` 命令。Docker 识别出您想使用一个命名卷,并自动为您创建了一个。
为了确认数据库已启动并正在运行,请连接到数据库并验证是否可以连接。
$ docker exec -it <mysql-container-id> mysql -u root -p
出现密码提示时,输入 `secret`。在 MySQL shell 中,列出数据库并验证您是否看到 `todos` 数据库。
mysql> SHOW DATABASES;
您应该会看到如下输出:
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
退出 MySQL shell,返回到您机器上的 shell。
mysql> exit
您现在拥有一个 `todos` 数据库,并且已准备好供您使用。
连接到 MySQL
现在您知道 MySQL 已经启动并正在运行,可以使用它了。但是,如何使用它呢?如果在同一个网络上运行另一个容器,如何找到该容器呢?请记住,每个容器都有自己的 IP 地址。
为了回答上述问题并更好地理解容器网络,您将使用 nicolaka/netshoot 容器,它自带了许多用于排查网络问题的有用工具。
使用 nicolaka/netshoot 镜像启动一个新容器。确保将其连接到同一个网络。
$ docker run -it --network todo-app nicolaka/netshoot
在容器内部,您将使用 `dig` 命令,这是一个很有用的 DNS 工具。您将查找主机名 `mysql` 的 IP 地址。
$ dig mysql
您应该会得到如下输出:
; <<>> DiG 9.18.8 <<>> 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.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44
在“ANSWER SECTION”中,您将看到 `mysql` 的 `A` 记录,解析为 `172.23.0.2`(您的 IP 地址很可能不同)。虽然 `mysql` 通常不是有效的主机名,但 Docker 能够将其解析到具有该网络别名的容器的 IP 地址。请记住,您之前使用了 `--network-alias`。
这意味着您的应用只需要简单地连接到名为 `mysql` 的主机,就可以与数据库通信。
使用 MySQL 运行您的应用
todo 应用支持设置一些环境变量来指定 MySQL 连接设置。它们是:
- `MYSQL_HOST` - 正在运行的 MySQL 服务器的主机名
- `MYSQL_USER` - 用于连接的用户名
- `MYSQL_PASSWORD` - 用于连接的密码
- `MYSQL_DB` - 连接后要使用的数据库
注意
虽然在开发中使用环境变量设置连接设置通常是可以接受的,但在生产环境中运行应用时则强烈不推荐这样做。Docker 前安全负责人 Diogo Monica,写了一篇很棒的博客文章解释了原因。
更安全的机制是使用您的容器编排框架提供的 secret 支持。在大多数情况下,这些 secrets 作为文件挂载到正在运行的容器中。您会看到许多应用(包括 MySQL 镜像和 todo 应用)也支持带有 `_FILE` 后缀的环境变量,指向包含该变量的文件。
例如,设置 `MYSQL_PASSWORD_FILE` 变量将使应用使用所引用文件的内容作为连接密码。Docker 本身不对这些环境变量提供任何支持。您的应用需要知道查找该变量并获取文件内容。
您现在可以启动您的开发就绪容器了。
指定之前提到的每个环境变量,并将容器连接到您的应用网络。请确保在运行此命令时位于 `getting-started-app` 目录中。
$ docker run -dp 127.0.0.1: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:18-alpine \ sh -c "yarn install && yarn run dev"
在 Windows 中,请在 PowerShell 中运行此命令。
$ docker run -dp 127.0.0.1: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:18-alpine ` sh -c "yarn install && yarn run dev"
在 Windows 中,请在命令提示符中运行此命令。
$ docker run -dp 127.0.0.1:3000:3000 ^ -w /app -v "%cd%:/app" ^ --network todo-app ^ -e MYSQL_HOST=mysql ^ -e MYSQL_USER=root ^ -e MYSQL_PASSWORD=secret ^ -e MYSQL_DB=todos ^ node:18-alpine ^ sh -c "yarn install && yarn run dev"
$ docker run -dp 127.0.0.1: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:18-alpine \ sh -c "yarn install && yarn run dev"
如果您查看容器的日志 (`docker logs -f
`),应该会看到类似如下的消息,这表明它正在使用 mysql 数据库。 $ nodemon src/index.js [nodemon] 2.0.20 [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 数据库,证明这些项目正在被写入数据库。记住,密码是 `secret`。
$ docker exec -it <mysql-container-id> mysql -p todos
在 mysql shell 中,运行以下命令:
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 | +--------------------------------------+--------------------+-----------+
您的表会看起来不同,因为它包含您添加的项目。但是,您应该能看到它们存储在那里。
总结
至此,您的应用现在将数据存储在运行于独立容器中的外部数据库中。您学习了一些关于容器网络和使用 DNS 进行服务发现的知识。
相关信息
下一步
您很有可能开始觉得,启动这个应用需要做的所有事情有点让人不知所措。您必须创建一个网络,启动容器,指定所有的环境变量,暴露端口,等等。需要记住的东西太多了,这无疑使将应用交接给其他人变得更加困难。
在下一章节中,您将学习 Docker Compose。使用 Docker Compose,您可以以一种更容易的方式共享您的应用堆栈,并让其他人通过一个简单的命令即可启动它们。