网络研讨会系列
本文补充了关于在云中部署和管理容器化工作负载的 网络研讨会系列 。 该系列涵盖了容器的基本知识,包括容器生命周期管理、部署多容器应用程序、扩展工作负载和理解 Kubernetes,并重点介绍了运行有状态应用程序的最佳实践。
本教程包括该系列的第二部分“构建容器化应用程序”中涵盖的概念和命令。
介绍
在上一篇教程如何安装和配置Docker中,我们探讨了一种将Docker容器转换为Docker镜像的方法。 尽管我们使用的方法有效,但它并不总是构建图像的最佳方式。
在许多情况下,您需要将现有代码引入容器映像,并且需要一种可重复、一致的机制来创建与最新版本代码库同步的 Docker 映像。
Dockerfile 通过提供一种声明性和一致的方式来构建 Docker 映像来满足这些要求。
此外,您有时需要将由多个异构容器组成的整个应用程序容器化,这些容器一起部署和管理。
Docker Compose 与 Dockerfile 一样,采用声明式方法为您提供定义整个技术堆栈的方法,包括网络和存储要求。 这不仅使构建容器化应用程序变得更加容易,而且还使管理和扩展它们变得更加容易。
在本教程中,您将使用基于 Node.js 和 MongoDB 的示例 Web 应用程序从 Dockerfile 构建 Docker 映像,您将创建一个自定义网络以允许您的 Docker 容器进行通信,您将使用 Docker Compose 启动和扩展容器化应用程序。
先决条件
要遵循本教程,您将需要:
- 按照本 Ubuntu 16.04 初始服务器设置教程 设置一个 Ubuntu 16.04 Droplet,包括 sudo 非 root 用户和防火墙。
- 最新版 Docker 社区版按照【X70X】本次网络研讨会系列第一篇教程【X115X】安装。
第 1 步 — 使用 Dockerfile 构建映像
首先切换到您的主目录,然后使用 Git 从 GitHub 上的 官方存储库中克隆本教程的示例 Web 应用程序。
cd ~ git clone https://github.com/janakiramm/todo-app.git
这会将示例应用程序复制到名为 todo-app
的新目录中。
切换到 todo-app
并使用 ls
查看目录的内容。
cd todo-app ls
新目录包含两个子目录和两个文件:
app
- 存储示例应用程序源代码的目录compose
- 存放Docker Compose配置文件的目录Dockerfile
- 包含构建 Docker 映像的说明的文件README.md
- 包含示例应用程序的一句话摘要的文件
运行 cat Dockerfile
向我们展示了以下内容:
~/todo-app/Dockerfile
FROM node:slim LABEL maintainer = "jani@janakiram.com" RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY ./app/ ./ RUN npm install CMD ["node", "app.js"]
让我们更详细地看一下这个文件的内容:
FROM
表示您从中构建自定义映像的基本映像。 在此示例中,该映像基于node:slim
,这是一个 public Node.js 映像,其中仅包含运行node
所需的最小包。LABEL
是一个键值对,通常用于添加描述性信息。 在这种情况下,它包含维护者的电子邮件地址。RUN
在容器内执行命令。 这包括创建目录和通过运行基本 Linux 命令初始化容器等任务。 此文件中的第一个RUN
命令用于创建包含源代码的目录/usr/src/app
。WORKDIR
定义执行所有命令的目录。 它通常是复制代码的目录。COPY
将文件从主机复制到容器映像中。 在这种情况下,您将整个app
目录复制到图像中。- 第二个
RUN
命令执行npm install
以安装在package.json
中定义的应用程序的依赖项。 CMD
运行将保持容器运行的进程。 在本例中,您将使用参数app.js
执行node
。
现在是时候从 Dockerfile
构建映像了。 使用 -t
开关使用注册表用户名、映像名称和可选标记来标记映像。
docker build -t sammy/todo-web .
输出确认图像是 Successfully built
并适当标记。
Output from docker build -tSending build context to Docker daemon 8.238MB Step 1/7 : FROM node:slim ---> 286b1e0e7d3f Step 2/7 : LABEL maintainer = "jani@janakiram.com" ---> Using cache ---> ab0e049cf6f8 Step 3/7 : RUN mkdir -p /usr/src/app ---> Using cache ---> 897176832f4d Step 4/7 : WORKDIR /usr/src/app ---> Using cache ---> 3670f0147bed Step 5/7 : COPY ./app/ ./ ---> Using cache ---> e28c7c1be1a0 Step 6/7 : RUN npm install ---> Using cache ---> 7ce5b1d0aa65 Step 7/7 : CMD node app.js ---> Using cache ---> 2cef2238de24 Successfully built 2cef2238de24 Successfully tagged sammy/todo-web:latest
我们可以通过运行 docker images
命令来验证图像是否已创建。
docker images
在这里,我们可以看到图像的大小以及自创建以来经过的时间。
Output from docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE sammy/todo-web latest 81f5f605d1ca 9 minutes ago 236MB
因为我们还需要一个 MongoDB 容器来运行示例 Web 应用程序,所以让我们将它放到我们的机器上。
docker pull mongo:latest
输出准确地报告了哪个图像被拉出以及下载状态。
Output from docker pulllatest: Pulling from library/mongo Digest: sha256:18b239b996e0d10f4ce2b0f64db6f410c17ad337e2cecb6210a3dcf2f732ed82 Status: Downloaded newer image for mongo:latest
我们现在拥有运行示例应用程序所需的一切,所以让我们创建一个自定义网络,允许我们的容器相互通信。
第 2 步 - 创建一个网络来链接容器
如果我们通过 docker run
命令独立启动 Web 应用程序和数据库容器,它们将无法找到彼此。
要了解原因,请查看 Web 应用程序的数据库配置文件的内容。
cat app/db.js
在导入 Mongoose(Node.js 的 MongoDB 对象建模库)并定义新的 数据库模式 后,Web 应用程序尝试连接到主机名 db
,尚不存在。
~/todo-app/app/db.js
var mongoose = require( 'mongoose' ); var Schema = mongoose.Schema; var Todo = new Schema({ user_id : String, content : String, updated_at : Date }); mongoose.model( 'Todo', Todo ); mongoose.connect( 'mongodb://db/express-todo' );
为了确保属于同一应用程序的容器能够相互发现,我们需要在同一网络上启动它们。
除了在安装期间创建的 默认网络 之外,Docker 还提供了创建自定义网络的能力。
您可以使用以下命令检查当前可用的网络:
docker network ls
Docker 创建的每个网络都基于一个 驱动程序。 在以下输出中,我们看到名为 bridge
的网络基于驱动程序 bridge
。 local
范围表示网络仅在此主机上可用。
Output from docker network lsNETWORK ID NAME DRIVER SCOPE 5029df19d0cf bridge bridge local 367330960d5c host host local f280c1593b89 none null local
我们现在将为我们的应用程序创建一个名为 todo_net
的自定义网络,然后我们将在该网络上启动容器。
docker network create todo_net
输出告诉我们创建的网络的哈希值。
Output from docker network createC09f199809ccb9928dd9a93408612bb99ae08bb5a65833fefd6db2181bfe17ac
现在,再次列出可用的网络。
docker network ls
在这里,我们看到 todo_net
可以使用了。
Output from docker network lsNETWORK ID NAME DRIVER SCOPE c51377a045ff bridge bridge local 2e4106b07544 host host local 7a8b4801a712 none null local bc992f0b2be6 todo_net bridge local
当使用 docker run
命令时,我们现在可以使用 --network
开关来引用这个网络。 让我们启动具有特定主机名的 Web 和数据库容器。 这将确保容器可以通过这些主机名相互连接。
首先,启动 MongoDB 数据库容器。
docker run -d \ --name=db \ --hostname=db \ --network=todo_net \ mongo
仔细查看该命令,我们看到:
-d
开关以 分离模式 运行容器。--name
和--hostname
开关为容器分配一个用户定义的名称。--hostname
开关还向 Docker 管理的 DNS 服务添加了一个条目。 这有助于通过主机名解析容器。--network
开关指示 Docker Engine 在自定义网络而不是默认桥接网络上启动容器。
当我们看到 docker run
命令的输出为长字符串时,我们可以假设容器已成功启动。 但是,这可能不能保证容器实际上正在运行。
Output docker runaa56250f2421c5112cf8e383b68faefea91cd4b6da846cbc56cf3a0f04ff4295
使用 docker logs
命令验证 db
容器是否已启动并正在运行。
docker logs db
这会将容器日志打印到 stdout
。 日志的最后一行表示 MongoDB 已准备就绪并且 waiting for connections
。
Output from docker logs2017-12-10T02:55:08.284+0000 I CONTROL [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=db . . . . 2017-12-10T02:55:08.366+0000 I NETWORK [initandlisten] waiting for connections on port 27017
现在,让我们启动 Web 容器并验证它。 这一次,我们还包括 --publish=3000:3000
,它将主机的端口 3000
发布到容器的端口 3000
。
docker run -d \ --name=web \ --publish=3000:3000 \ --hostname=web \ --network=todo_net \ sammy/todo-web
您将像以前一样收到一个长字符串作为输出。
我们还要验证这个容器是否已启动并正在运行。
docker logs web
输出确认 Express(我们的测试应用程序所基于的 Node.js 框架)是 listening on port 3000
。
Output from docker logsExpress server listening on port 3000
验证 Web 容器是否能够使用 ping
命令与 db 容器通信。 我们通过在附加到伪 TTY (-t
) 的交互式 (-i
) 模式下运行 docker exec
命令来做到这一点。
docker exec -it web ping db
该命令产生标准的 ping
输出,让我们知道两个容器可以相互通信。
Output from docker exec -it web ping dbPING db (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.210 ms 64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.095 ms ...
按 CTRL+C
停止 ping
命令。
最后,通过将 Web 浏览器指向 http://your_server_ip:3000
来访问示例应用程序。 您将看到一个带有标签的网页,该标签显示 Containers Todo Example 以及一个接受 todo 任务作为输入的文本框。
为避免命名冲突,您现在可以使用 docker rm
和 docker network remove
命令停止容器并清理资源。
docker rm -f db docker rm -f web docker network remove todo_net
此时,我们有一个由两个独立容器组成的容器化 Web 应用程序。 在下一步中,我们将探索一种更稳健的方法。
第 3 步 — 部署多容器应用程序
尽管我们能够启动链接容器,但这并不是处理多容器应用程序的最优雅方式。 我们需要一种更好的方法来声明所有相关的容器并将它们作为一个逻辑单元进行管理。
Docker Compose 是一个可供开发人员处理多容器应用程序的框架。 与Dockefile 一样,它是一种定义整个堆栈的声明性机制。 我们现在将我们的 Node.js 和 MongoDB 应用程序转换为基于 Docker Compose 的应用程序。
首先安装 Docker Compose。
sudo apt-get install -y docker-compose
让我们检查位于示例 Web 应用程序的 compose
目录中的 docker-compose.yaml
文件。
cat compose/docker-compose.yaml
docker-compose.yaml
文件将所有内容组合在一起。 它在 db:
块中定义了 MongoDB 容器,在 web:
块中定义了 Node.js Web 容器,在 networks:
块中定义了自定义网络。
请注意,使用 build: ../.
指令,我们将 Compose 指向 app
目录中的 Dockerfile
。 这将指示 Compose 在启动 Web 容器之前构建映像。
~/todo-app/compose/docker-compose.yaml
version: '2' services: db: image: mongo:latest container_name: db networks: - todonet web: build: ../. networks: - todonet ports: - "3000" networks: todonet: driver: bridge
现在,切换到 compose
目录并使用 docker-compose up
命令启动应用程序。 与 docker run
一样,-d
开关以分离模式启动容器。
cd compose docker-compose up -d
输出报告说 Docker Compose 创建了一个名为 compose_todonet
的网络并在其上启动了两个容器。
Output from docker-compose up -dCreating network "compose_todonet" with driver "bridge" Creating db Creating compose_web_1
请注意,我们没有提供明确的主机端口映射。 这将强制 Docker Compose 分配一个随机端口以在主机上公开 Web 应用程序。 我们可以通过运行以下命令找到该端口:
docker ps
我们看到 Web 应用程序暴露在主机端口 32782
上。
Output from docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6700761c0a1e compose_web "node app.js" 2 minutes ago Up 2 minutes 0.0.0.0:32782->3000/tcp compose_web_1 ad7656ef5db7 mongo:latest "docker-entrypoint..." 2 minutes ago Up 2 minutes 27017/tcp db
通过将 Web 浏览器导航到 http://your_server_ip:32782
来验证这一点。 这将打开 Web 应用程序,就像您在 Step 2 末尾看到的那样。
随着我们的多容器应用程序通过 Docker Compose 启动并运行,让我们来看看管理和扩展我们的应用程序。
第 4 步 — 管理和扩展应用程序
Docker Compose 可以轻松扩展无状态 Web 应用程序。 我们可以使用一个命令启动我们的 web
容器的 10 个实例。
docker-compose scale web=10
输出让我们可以实时观察正在创建和启动的实例。
Output from docker-compose scaleCreating and starting compose_web_2 ... done Creating and starting compose_web_3 ... done Creating and starting compose_web_4 ... done Creating and starting compose_web_5 ... done Creating and starting compose_web_6 ... done Creating and starting compose_web_7 ... done Creating and starting compose_web_8 ... done Creating and starting compose_web_9 ... done Creating and starting compose_web_10 ... done
通过运行 docker ps
验证 Web 应用程序是否扩展到 10 个实例。
docker ps
请注意,Docker 分配了一个随机端口来公开主机上的每个 web
容器。 这些端口中的任何一个都可用于访问应用程序。
Output from docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cec405db568d compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32788->3000/tcp compose_web_9 56adb12640bb compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32791->3000/tcp compose_web_10 4a1005d1356a compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32790->3000/tcp compose_web_7 869077de9cb1 compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32785->3000/tcp compose_web_8 eef86c56d16f compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32783->3000/tcp compose_web_4 26dbce7f6dab compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32786->3000/tcp compose_web_5 0b3abd8eee84 compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32784->3000/tcp compose_web_3 8f867f60d11d compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32789->3000/tcp compose_web_6 36b817c6110b compose_web "node app.js" About a minute ago Up About a minute 0.0.0.0:32787->3000/tcp compose_web_2 6700761c0a1e compose_web "node app.js" 7 minutes ago Up 7 minutes 0.0.0.0:32782->3000/tcp compose_web_1 ad7656ef5db7 mongo:latest "docker-entrypoint..." 7 minutes ago Up 7 minutes 27017/tcp db
您还可以使用相同的命令缩小 Web 容器。
docker-compose scale web=2
这一次,我们看到额外的实例被实时删除。
Output from docker-composeStopping and removing compose_web_3 ... done Stopping and removing compose_web_4 ... done Stopping and removing compose_web_5 ... done Stopping and removing compose_web_6 ... done Stopping and removing compose_web_7 ... done Stopping and removing compose_web_8 ... done Stopping and removing compose_web_9 ... done Stopping and removing compose_web_10 ... done
最后,重新检查实例。
docker ps
输出确认只剩下两个实例。
Output from docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 36b817c6110b compose_web "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:32787->3000/tcp compose_web_2 6700761c0a1e compose_web "node app.js" 9 minutes ago Up 9 minutes 0.0.0.0:32782->3000/tcp compose_web_1 ad7656ef5db7 mongo:latest "docker-entrypoint..." 9 minutes ago Up 9 minutes 27017/tcp db
您现在可以停止应用程序,并且像以前一样,您还可以清理资源以避免命名冲突。
docker-compose stop docker-compose rm -f docker network remove compose_todonet
结论
本教程向您介绍了 Dockerfiles 和 Docker Compose。 我们从 Dockerfile 作为构建镜像的声明性机制开始,然后我们探索了 Docker 网络的基础知识。 最后,我们使用 Docker Compose 扩展和管理多容器应用程序。
要扩展您的新设置,您可以添加在另一个容器内运行的 Nginx 反向代理 ,以将请求路由到可用的 Web 应用程序容器之一。 或者,您可以利用 DigitalOcean 的块存储 和 负载均衡器 为容器化应用程序带来持久性和可扩展性。