该材料的作者是DevOps工程师,我们今天发布其译文。 他说他必须使用
Docker 。 特别是,此容器管理平台用于Node.js应用程序生命周期的各个阶段。 使用Docker是一项最近非常流行的技术,它使您可以优化生产环境中Node.js项目的开发和输出过程。

现在,我们正在发布
一系列有关Docker
的文章 ,这些
文章专为那些希望学习该平台以在各种情况下使用的人而设计。 相同的材料主要侧重于在Node.js开发中对Docker的专业使用。
什么是码头工人?
Docker是一个旨在在操作系统级别组织虚拟化(容器化)的程序。 容器的核心是分层图像。 简而言之,Docker是一种工具,可让您使用独立于运行容器的操作系统的容器来创建,部署和运行应用程序。 该容器包括应用程序运行所需的基本OS映像,该应用程序所依赖的库以及该应用程序本身。 如果多个容器在同一台计算机上运行,则它们将一起使用此计算机的资源。 Docker容器可以打包使用多种技术创建的项目。 我们对基于Node.js的项目感兴趣。
创建一个Node.js项目
在将Node.js项目打包到Docker容器中之前,我们需要创建此项目。 来吧 这是该项目的
package.json
文件:
{ "name": "node-app", "version": "1.0.0", "description": "The best way to manage your Node app using Docker", "main": "index.js", "scripts": { "start": "node index.js" }, "author": "Ankit Jain <ankitjain28may77@gmail.com>", "license": "ISC", "dependencies": { "express": "^4.16.4" } }
要安装项目依赖项,请运行
npm install
命令。 在此命令过程中,除其他外,还将创建
package-lock.json
文件。 现在创建
index.js
文件,其中将包含项目代码:
const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send('The best way to manage your Node app using Docker\n'); }); app.listen(3000); console.log('Running on http://localhost:3000');
如您所见,这里我们描述了一个简单的服务器,该服务器返回一些文本以响应对其的请求。
创建Dockerfile
现在应用程序已准备就绪,让我们讨论如何将其打包到Docker容器中。 即,它将是关于任何基于Docker的项目中最重要的部分,即Dockerfile。
Dockerfile是一个文本文件,其中包含有关为应用程序创建Docker映像的说明。 该文件中的说明(如果不做详细介绍)描述了多层文件系统各层的创建,其中包含应用程序需要工作的所有内容。 Docker平台可以缓存图像层,当重新使用缓存中已存在的层时,可以加快构建图像的过程。
在面向对象的编程中,有一个类。 类用于创建对象。 在Docker中,可以将图像与类进行比较,并且可以将容器与图像实例即对象进行比较。 考虑一下生成Dockerfile的过程,这将有助于我们解决这一问题。
创建一个空的Dockerfile:
touch Dockerfile
由于我们将为Node.js应用程序构建一个容器,因此我们需要放入容器中的第一件事是基本的Node映像,该映像可在
Docker Hub上找到。 我们将使用LTS版本的Node.js。 结果,我们的Dockerfile的第一条语句将是以下语句:
FROM node:8
之后,为我们的代码创建一个目录。 同时,由于这里使用了
ARG
指令,因此,在容器的组装过程中,如有必要,我们可以指定
/app
以外的应用程序目录的名称。 有关本手册的详细信息,请参见
此处 。
由于我们使用Node映像,因此已经安装了Node.js和npm平台。 使用映像中已存在的内容,可以组织项目依赖项的安装。 使用
--production
标志(或者如果将
NODE_ENV
环境
NODE_ENV
设置为
production
),npm将不会安装
devDependencies
文件的
devDependencies
部分中列出的模块。
在这里,我们将
package*.json
文件复制到映像中,而不是例如复制所有项目文件。 我们这样做是因为Dockerfile
RUN
,
COPY
和
ADD
指令会创建其他映像层,因此您可以使用Docker平台层的缓存功能。 使用这种方法,下次我们收集类似的映像时,Docker将发现是否可以重用缓存中已经存在的映像层,如果可以,则将利用已有的映像层,而不是创建新的映像层层。 这使您可以在大型项目(包括许多npm模块)中的工作过程中组装层时,节省大量时间。
现在,将项目文件复制到当前工作目录。 在这里,我们将不使用
ADD指令,而是使用
COPY指令。 实际上,在大多数情况下,建议优先使用
COPY
指令。
与
COPY
相比,
ADD
指令具有某些功能,但是并不总是需要这些功能。 例如,我们正在讨论用于解压缩.tar归档文件和通过URL下载文件的选项。
Docker容器是隔离的环境。 这意味着当我们在容器中启动应用程序时,如果不打开该应用程序侦听的端口,我们将无法直接与它进行交互。 为了通知Docker某个容器中有某个应用程序正在侦听某个端口,可以使用
EXPOSE指令。
到目前为止,我们已经使用Dockerfile描述了应用程序将包含的映像以及成功启动所需的一切。 现在,将指令添加到允许您启动应用程序的文件中。 这是
CMD指令。 它允许您指定带有在容器启动时将执行的参数的特定命令,并且在必要时可以被命令行工具覆盖。
这是完成的Dockerfile的外观:
FROM node:8
图像组装
我们准备了一个Dockerfile文件,其中包含用于构建映像的说明,在此基础上,将创建一个具有运行中应用程序的容器。 通过执行以下形式的命令来组装图像:
docker build --build-arg <build arguments> -t <user-name>/<image-name>:<tag-name> /path/to/Dockerfile
在我们的情况下,它将如下所示:
docker build --build-arg APP_DIR=var/app -t ankitjain28may/node-app:V1 .
Dockerfile有一个描述参数
APP_DIR
的
ARG
语句。 在这里,我们设置其含义。 如果不这样做,它将采用文件中分配给它的值
app
。
组装完映像后,检查Docker是否可见。 为此,请运行以下命令:
docker images
响应该命令,大约应输出以下内容。
Docker映像图片启动
组装完Docker映像后,我们可以运行它,即创建一个实例,以工作容器表示。 为此,请使用以下命令:
docker run -p <External-port:exposed-port> -d --name <name of the container> <user-name>/<image-name>:<tag-name>
在我们的情况下,它将如下所示:
docker run -p 8000:3000 -d --name node-app ankitjain28may/node-app:V1
我们将使用以下命令询问系统有关工作容器的信息:
docker ps
响应于此,系统应输出如下内容:
Docker容器到目前为止,一切都按预期进行,尽管我们尚未尝试访问容器中运行的应用程序。 即,我们的名为
node-app
容器在端口
8000
上侦听。 为了尝试访问它,您可以打开一个浏览器,然后在
localhost:8000
。 另外,为了检查容器的运行状况,可以使用以下命令:
curl -i localhost:8000
如果该容器确实起作用,则将响应该命令返回下图所示的内容。
容器健康检查结果基于相同的映像,例如基于刚刚创建的映像,可以创建许多容器。 另外,您可以将我们的映像发送到Docker Hub注册表,这将使其他开发人员可以上载我们的映像并在家里启动适当的容器。 这种方法简化了项目的工作。
推荐建议
这里有一些值得考虑的建议,以便利用Docker的功能并创建尽可能紧凑的映像。
▍1。 始终创建.dockerignore文件
在您计划放置在容器中的项目文件夹中,始终需要创建一个
.dockerignore
文件。 它使您可以忽略构建映像时不需要的文件和文件夹。 使用这种方法,我们可以减少所谓的构建上下文,这将使我们能够快速组装图像并减小其大小。 该文件支持文件名模板,类似于
.gitignore
文件。 建议将命令添加到
.dockerignore
,因此Docker将忽略
/.git
文件夹,因为该文件夹通常包含较大的材质(尤其是在项目开发期间),并将其添加到映像中会导致其尺寸增加。 此外,将此文件夹复制到图像中没有多大意义。
▍2。 使用多阶段图像组装过程
当我们为某个组织收集项目时,请考虑该示例。 该项目使用许多npm软件包,每个这样的软件包都可以安装它依赖的其他软件包。 执行所有这些操作会导致在构建映像的过程中花费更多的时间(尽管这要归功于Docker的缓存功能,但这并不是什么大问题)。 更糟糕的是,包含特定项目依赖项的结果图像很大。 在这里,如果我们谈论的是前端项目,我们可以回想起这些项目通常是使用诸如webpack之类的捆绑程序进行处理的,这使得可以方便地将应用程序需要的所有内容打包到销售包中。 结果,不需要用于此类项目的npm软件包文件。 这意味着我们可以在使用相同的Webpack构建项目后摆脱此类文件。
有了这个想法,尝试执行以下操作:
但是,这种方法不适合我们。 就像我们已经说过的那样,
RUN
,
ADD
和
COPY
指令创建了Docker缓存的层,因此我们需要找到一种方法来处理依赖项的安装,构建项目,然后使用单个命令删除不必要的文件。 例如,它可能看起来像这样:
在此示例中,只有一个
RUN
语句可安装依赖项,
node_modules
项目并删除
node_modules
文件夹。 这导致一个事实,即图像的大小将不如包含
node_modules
文件夹的图像的大小大。 我们仅在项目的构建过程中使用此文件夹中的文件,然后将其删除。 的确,这种方法很糟糕,因为它需要花费很多时间来安装npm依赖项。 您可以使用多阶段图像组装技术来消除此缺陷。
想象一下,我们正在开发一个具有许多依赖项的前端项目,并且我们使用webpack来构建该项目。 通过这种方法,为了减少映像的大小,我们可以利用Docker的功能来进行
映像的多阶段组装 。
FROM node:8 As build
通过这种方法,生成的图像比以前的图像小得多,并且我们还使用了
node:alpine
图像,它本身很小。 这是一对图像的比较,在此过程中可以看出,
node:alpine
的图像比
node:8
的图像小得多。
比较来自节点存储库的图像▍3。 使用Docker缓存
努力使用Docker的缓存功能来构建您的映像。 当使用名称
package*.json
访问的文件时,我们已经注意了此功能。 这减少了图像的生成时间。 但是,不应轻率利用这一机会。
假设我们在Dockerfile中描述了在从基本
Ubuntu:16.04
映像创建的映像中安装软件包
Ubuntu:16.04
:
FROM ubuntu:16.04 RUN apt-get update && apt-get install -y \ curl \ package-1 \ . .
当系统处理该文件时,如果安装了很多软件包,则更新和安装操作将花费大量时间。 为了改善这种情况,我们决定利用Docker的层缓存功能并按如下方式重写Dockerfile:
FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \ curl \ package-1 \ . .
现在,第一次组装图像时,一切都按预期进行,因为尚未形成缓存。 想象一下,现在我们需要安装另一个软件包
package-2
。 为此,我们重写文件:
FROM ubuntu:16.04 RUN apt-get update RUN apt-get install -y \ curl \ package-1 \ package-2 \ . .
由于此命令,将不会安装或更新
package-2
。 怎么了 事实是,在执行
RUN apt-get update
指令时,Docker看不到该指令与之前执行的指令之间的任何区别,因此,它从缓存中获取数据。 并且此数据已经过时。 在处理
RUN apt-get install
指令
RUN apt-get install
系统会执行它,因为它看起来不像之前的Dockerfile中的类似指令,但是在安装过程中,可能会发生错误,或者将安装旧版本的软件包。 结果,事实证明,
update
和
install
命令必须在同一
RUN
指令中执行,如第一个示例中那样。 缓存是一个很棒的功能,但是鲁ck使用此功能可能会导致问题。
▍4。 减少图像层数
建议尽可能地尽量减少映像层的数量,因为每一层都是Docker映像的文件系统,这意味着映像中的层越小,它将越紧凑。 当使用图像组装的多阶段处理时,实现了图像中的层数的减少和图像尺寸的减小。
总结
在本文中,我们研究了在Docker容器中打包Node.js应用程序并使用此类容器的过程。 此外,我们提出了一些建议,这些建议不仅可以在为Node.js项目创建容器时使用。
亲爱的读者们! 如果您在处理Node.js项目时专业使用Docker,请与初学者分享有关有效使用该系统的建议。
