在这篇文章中
- 设置一个演示的Node.js应用程序
- 安装Docker
- 设置一个Docker文件
- 构建Docker镜像
- 在容器中运行Docker镜像
- 共享Docker镜像
- 将你的Docker化Node.js应用部署到生产中
- 结论和进一步的Docker阅读
- 发表于:2021年11月23日
如何将现有的Node.js应用程序Docker化
本文最初发表于2021年9月1日的AppSignal博客。
Docker是一个软件平台,可以将应用程序打包成容器。这些容器代表隔离的环境,提供运行应用程序所需的一切。Dockerizing应用程序是指将其打包成Docker镜像,在一个或多个容器中运行。
Docker化应用程序包括在Dockerfile ,指定运行该应用程序所需的一切,然后使用该文件建立一个专门的Docker镜像,可以共享给多台机器。Docker镜像是应用程序的一个可复制的环境,保证了跨机器的可移植性。
在本教程中,你将学习从头开始对现有Node.js应用程序进行Docker化的过程。我们将涵盖以下主题。
Dockerfile代表什么- 将Docker镜像共享给多台机器,以及
- 用于协调多容器应用程序的Docker Compose的基础知识。
读完这篇文章后,你应该掌握足够的知识来对自己的应用程序进行Docker化,即使它们是用其他技术构建的。
设置一个演示的Node.js应用程序
为了演示本文讨论的概念,我们将使用一个演示的Node.js应用程序,它提供了一个检索Covid-19统计数据的端点。它使用由disease.sh提供的免费API。你可以使用下面的命令将其GitHub仓库克隆到你的电脑上。
$ git clone https://github.com/finallyayo/covid-node
下载后,cd 到项目文件夹中,并运行yarn 来安装其依赖性。之后,在你的文本编辑器中打开app.js 文件。你应该看到以下内容。
app.js
const fastify = require('fastify')({
logger: true,
});
const got = require('got');
const NodeCache = require('node-cache');
const appCache = new NodeCache();
fastify.get('/covid', async function (req, res) {
try {
let covidAllStats = appCache.get('covidAllStats');
if (covidAllStats == null) {
const response = await got('https://disease.sh/v3/covid-19/all');
covidAllStats = response.body;
appCache.set('covidAllStats', covidAllStats, 600);
}
res
.header('Content-Type', 'application/json; charset=utf-8')
.send(covidAllStats);
} catch (err) {
fastify.log.error(err);
res.code(error.response.code).send(err.response.body);
}
});
fastify.listen(4000, '0.0.0.0', (err, address) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.log.info(`server listening on ${address}`);
});
这个应用程序提供了一个单一的端点(/covid),返回迄今为止全球Covid-19的汇总总数。一旦从API检索到数据,随后将在内存中缓存10分钟。
在部署到Docker时,指定'0.0.0.0' 作为地址是至关重要的,因为Docker容器并不默认将映射的端口暴露给localhost 。如果缺少这个地址,尽管在容器中成功启动,你的应用程序可能无法访问。
继续用yarn dev 启动服务器,然后用curl 或其他工具向/covid 端点发出GET请求。你应该看到一个类似于下面显示的输出的JSON响应。
$ curl http://localhost:4000/covid
输出
{
"updated":1629986413872,
"cases":214977601,
"todayCases":270792,
"deaths":4481152,
"todayDeaths":5588,
"recovered":192301169,
"todayRecovered":273952,
"active":18195280,
"critical":112761,
"casesPerOneMillion":27580,
"deathsPerOneMillion":574.9,
"tests":3264569720,
"testsPerOneMillion":416082.42,
"population":7845968850,
"oneCasePerPeople":0,
"oneDeathPerPeople":0,
"oneTestPerPeople":0,
"activePerOneMillion":2319.06,
"recoveredPerOneMillion":24509.55,
"criticalPerOneMillion":14.37,
"affectedCountries":223
}

