写点什么

基于 BuildKit 优化 Dockerfile 的构建

作者:琦彦
  • 2022 年 10 月 05 日
    中国香港
  • 本文字数:4382 字

    阅读完需:约 14 分钟

 Docker 通过读取 Dockerfile 中的指令自动构建镜像,Dockerfile 是一个文本文件,其中依次包含构建给定镜像所需的所有命令。

上面的解释摘自 Docker 的官方文档并总结了 Dockerfile 的用途。Dockerfile 的使用非常重要,因为它是我们的蓝图,是我们添加到 Docker 镜像中的层的记录。

本文,我们将学习如何利用BuildKit功能,这是 Docker v18.09 上引入的一组增强功能。集成 BuildKit 将为我们提供更好的性能,存储管理和安全性。

本文目标

  • 减少构建时间;

  • 缩小镜像尺寸;

  • 获得可维护性;

  • 获得可重复性;

  • 了解多阶段 Dockerfile;

  • 了解 BuildKit 功能。

先决条件

让我们开始吧!

简单的 Dockerfile 示例

以下是一个包含 Java 应用程序的未优化 Dockerfile 的示例。我们将逐步进行一些优化。

FROM debianCOPY . /appRUN apt-get updateRUN apt-get -y install openjdk-11-jdk ssh emacsCMD [“java”, “-jar”, “/app/target/my-app-1.0-SNAPSHOT.jar”]
复制代码

在这里,我们可能会问自己:构建需要多长时间?为了回答这个问题,让我们在本地开发环境上创建该 Dockerfile,并让 Docker 构建镜像。

# enter your Java app foldercd simple-java-maven-app-master# create a Dockerfilevim Dockerfile# write content, save and exitdocker pull debian:latest # pull the source imagetime docker build --no-cache -t docker-class . # overwrite previous layers# notice the build time0,21s user 0,23s system 0% cpu 1:55,17 total
复制代码

此时,我们的构建需要 1m55s。

如果我们仅启用 BuildKit 而没有其他更改,会有什么不同吗?

启用 BuildKit

BuildKit 可以通过两种方法启用:

  1. 在调用 Docker build 命令时设置 DOCKER_BUILDKIT = 1 环境变量,例如:

time DOCKER_BUILDKIT=1 docker build --no-cache -t docker-class .
复制代码
  1. 将 Docker BuildKit 设置为默认开启,需要在/etc/docker/daemon.json 进行如下设置,然后重启:

{ "features": { "buildkit": true } }
复制代码

BuildKit 最初的效果

DOCKER_BUILDKIT=1 docker build --no-cache -t docker-class .0,54s user 0,93s system 1% cpu 1:43,00 total
复制代码

此时,我们的构建需要 1m43s。在相同的硬件上,构建花费的时间比以前少了约 12 秒。这意味着构建几乎无需费力即可节约 10%左右的时间。

现在让我们看看是否可以采取一些额外的步骤来进一步改善。

从最小到最频繁变化的顺序

因为顺序对于缓存很重要,所以我们将 COPY 命令移到更靠近 Dockerfile 末尾的位置。

FROM debianRUN apt-get updateRUN apt-get -y install openjdk-11-jdk ssh emacsRUN COPY . /appCMD [“java”, “-jar”, “/app/target/my-app-1.0-SNAPSHOT.jar”]
复制代码

避免使用“COPY .”

选择更具体的 COPY 参数,以避免缓存中断。仅复制所需内容。

FROM debianRUN apt-get updateRUN apt-get -y install openjdk-11-jdk ssh vimCOPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

apt-get update 和 install 命令一起使用

这样可以防止使用过时的程序包缓存。

FROM debianRUN apt-get update && \    apt-get -y install openjdk-11-jdk ssh vimCOPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

删除不必要的依赖

在开始时,不要安装调试和编辑工具,以后可以在需要时安装它们。

