如何将现有的Node.js应用程序Docker化

927 阅读6分钟
在这篇文章中

在Twitter上分享

  • 发表于: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
}

Screenshot of API response from disease.sh

虽然这是一个非常简单的应用,但它足以展示本教程中涉及的Docker概念。在下一节中,我们将看一下如何在你的机器上本地设置Docker引擎

安装Docker

在对一个应用程序进行Docker化之前,你需要安装Docker引擎。Docker官方手册提供了在各种操作系统上安装软件的指南,其中最引人注目的是在macOSWindows和各种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 来访问你服务器上的路由。

Screenshot of docker run command

共享Docker镜像

你可以通过各种方式将Docker镜像从一台机器转移到另一台。最流行的方法是使用docker push 命令将镜像推送到官方的Docker注册表,然后通过docker pull 命令来检索它。

你需要先在Docker Hub注册一个免费账户。注册过程完成后,前往Repositories页面,并创建一个新的仓库。给它起个名字,并把它的可见性设置为 "公共 "或 "私人"。注意,免费账户可以访问有限数量的私有仓库。

Screenshot of Docker hub create repo page

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

Screenshot of Docker login command

在你推送镜像到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

一旦镜像被成功推送到注册表,它将反映在你的版本库仪表盘上。

Screenshot of Docker hub repository

你可以通过下面的命令在任何安装了docker 的机器上拉出镜像。如果版本库是私有的,你需要先通过docker login 命令登录。请记住,从注册表下载图像的速度取决于图像的大小和你的网络连接速度。这也是为什么一般情况下首选较小的Docker镜像的原因之一。

$ docker pull <your docker username>/covid

请注意,你也可以选择通过其他云服务(如GitLabGoogle CloudRedHat等)提供的注册表来分享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。