虽然这是一个非常简单的应用,但它足以展示本教程中涉及的Docker概念。在下一节中,我们将看一下如何在你的机器上本地设置Docker引擎。
安装Docker
在对一个应用程序进行Docker化之前,你需要安装Docker引擎。Docker官方手册提供了在各种操作系统上安装软件的指南,其中最引人注目的是在macOS、Windows和各种Linux发行版上。确保你安装的是最新的稳定版本--在撰写本文时为v20.10.x。
$ docker -v
Docker version 20.10.5, build 55c4c88
设置Docker文件
一旦Docker引擎被安装,下一步就是设置Dockerfile,为你的应用程序建立一个Docker镜像。一个镜像代表了一个环境的不可改变的快照,它包含了所有的源代码、依赖性和其他应用程序运行所需的文件。一旦创建了Docker镜像,它就可以被运送到另一台机器上执行,而不会出现兼容性问题。
Docker镜像是通过Dockerfile 。它是一个文本文件,包含一组连续执行的指令。这些指令是在一个父镜像上执行的,文件中的每一步都有助于为你的应用程序创建一个完全定制的镜像。
让我们继续,在项目目录的根部为我们的演示应用程序创建一个Dockerfile 。
$ touch Dockerfile
在你的文本编辑器中打开Dockerfile ,并在文件中添加第一行。
Dockerfile
FROM node:16-alpine
上述内容指定基础镜像为官方Node.js Alpine Linux镜像。这里使用Alpine Linux是因为它的体积小,这在将镜像从一台机器运输到另一台机器时有很大的帮助。
Dockerfile 中的下一行是这样的。
Dockerfile
WORKDIR /app
WORKDIR 指令将工作目录设置为/app 。如果这个目录不存在,将被创建。
使用下面几行来安装你的应用程序的依赖项:这是构建Docker镜像的关键步骤。注意,以# 开始的行表示注释。
Docker文件
# Copy and download dependencies
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
# Copy the source files into the image
COPY . .
接下来,我们需要通过EXPOSE 指令公开应用程序将运行的端口。
Dockerfile
EXPOSE 4000
最后,指定启动应用程序的命令。
Dockerfile
CMD yarn start
你可以看到下面的整个Dockerfile 。
Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY . .
EXPOSE 4000
CMD yarn start
构建Docker镜像
现在,Dockerfile 已经完成,是时候根据文件中的说明构建Docker镜像了。这可以通过docker build 命令来实现。你需要把Dockerfile 所在的目录和你喜欢的镜像名称传给它。
$ docker build . -t covid
如果一切顺利,构建成功,你会在命令输出的最后看到下面的信息。
Successfully built 973edfcb25d2
Successfully tagged covid:latest
你可以运行docker images ,查看所建镜像的一些基本信息。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
covid latest 973edfcb25d2 2 minutes ago 137MB
在容器中运行Docker镜像
使用docker run 命令在容器中运行你新制作的Docker镜像。由于应用程序已经被内置到镜像中,它拥有工作所需的一切。它可以在一个孤立的进程中直接启动。在你能够访问你在容器内运行的镜像之前,你必须通过--publish 或-p 标志将其端口暴露给外界。这可以让你把容器中的端口绑定到容器外的端口上。
$ docker run -p 4000:4000 covid
上面的命令在容器内启动了covid 镜像,并将容器内的4000端口暴露给容器外的4000端口。随后你可以通过http://localhost:4000 来访问你服务器上的路由。

共享Docker镜像
你可以通过各种方式将Docker镜像从一台机器转移到另一台。最流行的方法是使用docker push 命令将镜像推送到官方的Docker注册表,然后通过docker pull 命令来检索它。
你需要先在Docker Hub注册一个免费账户。注册过程完成后,前往Repositories页面,并创建一个新的仓库。给它起个名字,并把它的可见性设置为 "公共 "或 "私人"。注意,免费账户可以访问有限数量的私有仓库。

一旦你创建了一个仓库,在你的终端输入docker login 命令,在你的机器上登录到Docker Hub。

在你推送镜像到Docker Hub之前,你需要更新镜像标签以匹配你的版本库命名空间:<your docker username>/<repo name> 。这是因为docker push 命令希望有一个这种格式的参数。
输入下面的命令,给你的covid 镜像加上新的名字。确保用你的实际docker用户名替换<your docker username> 。
$ docker tag covid <your docker username>/covid
最后,使用docker push 命令将镜像推送到Docker Hub,如下图所示。
$ docker push <your docker username>/covid
一旦镜像被成功推送到注册表,它将反映在你的版本库仪表盘上。