FROM debianRUN apt-get update && \    apt-get -y install --no-install-recommends \    openjdk-11-jdkCOPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

删除程序包管理器缓存

你的镜像不需要此缓存数据。借此机会释放一些空间。

FROM debianRUN apt-get update && \    apt-get -y install --no-install-recommends \    openjdk-11-jdk && \    rm -rf /var/lib/apt/lists/*COPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

尽可能使用官方镜像

使用官方镜像有很多理由,例如减少镜像维护时间和减小镜像尺寸,以及预先配置镜像以供容器使用。

FROM openjdkCOPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

使用特定标签

请勿使用 latest 标签。

FROM openjdk:8COPY target/my-app-1.0-SNAPSHOT.jar /appCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

寻找最小的镜像

以下是 openjdk 镜像列表。选择最适合自己的最轻的那个镜像。

在一致的环境中从源构建

如果你不需要整个 JDK,则可以使用 Maven Docker 镜像作为构建基础。

FROM maven:3.6-jdk-8-alpineWORKDIR /appCOPY pom.xml .COPY src ./srcRUN mvn -e -B packageCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

在单独的步骤中获取依赖项

可以缓存--用于获取依赖项的 Dockerfile 命令。缓存此步骤将加快构建速度。

FROM maven:3.6-jdk-8-alpineWORKDIR /appCOPY pom.xml .RUN mvn -e -B dependency:resolveCOPY src ./srcRUN mvn -e -B packageCMD [“java”, “-jar”, “/app/my-app-1.0-SNAPSHOT.jar”]
复制代码

多阶段构建:删除构建依赖项

为什么要使用多阶段构建?

  • 将构建与运行时环境分开

  • DRY 方式

  • 具有开发,测试等环境的不同详细信息

  • 线性化依赖关系

  • 具有特定于平台的阶段

FROM maven:3.6-jdk-8-alpine AS builderWORKDIR /appCOPY pom.xml .RUN mvn -e -B dependency:resolveCOPY src ./srcRUN mvn -e -B packageFROM openjdk:8-jre-alpineCOPY --from=builder /app/target/my-app-1.0-SNAPSHOT.jar /CMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]
复制代码

如果你此时构建我们的应用程序,

time DOCKER_BUILDKIT=1 docker build --no-cache -t docker-class .0,41s user 0,54s system 2% cpu 35,656 total
复制代码

你会注意到我们的应用程序构建需要大约 35.66 秒的时间。这是一个令人愉快的进步。

下面,我们将介绍其他场景的功能。

多阶段构建:不同的镜像风格

下面的 Dockerfile 显示了基于 Debian 和基于 Alpine 的镜像的不同阶段。

FROM maven:3.6-jdk-8-alpine AS builderFROM openjdk:8-jre-jessie AS release-jessieCOPY --from=builder /app/target/my-app-1.0-SNAPSHOT.jar /CMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]FROM openjdk:8-jre-alpine AS release-alpineCOPY --from=builder /app/target/my-app-1.0-SNAPSHOT.jar /CMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]
复制代码

要构建特定的镜像,我们可以使用--target 参数:

time docker build --no-cache --target release-jessie .
复制代码

不同的镜像风格(DRY /全局 ARG)

ARG flavor=alpineFROM maven:3.6-jdk-8-alpine AS builderFROM openjdk:8-jre-$flavor AS releaseCOPY --from=builder /app/target/my-app-1.0-SNAPSHOT.jar /CMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]
复制代码

ARG 命令可以指定要构建的镜像。在上面的例子中,我们指定 alpine 为默认的镜像,但我们也可以在 docker build 命令中,通过--build-arg flavor=<flavor>参数指定镜像。

time docker build --no-cache --target release --build-arg flavor=jessie .
复制代码

并发

并发在构建 Docker 镜像时很重要,因为它会充分利用可用的 CPU 线程。在线性 Dockerfile 中,所有阶段均按顺序执行。通过多阶段构建,我们可以让较小的依赖阶段准备就绪,以供主阶段使用它们。

BuildKit 甚至带来了另一个性能上的好处。如果在以后的构建中不使用该阶段,则在结束时将直接跳过这些阶段,而不是对其进行处理和丢弃。

下面是一个示例 Dockerfile,其中网站的资产是在一个 assets 阶段中构建的:

FROM maven:3.6-jdk-8-alpine AS builderFROM tiborvass/whalesay AS assetsRUN whalesay “Hello DockerCon!” > out/assets.htmlFROM openjdk:8-jre-alpine AS releaseCOPY --from=builder /app/my-app-1.0-SNAPSHOT.jar /COPY --from=assets /out /assetsCMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]
复制代码

这是另一个 Dockerfile,其中分别编译了 C 和 C ++库,并在 builder 以后使用该阶段。

FROM maven:3.6-jdk-8-alpine AS builder-baseFROM gcc:8-alpine AS builder-someClibRUN git clone … ./configure --prefix=/out && make && make installFROM g++:8-alpine AS builder-some CPPlibRUN git clone … && cmake …FROM builder-base AS builderCOPY --from=builder-someClib /out /COPY --from=builder-someCpplib /out /
复制代码

BuildKit 应用程序缓存

BuildKit 具有程序包管理器缓存的特殊功能。以下是一些缓存文件夹位置的示例:

我们可以将此 Dockerfile 与上面介绍的在一致的环境中从源代码构建中介绍的 Dockerfile 进行比较。这个较早的 Dockerfile 没有特殊的缓存处理。我们可以使用--mount=type=cache 来做到这一点。

FROM maven:3.6-jdk-8-alpine AS builderWORKDIR /appRUN --mount=target=. --mount=type=cache,target /root/.m2 \    && mvn package -DoutputDirectory=/FROM openjdk:8-jre-alpineCOPY --from=builder /app/target/my-app-1.0-SNAPSHOT.jar /CMD [“java”, “-jar”, “/my-app-1.0-SNAPSHOT.jar”]
复制代码

BuildKit 的安全功能

BuildKit 具有安全功能,下面的示例中,我们使用了--mount=type=secret 隐藏了一些机密文件,例如~/.aws/credentials。

FROM <baseimage>RUN …RUN --mount=type=secret,id=aws,target=/root/.aws/credentials,required \./fetch-assets-from-s3.shRUN ./build-scripts.sh
复制代码

要构建此 Dockerfile,需要使用--secret 参数:

docker build --secret id=aws,src=~/.aws/credentials
复制代码

还有为了提高安全性,避免使用诸如 COPY ./keys/private.pem /root .ssh/private.pem 之类的命令,我们可以使用 BuildKit 中的 ssh 解决此问题:

FROM alpineRUN apk add --no-cache openssh-clientRUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hostsARG REPO_REF=19ba7bcd9976ef8a9bd086187df19ba7bcd997f2RUN --mount=type=ssh,required git clone git@github.com:org/repo /work && cd /work && git checkout -b $REPO_REF
复制代码

要构建此 Dockerfile,你需要在 ssh-agent 中加载到你的 SSH 私钥。

eval $(ssh-agent)ssh-add ~/.ssh/id_rsa # this is the SSH key default locationdocker build --ssh=default .
复制代码

结论

本文,我们介绍了使用 Docker BuildKit 优化 Dockerfile,并因此加快了镜像构建时间。这些速度的提高,可以帮助我们提高效率和节省计算能力。

译文链接:Dockerfile Optimization for Fast Builds and Light Images - DZone DevOps

发布于: 刚刚阅读数: 4
用户头像

琦彦

关注

孤独的技术没有价值 2019.08.24 加入

还未添加个人简介

评论

发布
暂无评论
基于BuildKit优化Dockerfile的构建_Dockerfile_琦彦_InfoQ写作社区