网络研讨会系列:构建容器化应用程序

来自菜鸟教程
跳转至:导航、​搜索

网络研讨会系列

本文补充了关于在云中部署和管理容器化工作负载的 网络研讨会系列 。 该系列涵盖了容器的基本知识,包括容器生命周期管理、部署多容器应用程序、扩展工作负载和理解 Kubernetes,并重点介绍了运行有状态应用程序的最佳实践。

本教程包括该系列的第二部分“构建容器化应用程序”中涵盖的概念和命令。


观看 YouTube 视频

介绍

在上一篇教程如何安装和配置Docker中,我们探讨了一种将Docker容器转换为Docker镜像的方法。 尽管我们使用的方法有效,但它并不总是构建图像的最佳方式。

在许多情况下,您需要将现有代码引入容器映像,并且需要一种可重复、一致的机制来创建与最新版本代码库同步的 Docker 映像。

Dockerfile 通过提供一种声明性和一致的方式来构建 Docker 映像来满足这些要求。

此外,您有时需要将由多个异构容器组成的整个应用程序容器化,这些容器一起部署和管理。

Docker Compose 与 Dockerfile 一样,采用声明式方法为您提供定义整个技术堆栈的方法,包括网络和存储要求。 这不仅使构建容器化应用程序变得更加容易,而且还使管理和扩展它们变得更加容易。

在本教程中,您将使用基于 Node.jsMongoDB 的示例 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 的网络基于驱动程序 bridgelocal 范围表示网络仅在此主机上可用。

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 rmdocker 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 的块存储负载均衡器 为容器化应用程序带来持久性和可扩展性。