镜像构建最佳实践
镜像分层
使用 docker image history
命令,你可以看到用于创建镜像中每一层的命令。
使用
docker image history
命令查看你创建的getting-started
镜像中的层。$ docker image history getting-started
你应该会看到类似以下的输出。
IMAGE CREATED CREATED BY SIZE COMMENT a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB 9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B <missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB <missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B <missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB <missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B <missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB
每一行代表镜像中的一个层。这里显示的是底部为基础层,顶部为最新层。通过这个命令,你还可以快速查看每一层的大小,这有助于诊断大型镜像。
你会注意到有些行被截断了。如果你加上
--no-trunc
标志,你会看到完整的输出。$ docker image history --no-trunc getting-started
层缓存
现在你已经看到了分层的实际效果,接下来需要学习一个重要技巧来帮助减少容器镜像的构建时间。一旦一个层发生变化,其后的所有层也必须重新创建。
请看你为入门应用创建的以下 Dockerfile。
# syntax=docker/dockerfile:1
FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
回顾镜像历史输出,你会看到 Dockerfile 中的每个命令都成为镜像中的一个新层。你可能还记得,当你对镜像进行更改时,需要重新安装 yarn 依赖。每次构建时都重新包含相同的依赖是没有意义的。
为了解决这个问题,你需要重构 Dockerfile 以支持依赖的缓存。对于基于 Node 的应用,这些依赖在 package.json
文件中定义。你可以先只复制这个文件,安装依赖,然后再复制其他所有文件。这样,只有当 package.json
文件发生变化时,yarn 依赖才会被重新创建。
更新 Dockerfile,使其先复制
package.json
,安装依赖,然后复制其他所有文件。# syntax=docker/dockerfile:1 FROM node:lts-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]
使用
docker build
构建一个新的镜像。$ docker build -t getting-started .
你应该会看到类似以下的输出。
[+] Building 16.1s (10/10) FINISHED => [internal] load build definition from Dockerfile => => transferring dockerfile: 175B => [internal] load .dockerignore => => transferring context: 2B => [internal] load metadata for docker.io/library/node:lts-alpine => [internal] load build context => => transferring context: 53.37MB => [1/5] FROM docker.io/library/node:lts-alpine => CACHED [2/5] WORKDIR /app => [3/5] COPY package.json yarn.lock ./ => [4/5] RUN yarn install --production => [5/5] COPY . . => exporting to image => => exporting layers => => writing image sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25 => => naming to docker.io/library/getting-started
现在,修改
src/static/index.html
文件。例如,将<title>
改为 "The Awesome Todo App"。现在,再次使用
docker build -t getting-started .
构建 Docker 镜像。这次,你的输出应该会略有不同。[+] Building 1.2s (10/10) FINISHED => [internal] load build definition from Dockerfile => => transferring dockerfile: 37B => [internal] load .dockerignore => => transferring context: 2B => [internal] load metadata for docker.io/library/node:lts-alpine => [internal] load build context => => transferring context: 450.43kB => [1/5] FROM docker.io/library/node:lts-alpine => CACHED [2/5] WORKDIR /app => CACHED [3/5] COPY package.json yarn.lock ./ => CACHED [4/5] RUN yarn install --production => [5/5] COPY . . => exporting to image => => exporting layers => => writing image sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda => => naming to docker.io/library/getting-started
首先,你会注意到构建速度快了很多。而且,你会看到有几个步骤使用了之前缓存的层。推送和拉取此镜像及其更新也会快得多。
多阶段构建
多阶段构建是一个非常强大的工具,可以帮助你使用多个阶段来创建镜像。它们有几个优点:
- 将构建时依赖与运行时依赖分离
- 通过仅包含应用运行所需内容来减小整体镜像大小
Maven/Tomcat 示例
构建基于 Java 的应用时,你需要一个 JDK 将源代码编译成 Java 字节码。然而,生产环境中并不需要 JDK。此外,你可能使用 Maven 或 Gradle 等工具来帮助构建应用。这些工具在最终镜像中也不需要。多阶段构建可以提供帮助。
# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package
FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
在此示例中,你使用一个阶段(称为 build
)来使用 Maven 执行实际的 Java 构建。在第二个阶段(从 FROM tomcat
开始),你从 build
阶段复制文件。最终镜像只生成最后一个阶段,可以使用 --target
标志覆盖。
React 示例
构建 React 应用时,你需要一个 Node 环境将 JS 代码(通常是 JSX)、SASS 样式表等编译成静态 HTML、JS 和 CSS。如果你不进行服务器端渲染,甚至在生产构建中都不需要 Node 环境。你可以将静态资源打包到静态 nginx 容器中。
# syntax=docker/dockerfile:1
FROM node:lts AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
在之前的 Dockerfile 示例中,它使用 node:lts
镜像执行构建(最大化层缓存),然后将输出复制到 nginx 容器中。
总结
在本节中,你学习了一些镜像构建的最佳实践,包括层缓存和多阶段构建。
相关信息
下一步
在下一节中,你将学习更多可用于继续了解容器的资源。