固定链接 如何将应用程序容器化?

如何将应用程序容器化?

如何将应用程序容器化?

序言

很多时候我们都有将已有代码迁到容器镜像中的想法,此时,你需要的是一个可重复创建 Docker 镜像的机制,确保创建出来的 Docker 镜像中的代码与你代码库中最新的代码一致。

Dockfile 通过提供一种构建 Docker 镜像的陈述式一致性方法,解决了这类需求。

此外,你有时也想将整个应用程序容器化,这意味着你会集中部署和管理的多个异构容器。

与 Dockerfile 类似,Docker Compose 也是通过这种方法,提供一种定义整个技术栈的方式,包括网络和存储需求。这种方式不仅让构建容器化的应用程序变得更简单,也让应用程序的管理和扩容变得更方便。

本文将使用一个基于 Node.js 和 MongoDB 的 Web 应用程序作为案例,介绍如何使用 Dockerfile 来构建 Docker 镜像。

我们会创建一个自定义的网络,用来让容器间进行通信;同时,我们也会使用 Docker Compose 来启动和扩展容器话的应用。

前提准备

在正式开始之前,你需要:

  • 一个 Ubuntu 16.04 的云服务,包括一个非 root 账号和一个防火墙(可以参考 )
  • 最新版的 Docker 社区版

第一步:使用 Dockerfile 构建一个镜像

首先,切换到你的根目录,然后通过 Git 命令从 GitHub 的官方 repository 克隆本教程的示例 Web 应用程序。

以上命令会将示例 Web 应用程序拷贝到一个名叫 todo-app 的新目录下。

切换到 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 进行构建的,它是一个公开的 Node.js 镜像,仅包含了运行 node 所需要最小的 package。
  • LABEL 是一个键值对,一般用来增加描述信息。本例中,它包含的是维护者的邮件信息。
  • RUN 用来在容器中执行命令。它一般包含类似创建目录和通过基础的 Linux 命令完成容器初始化的任务。
  • WORKDIR 定义了所有命令执行的目录。它通常是代码所在的目录。
  • COPY 将主机中的文件拷贝到容器镜像中。在本例中,我们会将整个 app 拷贝到镜像中。
  • 第二个 RUN 命令通过执行 npm install 来安装 package.json 中定义的应用程序的相关依赖。
  • CMD 运行保证容器持续运行的进程。本例中,我们将使用 app.js 参数执行 node 命令。

现在该从 Dockerfile 中构建镜像了。

通过 -t 给镜像打上 registry 用户名、镜像名称和一个可选标签:

输出结果确定该镜像已经构建成功,且打上了合适的标记:

我们可以通过运行 docker images 验证该镜像已经创建:

同时,我们也可以看到该镜像的大小以及已创建多久:

由于该示例应用程序也需要一个 MangoDB 容器,我们可以通过以下命令获得:

输出结果说明了正在拉取的是哪个镜像,以及下载进度:

现在,我们已经将运行该应用程序需要的一切准备完毕。我们可以创建一个自定义的网络用于容器之间的通信。

第二步:创建一个网络来关联容器

如果我们通过 docker run 命令分别启动 Web 应用程序和数据库的容器,他们彼此无法找到对方。

想要知道原因,可以看看 Web 应用程序的数据库配置文件内容:

在导入 Mongoose(这是一个在 Node.js 异步环境下对 MongoDB 进行便捷操作的对象模型工具)和定义一个新的数据库 schema 之后,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’ );

为了确保属于同一个应用的容器能找到彼此,我们需要在同一个网络中启动它们。

除了在安装时默认创建的 Default 网络外,Docker 也提供了创建自定义网络的能力。

你可以通过以下命令检查当前可用的网络:

Docker 创建的每个网络都基于一个驱动。在以下输出结果中,我们可以看到名叫 bridge 的网络是基于 bridge 驱动的。而 local 的 scope 意味着该网络仅在该主机上可用:

现在我们可以为应用程序创建一个自定义网络,叫 todo_net, 然后基于此网络启动容器:

输出结果显示该网络已经被创建出来:

现在列举出当前可用的网络:

在此我们可以看到 todo_net 已经可用:

现在使用 docker run 命令,我们可以通过 –network 将网络指向此网络。现在我们将 Web 和数据库容器都启动,并赋予特定的主机名。这样可以确保容器找到彼此。

首先,启动 MongoDB 数据库容器:

以上命令的意思如下:

  • -d 指的是以后台运行的模式运行容器
  • –name–hostname 将用户定义的名字赋给容器。 –hostname 也会在 Docker 管理的 DNS 服务中添加一个入口,这样能通过主机名来解析容器
  • –network 指示 Docker 引擎在一个自定义的网络上启动容器,而不是默认的 bridge 网络上

当我们看到 docker run 命令返回的一个长长的字符串时,这意味着容器已经成功启动。但是这并不能保证容器真的在运行中:

可以通过 docker logs 命令验证 DB 容器是否已经起来并在运行中:

以上命令会将容器的 log 打印到 stdout 中。log 的最后一行意味着 MongoDB 已经准备好,并在等在链接:

现在我们要启动 Web 容器了并进行验证了。这次我们使用 –publish=3000:3000,它意味着将主机的 3000 端口发布给容器的 3000 端口:

和以前一样,你将得到一个长长的字符串。

下面来验证该容器已经起来了并在运行中:

输出结果确定 Express — 本文使用的应用程序基于的 Node.js 框架 — 正在监听 3000 端口。

验证 Web 容器可以通过 ping 命令与 DB 容器进行通话。我们可以通过执行 docker exec 命令,并使用关联到伪 TTY (-t)的非交互模式( -i ):

该命令会产生标准的 ping 输出结果,这样我们就能知道两个容器之间是否可以互相通信:

注意:如果出现 exec: “ping”: executable file not found in $PATH. 错误,可以通过如下命令去 Web 容器中安装一下 ping 命令依赖的 package:

可以通过 CTRL+C 来停止 ping 命令。

最后,使用浏览器打开 http://your_server_ip:3000 来访问实例 Web 应用程序。你将看到一个标记了 Container Todo Example 的标签和一个接受 todo 任务作为输入的文本框。

为了避免命名重复,你现在可以停止容器,并通过 docker rmdocker network remove 命令清理相关资源:

此时,我们已经将 Web 应用程序容器化了——包含 2个单独的容器。

下一步,我们将探索一个更健壮的方法。

第三步:部署一个多容器的应用程序

尽管我们已经可以启动关联的容器,但这种处理多容器应用程序的方式并不优雅。我们需要一种更好的方式来声明所有相关容器,并将他们作为一个逻辑单元进行管理。

Docker Compose 是一个开发者可以用来处理多容器应用程序的框架。与 Dockerfile 一样,它也是使用声明式的机制来定义整个栈。现在,我们将把 Node.js 和 MongoDB 应用转化为基于 Docker Compose 的应用程序。

首先安装 Docker Compose:

然后检查以下位于示例 Web 应用程序的 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 意味着以后台运行模式启动容器:

输出结果显示 Docker Compose 创建了一个名叫 compose_todotest 的网络,并在这个网络上启动这两个容器:

注意:我们并没有提供显式的主机端口映射。在这种情况下,Docker Compose 会随机分配一个端口给 Web 应用程序。我们可以通过以下命令找到这个端口:

可以看到 Web 应用程序在主机上的端口为 32782

我们可以通过打开浏览器访问 http://your_server_ip:32782 进行验证。然后我们会看到在第二步结尾的那个 Web 应用程序。

使用 Docker Compose 完成多容器应用程序的启动和运行之后,我们可以进一步看看如何管理和扩展应用程序。

第四步:管理和扩展应用程序

Docker Compose 让无状态 Web 应用程序的扩展变得非常容易。我们可以通过一个命令启动 10 个 Web 容器:

我们可以从输出结果实时看到正在被创建和启动的实例:

可以通过运行 docker ps 命令验证 Web 应用程序已经扩展到 10 个实例了:

注意:Docker 会给每个 Web 容器分配一个随机端口。你可以使用任何一个端口来访问该应用程序:

你也可以使用相同灵命缩减 Web 容器:

你可以实时看到正在被移除的实例:

最后,再次检查这些实例:

输出结果确认只有 2 个实例被留下来了:

现在你可以停止该应用程序,与之前一样,你可以清理资源,避免命名冲突:

结论

本文主要介绍了 Dockerfile 和 Docker Compose。

本文开头介绍 Dockerfile 是一个构建镜像的声明式机制,然后我们介绍了 Docker 网络的基础知识。最后,我们演示了使用 Docker Compose 扩展和管理多容器应用程序。

本文作者:马小婷

您的留言将激励我们越做越好