如何使用 docker 多阶段构建为开发和生产创建最佳镜像(NodeJs 应用程序示例)
Docker在过去几年中人气飙升。它是改变软件工程师和 DevOps 工程师工作方式的工具之一。从 Docker v 17.05 开始引入多阶段构建功能,这有助于摒弃使用阶段和目标的旧式构建器模式。本文讨论了如何利用Node.js 示例应用程序构建适用于开发/测试和生产环境的最佳镜像。docker multi-stage build
Iron Doggy的Andrew Bain拍摄的照片
先决条件
- 您了解 docker 并了解基本的 docker 命令,例如 build、exec
- 你了解 docker-compose(不是必需的)
Docker 多阶段构建简介
Docker 多阶段构建允许我们使用多条语句分阶段构建 Docker 镜像FROM
。文件可以从一个阶段复制到另一个阶段。一个很好的例子是,一个294 MB 的Golang 1.13 官方镜像(即使使用 Alpine 也只有 123 MB)可能和你应用程序的 Go 可执行文件一样大。由于 Golang 编译后会生成一个可执行二进制文件,因此第一阶段可以对其进行编译,第二阶段可以生成一个 Alpine 镜像(5 MB)来运行该可执行文件。因此,如果你的 Go 应用程序二进制文件是 10 MB,那么你的镜像可以是 15 MB(10 MB 二进制文件 + 5 MB Alpine),而不是笨重的 294 MB 官方 Go 镜像或 123 MB Alpine Go 镜像。你也可以查看一个示例。
另一个很好的例子是前端 JavaScript 应用,你可以使用包含 Node、Webpack 以及所有必要的 npm dev 依赖项的应用来构建该应用。在下一阶段,可以使用精简的 nginx apline 镜像来提供服务,这样体积会小得多。
以下是docker多阶段构建的官方信息:
使用多阶段构建,您可以在 Dockerfile 中使用多个 FROM 语句。每个 FROM 指令可以使用不同的基础构建,并且每个指令都会开启一个新的构建阶段。您可以选择性地将构建文件从一个阶段复制到另一个阶段,从而将不需要的所有内容保留在最终镜像中。
遗憾的是,并非所有 Go 语言都能像 Go 那样编译成可执行二进制文件。不过,您可以利用多阶段构建来制作更符合需求的 Docker 镜像。下面我们将通过一个开源 Node.js 应用程序示例来探讨如何实现这一点。
多阶段构建之前的问题
我们将看到一个 Node.js 应用示例,它是一个使用 Express 构建的货币转换器 API。目前,Dockerfile 和构建存在以下问题:
- Nodemon 已安装在生产环境中
- 当前 docker 镜像没有开发依赖项(运行
npm install --production
) - 图像尺寸可以变得更小(即使使用 alpine)
以下是当前Dockerfile
和docker-compose.yml
针对本地开发的内容:
Dockerfile
FROM node:12-alpine
WORKDIR /src
COPY package.json package-lock.json /src/
RUN npm install --production
COPY . /src
EXPOSE 8080
RUN npm config set unsafe-perm true
RUN npm install -g nodemon
CMD ["node", "index.js"]
我们看到,nodemon
即使在生产环境中也安装了它,但这在生产环境中是不必要的。另一个问题是它没有开发依赖项,因此无法在 docker 内部运行测试。
Docker Compose 文件
web:
build: .
volumes:
- .:/src
command: npm start
ports:
- "8080:8080"
environment:
NODE_ENV: dev
VIRTUAL_HOST: 'currency.test'
VIRTUAL_PORT: 8080
不必担心,VIRTUAL_HOST
这VIRTUAL_PORT
是针对nginx 代理的。
当前图像大小
让我们看看运行后得到的图像有多大docker build . -t currency-api-original
。
所以目前它是 165 Mb,希望我们也可以在此过程中减小它的大小。
多阶段构建解决方案
现在,由于我们希望在开发构建中拥有开发依赖项,nodemon
而在生产构建中仅拥有生产 npm 依赖项,因此对 docker 相关文件进行了如下修改:
具有多阶段构建的 Dockerfile
FROM node:12-alpine as base
WORKDIR /src
COPY package.json package-lock.json /src/
COPY . /src
EXPOSE 8080
FROM base as production
ENV NODE_ENV=production
RUN npm install --production
CMD ["node", "index.js"]
FROM base as dev
ENV NODE_ENV=development
RUN npm config set unsafe-perm true && npm install -g nodemon
RUN npm install
CMD ["npm", "start"]
让我们分析一下这里发生了什么变化以及为什么会发生?以下是重点内容:
- 我们从一个有节点的基础镜像开始,然后将需要的文件复制到镜像中,如 1-5
- 对于生产,我们将其设置
NODE_ENV
为生产并安装非开发依赖项,还要注意我们运行节点(而不是 nodemon) - 在 Dockefile 的最后 6 行中,我们从基础创建镜像并设置
NODE_ENV
为开发,然后我们安装 nodemon,因为我们想要在开发环境中查看文件 - 在 dev 镜像构建时,我们安装所有 npm 依赖项,包括 dev 依赖项,以便我们可以运行测试
构建过程更加精简,我们也优化了 Docker 镜像,使其更加贴合环境。我们解决了上述问题,nodemon
生产环境不再依赖开发环境,测试可以在开发/测试环境中运行。这真是太棒了!
多阶段构建后的 Docker-compose 文件
version: '3.5'
services:
web:
build:
context: ./
target: dev
volumes:
- .:/src
command: npm start
ports:
- "8080:8080"
environment:
NODE_ENV: dev
VIRTUAL_HOST: 'currency.test'
VIRTUAL_PORT: 8080
docker-compose 文件的主要变化在于target:dev
构建参数。
所有更改都可以在此拉取请求中查看。我们来看看镜像现在有多大:
我们运行以下命令来构建开发和生产图像:
- docker build . -t currency-api-dev --target=dev
- docker build . -t currency-api-prod --target=production
我们已将旧镜像缩减约 25 MB,用于生产版本。这是因为我们移除了 nodemon 和一些用于生产的开发依赖项。即使是开发版本,也减少了约 21 MB。
结论
这里的重点是构建适合环境的 Docker 镜像,而多阶段构建正是解决这个问题的方法。您可以使用相同的概念通过 Composer 构建 PHP 镜像。例如,开发构建可以使用 xdebug 进行调试,生产构建可以默认启用 opcache。
链接:https://dev.to/geshan/how-to-use-docker-multi-stage-build-to-create-optimal-images-for-dev-and-production-4o82当您需要针对不同环境使用不同的东西时,请选择 docker 多阶段构建并避免使用 3 个不同的 dockerfile。