docker 图像构建/部署的最佳实践

Best practice for build / deployment of docker images

我刚刚完成了 angular 应用程序的基本管道,该应用程序在 docker 中的节点映像中运行。所以,流程是这样的:推送到Gitlab > Hook to Jenkins Build > Deployment script to docker build image and push to Quay > Publish script to prompt Rancher service to upgrade the container and refresh image > Complete.

现在,我遇到的问题是基础节点映像非常大,这意味着当我推送一个简单的更改时,需要很长时间才能完成构建管道(~8 分钟)。对于每一个微小的变化,这似乎都是不合理的,推送到 Quay 并随后发布到 Rancher 平台意味着我要移动大约 250mb 到 quay 和 250mb 到 Rancher。

我有几个 "micro-services" 计划部署,但如果每次我想将一个部署到开发环境并每次移动那么多数据,这似乎有点适得其反......我就是这样我做错了什么,我错过了什么,building/deploying/hosting 基于容器的服务时是否有任何最佳实践指南?

首先是关于映像、构建、注册表和客户端的一些信息。

图像和图层

Docker 图像构建与图层一起工作。 Dockerfile 中的每一步都会提交一层覆盖在前一层之上。

FROM node                   ---- a6b9ffdcf522
RUN apt-get update -y        --- 72886b467bd2
RUN git clone whatever        -- 430615b3487a
RUN npm install                - 4f8ddac8d3b5 mynode:latest

构成图像的每一层都由 sha256 校验和单独标识。 docker images -a 中的 IMAGE ID 是其中的一小段。

运行 dockviz images -t 在构建主机上会让您更好地了解可以构建的层树。当构建 运行ning 时,您可以看到一个分支在增长,然后最后一层最终被标记,但该层保留在树中并为其父级维护 link。

构建缓存

Docker 默认情况下,每个构建步骤都会缓存构建。如果 docker 文件中的 RUN 命令未更改或您正在复制的 COPY 源文件未更改,则该构建步骤不需要再次 运行。该层保持不变,sha256 校验和 ID 和 docker 尝试构建下一层。

当docker到达需要重建的步骤时,dockviz 呈现的图像“树”将分叉以创建具有新校验和的新层。此后的任何步骤都需要再次 运行 并在新分支上创建一个层。

注册表

注册管理机构也理解这种分层。如果您只更改新标记图像中的最顶层,那是唯一需要上传到注册表的层 (There are caveats to this, it works best with a recent docker-1.10.1+ and registry 2.3+) 注册表将已经拥有构成图像 ID 的大部分图像的副本您的新“图像”和新层将需要发送。

客户

Docker 注册表客户端以相同的方式处理图层。拉取图像时,它实际上会下载构成图像的各个层(blob)。您可以从 docker pulldocker run 新图像时打印的图像 ID 列表中看到这一点。同样,如果大多数层都相同,则更新将只需要下载那些已更改的最顶层层,从而节省宝贵的时间。

最小化构建时间

所以你要关注的事情是

  • 保持图片尺寸小
  • 利用构建缓存
  • 使用常见的“标记”父图像。

保持图片尺寸小

节省时间的主要方法是首先无事可做。 图片中的数据越少越好。

如果你能避免使用完整的 OS,那就去做吧。当您可以在 busyboxalpine 图像上 运行 应用程序时,它会让 Docker 众神微笑。 alpine + a node.js 构建小于 50MB。 Go 二进制文件也是最小化大小的一个很好的例子。它们可以静态编译,并且没有依赖性,因此甚至可以 运行 在空白 scratch 图像上。

利用 Docker 的构建缓存

将最常更改的工件(很可能是您的代码)作为 Dockerfile 中的后期条目非常重要。如果构建必须为一个小文件更改更新完整的 50MB 数据,这会使构建步骤的缓存无效,则构建将变慢。

总会有一些更改使整个缓存失效(例如更新基础 node 图像)。这些你只需要偶尔忍受一下。

构建中不经常更新的任何其他内容都应该放在 Dockerfile 的顶部。

使用常见的“标记”父图像

虽然图像校验和从 Docker 1.10 开始得到了一定程度的修复,但使用共同的父图像保证您将从相同的共享图像 ID 开始,无论您在哪里使用该图像 FROM .

在 Docker 1.10 之前,图像 ID 只是一个随机的 uuid。如果您在多台主机上构建 运行ning,则图层可能全部失效并根据构建它们的主机进行替换。即使图层实际上是同一件事。

当您有多个服务和多个基本相同的 Dockerfile 服务时,共同的父图像也会有所帮助。每当您开始在多个 Dockerfile 中重复构建步骤时,请将这些步骤拉出到一个公共父映像中,这样层肯定会在您的所有服务之间共享。通过使用 node 图像作为基础,您实际上已经得到了这一点。

Node.js 技巧

如果您在代码部署每个构建之后 运行 宁 npm install 并且您有许多依赖项,则 npm install 会导致大量重复工作,但不会实际上每次构建都会改变很多。在代码更改之前有一个工作流程来构建您的 node_modules 可能是值得的。然后npm install只需要运行你当package.json更新

FROM node
WORKDIR /app
COPY package.json /app/package.json
RUN npm install && rm -rf ~/.npm
COPY . /app/
CMD [ "node", "/app/server.js" ]

分阶段构建

如果您依赖带有本机模块的 npm 包,您有时需要在容器中安装一个完整的构建链到 运行 npm install。分阶段构建现在可以轻松地将构建映像与 运行 映像分开。

FROM node:8 AS build
WORKDIR /build
RUN apt-get update \
 && apt-get install build-essential;
COPY package.json /build/package.json
RUN npm install; \
 && rm -rf ~/.npm;

# Stage 2 app image
FROM node:8-slim
WORKDIR /app
COPY --from=build /build/node_modules /app/node_modules
COPY . /app/ 
CMD [ "node", "/app/server.js" ]

其他

确保您的构建主机具有 ssd 和良好的互联网连接,因为 有时您必须进行完全重建,因此越快越好。 AWS 通常运行良好,因为您拉取和推送的包和图像也可能托管在 AWS 上。 AWS 还提供仅收取存储费用的图像注册服务 (ECR)。