Docker 教程:为 Node.js 构建开发和生产环境

Docker

我当前主要的技术栈是 Node.js/Javascript ,并且与许多开发团队一样,我把我们团队的开发与产品环境移到了 Docker 容器化上来。 但是,当我开始深入学习 Docker 时, 我意识到许多技术文档关注在开发环境或发布的产品环境的讨论, 但鲜少有如何将开发与产品环境配置统一,同时适应两种需求的探讨。

本文, 我以 Node.js Docker 容器化配置演示了适应两种需求环境的例子, 解释使用 Docker 技术时如何确定设计规划, 及帮助您预设工作流。 以一个非常简单的例子开始, 当我们构想更复杂的场景和在有或没有 Docker 的环境中保持其统一性。

声明: 本指南是全面的且面向不同的 Docker 技术各个层次; 以您看来, 也许文中例示的指令都比较浅显, 但是, 我将试图从另外的视角解释其含义与技术背景。 以便在最后完成的配置上给您完全的理解或视图。

前提配置条件

案例需求描述

  • 基本的 Node.js Dockerfile 配置文件和 docker-compose 组件
  • 开发环境需 Nodemon 的运行, 而产品环境只需 Node 支持
  • 产品的 Docker 映像不引入开发环境中的依赖构件
  • 使用多级( multi-stage)构建 Docker 映像需要 node-gyp 的支持

添加 .dockerignore 文件

在配置我们项目的 Dockerfile 配置文件前, 先在应用目录下添加名为 .dockerignore 的文件。 它将指明,在 Dockerfile 文件中诸如 COPY/ADD 这些指令执行中自动忽略对某些文件的操作。 详情参考

node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
README.md
LICENSE
.vscode

基本 Node.js Dockerfile 配置文件

为保证清晰的流程理解, 我们将从最基本的一个为 Node.js 项目的配置开始, 简单来说, 这里不需要任何额外依赖或创建逻辑。

FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", "start" ]

在每个 Node.js Docker 技术文中,您都会看到类似的配置。 让我们简短地捋一下。

WORKDIR /usr/src/app

工作目录(workdir)是类似默认目录的设定, 它指定在 Dockerfile 配置中后续的 RUN ,CMD ,ENTRYPOINT , COPY 和 ADD 指令都执行在容器中的这个目录下。 在一些技术文中您会看到人们常创建 /app 目录, 并将它指定为工作目录。 但这并不是最佳实践。 使用容器中预有的 /usr/src/app 目录是最合适的。

COPY package*.json ./
RUN npm install

这里是另一最佳实践调整: 在复制您自己开发代码之前, 复制您的 package.json 和 package-lock.json 到容器中。 Docker 将缓存安装 node 模块作为独立的层, 因此, 若改变自有开发代码并执行重建映像, node 运行模块将不会重装, 只要您未改动过 package.json 包管理配置文件。 一般来说, 您不加这些配置, 也不会遇到什么问题。 通常, 您只需在 package.json 改变时, 执行重建 Docker 映像的工作, 那将重新开始创建 Docker 映像并重新安装所有模块。 换句话来说, 您并不想在您初始创建了适应开发环境的 Docker 映像后, 频繁地重建它。

引入 docker-compose 程序的时刻

在发布的产品中开始运行我们的应用之前, 必须设计开发它。 最好的工具是使用 docker-compose 。配置一系列您想运行的 容器/服务 ,通过在 YAML 配置文件中符合 YAML 语法的配置指令很容易将它们集成协调在一起。

version: '3'

services:
  example-service:
    build: .
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - 3000:3000
      - 9229:9229
    command: npm start

在上面基础的 docker-compose.yaml 配置例子中, 使用在您应用目录中的 Dockerfile 配置文件中的配置, 并映射挂载您当前应用目录到容器中的工作目录, 安装 node 模块到容器,此时,创建过程不会受您应用目录即时改变的影响。 主机端口 3000 被映射到容器相同端口,假定您要在此端口开启 web 服务。 端口 9229 被映射, 用于 debug 端口。 深入参考这里.

现在,使用如下命令运行您的应用:

docker-compose up

或使用 VS code 扩展。

使用这些配置, 我们暴露主机端口 3000 和 9229 做为主机与容器应用间的通讯端口, 挂载应用当前目录到容器的工作目录 /usr/src/app , 使用这样的技术手段,避免容器应用安装的 node 模块影响到主机的 node 模块环境。

这样就使 Dockerfile 配置都可以应用在开发环境和产品环境中吗?

不是