你可以通过下面的命令在任何安装了docker 的机器上拉出镜像。如果版本库是私有的,你需要先通过docker login 命令登录。请记住,从注册表下载图像的速度取决于图像的大小和你的网络连接速度。这也是为什么一般情况下首选较小的Docker镜像的原因之一。
$ docker pull <your docker username>/covid
请注意,你也可以选择通过其他云服务(如GitLab、Google Cloud、RedHat等)提供的注册表来分享Docker镜像。你甚至可以在专用服务器上建立你自己的私人注册表,供组织内部使用。
不使用注册表分享Docker镜像
与他人分享Docker镜像的另一种方式是将其导出为.tar ,并通过任何首选的传输方式将其传输到不同的机器上。这可以帮助你在使用Docker注册表不可取或不可能的情况下,在机器之间传输Docker镜像,无论什么原因。docker save 命令是你需要用来导出Docker镜像的工具。
$ docker save covid > covid.tar
上述命令将把covid 镜像导出到当前目录下的一个covid.tar 文件。然后这个文件可以被传送到远程机器上,并通过docker load 命令加载到机器的本地注册表。
$ docker load < covid.tar
Loaded image: covid:latest
将你的Docker化Node.js应用程序部署到生产中
在远程服务器上部署Docker化应用程序的最简单方法是使用docker pull ,然后使用docker run ,传输应用程序的镜像。这与你在开发环境中的做法类似,在容器中运行应用程序。然而,对于一个真正的生产就绪的应用程序来说,这样的策略是不理想的。
与我们的演示应用程序不同,一个真实世界的产品可能由几个不同的服务组成,这些服务相互依赖,以使整个应用程序正常工作。部署到生产中通常意味着以正确的顺序启动所有的组件服务,以确保顺利运行。你还需要为其他任务制定策略,例如在发生故障时重启服务、汇总日志和执行健康检查。所有这些问题--甚至更多--都可以通过Docker Compose来处理。
Docker Compose通过一条命令协调多容器Docker应用。它依赖于一个Compose文件,该文件提供了一组指令来配置所有应该被生成的容器。下面是我们的演示应用程序的Compose文件(docker-compose.yml )的样子。
docker-compose.yml
version: '3'
services:
web:
image: covid
ports:
- "4000:4000"
environment:
NODE_ENV: production
上面的Compose文件使用了Compose文件格式的第3版,并定义了一个名为web 的服务,该服务使用我们之前设置的covid 镜像。如果你漏掉了image 属性,那么来自Dockerfile 的Docker镜像将被构建在当前目录下并用于服务。ports 属性定义了容器和主机的暴露端口,而environment 属性则设置了任何必要的环境变量。
一旦你有了docker-compose.yml 文件,你就可以用docker-compose up 命令来启动定义的服务。在运行该命令之前,请确保你已经安装了docker-compose ,否则请了解如何在你的操作系统上安装Docker Compose。
$ docker-compose up
Recreating covid-node_web_1 ... done
Attaching to covid-node_web_1
web_1 | yarn run v1.22.5
web_1 | $ node app.js
web_1 | {"level":30,"time":1630001521702,"pid":28,"hostname":"204c8ce51d52","msg":"Server listening at http://0.0.0.0:4000"}
该命令将启动所定义的服务的容器,它们将在指定的端口上被访问。注意,如果你退出这个命令(比如按Ctrl-C),每个生成的容器将立即停止。为了防止这种情况的发生,请添加--detach 标志,这样容器就会在后台启动并持续运行。
$ docker-compose up --detach
我们只是触及了Compose文件可以实现的工作流程的表面。请确保查看完整的文档,以了解更多关于所有可用的选项。docker-compose CLI还提供了其他几个重要的命令,你应该了解这些命令,以便最大限度地利用它。你可以通过--help 标志或CLI参考页来检查它们的每一个。
结论和进一步的Docker阅读
在这篇文章中,我们介绍了将现有的Node.js应用程序Docker化、构建容器以及通过Docker Compose部署到生产的过程。
请记住,Docker的内容比一篇文章所能涵盖的要多得多。请参考官方文档,了解更多关于编写Docker文件的最佳做法、保护Docker容器、日志和其他重要主题,以便在你的应用工作流程中有效使用Docker。