网络研讨会系列
本文补充了关于在云中部署和管理容器化工作负载的网络研讨会系列 。 本系列介绍了容器的基本知识,包括容器生命周期管理,部署多容器应用程序,扩展工作负载以及理解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 Community Edition。
第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
向我们展示了以下内容:
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
,一个公共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
。
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
的数据库,该数据库尚不存在。
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引擎在自定义网络上启动容器,而不是默认网桥。
当我们看到一个很长的字符串作为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
来访问示例应用程序。 您将看到一个带有标签的网页,该标签读取容器待办事项示例以及接受待办事项任务作为输入的文本框。
为了避免命名冲突,现在可以停止容器并使用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:
block中的MongoDB容器,web db:
block中的Node.js Web容器,以及networks:
block中的自定义网络。
请注意, build: ../.
指令,我们指向编写到app
目录中的Dockerfile
。 这将指示Compose在启动Web容器之前构建图像。
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应用程序,就像您在第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的块存储和负载均衡器 ,为容器式应用带来持久性和可扩展性。