区别在于具体的配置指令(CMD)
首先, 一般您需要您的开发环境在您代码文件改变时重新载入。 对于这个需求, 您可使用 nodemon 后台服务进程工具。但是在产品环境, 并不需要它。 这意味着在开发环境的容器中和产品环境的容器中配置指令(CMD)不同。

对此有少量的不同配置:

1. 替换您应用容器不运行 nodemon ,可以在应用目录下的 package.json 中分别定义不同的指令脚本, 如下例示:

 "scripts": {
   "start": "nodemon --inspect=0.0.0.0 src/index.js",
   "start:prod": "node src/index.js"
 }

这种情况下, 您的 Dockerfile 配置文件可能如下面所示:

FROM node:10-alpine

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", “run”, "start:prod" ]

然而, 由于您的开发环境使用的是 docker-compose 配置, 我们可以在其中使用不同的配置指令, 对于上例情况,应当如下例示:

version: '3'

services:
   ### ... previous instructions
    command: npm start

2. 如果开发环境和产品环境仍有大的区别, 或者, 开发和产品都想使用 docker-compose 配置, 您可创建多个 docker-compose 或 Dockerfile 配置文件。 依据您的具体需求, 如 docker-compose.dev.yml 或 Dockerfile.dev 文件等。

管理安装包\
总体上最好是保持产品的 Docker 映像尽可能地小, 您不会想在产品中包含不必要的用于开发依赖的安装包。 并且尽可能在一个定义的 Dockerfile 配置中达到此需求。

重新检查 package.json 文件, 将开发依赖安装包分离。  深入参考这里 。 简短地说, 若您用选项 --production 标志执行 npm install 或设置您的 NODE_ENV 做为产品环境的应用容器, 所有的开发依赖包将不会在容器中安装。 我们现在在 Dockerfile 配置文件中添加一些语句来控制达到这个目标:

FROM node:10-alpine

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", “run”, "start:prod" ]

我们使用自设的逻辑控制

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

Docker 支持通过配置或 docker-compose 配置指令给创建映像传递创建参数。 NODE_ENV=development 使 development 将做为默认值, 除非我们修改它, 相关解释,参考 这里

现在,当使用 docker-compose 创建容器应用, 所有依赖包将安装。 当要创建产品容器应用时, 可传递用于产品的参数给创建命令,所有的用于开发依赖的包被忽略安装。 由于我使用 CI 服务创建容器, 我简单加那些选项。  参考这里

需要 node-gyp 支持的映像进行多级创建
你想运行在 Docker 中的每个应用不是仅依赖 JS , 一些应用需要使用 node-gyp 或其它原生的系统库。

有效解决这个问题的办法,可以使用  多级创建(multi-stage builds) , 它帮助我们安装所有共同需要的依赖包到一个相对独立的容器里且延续到之后的所有容器的创建中, 保持容器的干净。 Dockerfile 配置文件中配置类似如下:

# 第一级(阶段)创建指令
FROM node:10-alpine as builder

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

RUN apk --no-cache add python make g++

COPY package*.json ./
RUN npm install

# 第二级(阶段)创建指令
FROM node:10-alpine

WORKDIR /usr/src/app
COPY --from=builder node_modules node_modules

COPY . .

CMD [ "npm", “run”, "start:prod" ]

上面的例子中, 在第一级,安装并编译所有运行的基础依赖包, 之后, 在第二级, 仅复制在开发和产品环境中需要的 node_modules 到容器中。

配置行 RUN apk --no-cache add python make g++ 依据各项目的不同而不同, 依据对额外依赖包的需要而定。

COPY --from=builder node_modules node_modules

这行配置, 复制由第一级容器创建的 node_modules 文件目录到第二级创建的容器目录中。 由此, node_modules 被复制到第二级创建容器的工作目录 /usr/src/app 中。

总结

我希望本指南能帮助您理解如何组织和配置 Dockerfile 文件, 使它满足您在开发环境和产品环境的需求。 总结我们的建议主要有如下几点:

  • 尝试为开发环境和产品环境统一配置 Dockerfile 文件; 若不能统一; 把它们分开。
  • 不安装开发环境需要的 node_modules 在产品环境中。
  • 最终创建的 Docker 映像中, 不残留 node-gyp 或 node 模块依赖的扩展模块。
  • 使用 docker-compose 精心设计组织开发环境的构建。
  • 选择最新的技术精心设计组织产品环境容器, 那可以是 docker-compose, Docker Swarm 或 Kubernetes 。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://dev.to/alex_barashkov/using-dock...

译文地址:https://learnku.com/server/t/43012

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!