如何在 CI/CD 流水线中(快速)构建 Docker 镜像的技巧
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
Docker 镜像的构建技术近年来不断发展,涌现出了许多新的策略。其中一个常见的痛点是 CI/CD 流水线中镜像构建速度缓慢。许多流水线本身就是 Docker 容器,完全处于待优化状态。如果我们没有意识到这一点,就会错失许多优化机会。
以下是一些技巧和窍门,可以帮助您加快 Docker 镜像在构建流水线中的运行速度。我曾见过构建时间从几十分钟缩短到不到 60 秒,不过实际情况可能因人而异!
- 用法
.dockerfileignore:每次 Docker 构建的第一步都是 Docker 将整个目录打包成 tar 包,并将数据发送到 Docker 服务器。输出结果会显示为一行。这Sending build context to Docker daemon XX.XXMB应该只包含构建过程中使用的源代码和配置文件。它的大小不应该超过几十 MB。请务必排除所有不需要的文件。 - 不常更改的内容应该放在 Dockerfile 的顶部。例如,初始安装的软件包和静态环境变量都应该移到顶部。Docker 有缓存机制,但如果 Dockerfile 中第一行发生更改,之后的所有行都不会再被缓存。Dockerfile 的最后一行应该是添加你的代码,或者编译它!
一个典型的文件应该遵循以下流程:
FROM base-cotnainer
# Add all static environment variables, folders, users...
ENV IS_APPLICATION=1
RUN mkdir app
WORKDIR /app
RUN useradd -ms /bin/sh newuser
COMMAND ["python3", "main.py"]
# Install all base image packages
RUN apt install ...
# Copy over your library depency files (Python example below)
COPY Pipfile /app/Pipfile
COPY Pipfile.lock /app/Pipfile.lock
# Install your libraries (Python example below)
RUN pipenv install --deploy
# Lastly, copy the code over
COPY . /app/
-
命令中不要使用通配符
COPY。Docker 永远不会缓存该行命令。请务必明确指定! -
容器只需构建一次。没有任何理由需要多次构建应用程序。
-
同时构建所有容器。如果您有应用程序容器、测试容器、集成容器、性能容器等等,
请将它们作为并行任务/步骤/作业添加到您的流水线中。Docker 构建的大部分操作都与 I/O 任务相关,这些任务可以而且应该并行化。 -
手动复制库依赖项文件定义,然后在复制源代码之前运行库安装程序!
先执行此步骤可以缓存库的拉取过程(这感觉就像从互联网完整下载了一遍)。以下是
针对特定语言的示例:- Javascript
FROM [...] mkdir /app WORKDIR /app # ... COPY package.json /app COPY package-lock.json /app RUN npm install COPY [all your code and configuration]- Python(requirements.txt)
FROM [...] mkdir /app WORKDIR /app # ... COPY requirements.txt /app RUN pip3 install COPY [all your code and configuration]- Python(pipenv)
FROM [...] mkdir /app WORKDIR /app # ... COPY Pipfile /app COPY Pipfile.lock /app RUN pipenv install --deploy COPY [all your code and configuration]- Java(Maven)
FROM [...] mkdir /app WORKDIR /app # ... COPY pom.xml /app/pom.xml RUN mvn install COPY [all your code and configuration] RUN mvn package- Java(Gradle)
请将以下内容添加到您的
build.gradle文件中:task downloadDependencies(type: Exec) { configurations.testRuntime.files commandLine 'echo', 'Downloaded all dependencies' }示例 Dockerfile,用于将依赖项作为缓存层拉取。
FROM [...] mkdir /app WORKDIR /app # ... COPY build.gradle /app/build.gradle RUN ./gradlew downloadDependencies COPY [all your code and configuration] RUN ./gradlew build- .NET(Nuget)
FROM [...] mkdir /app WORKDIR /app # ... # Copy all solution files .sln (NO WILDCARDS!) COPY WebApp.sln /app/WebApp.sln # COPY all .csproj files (NO WILDCARDS!) COPY WebApp.Application/WebApp.Application.csproj /app/WebApp.Application/WebApp.Application.csproj # Yep.. gotta manually add them all (... Pokemon!) # This command will pull all library RUN dotnet restore COPY [all your code and configuration]- 不要预先拉取镜像。这样做速度慢而且无效。使用
BUILDKIT下面定义的新方法,Docker 会更智能地只拉取已缓存的镜像层。这样速度更快、效率更高,而且有效。
-
在构建过程中使用 Docker 的新库
BUILDKIT并将内联缓存信息集成到构建流程中。(这看似简单,实则意义重大)
要在流水线中实现正确的镜像缓存,我们需要设置以下三项:- 将环境变量设置
DOCKER_BUILDKIT为 1 - 添加一个构建参数,告诉新的 BUILDKIT 库在每个图像层中写入缓存信息:
--build-arg BUILDKIT_INLINE_CACHE=1 - 告诉 Docker 构建命令从哪里获取镜像以进行缓存。
--cache-from [remote-repo-url]/[CONTAINER_NAME]:latest完整的命令类似于:DOCKER_BUILDKIT=1 docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from [remote-repo-url]/[CONTAINER_NAME]:latest--tag [CONTAINER_NAME] 。
- 将环境变量设置
最后,别忘了将更新后的镜像推送到远程 Docker 仓库!
文章来源:https://dev.to/stevesmashcode/tips-on-building-a-docker-image-quickly-in-a-ci-cd-2ph7