CI 管道的一个常见用例是构建用于部署应用程序的 Docker 映像。GitLab CI 是一个很好的选择,因为它支持集成的拉代理服务,这意味着更快的管道,以及用于存储构建图像的内置注册表。
在本指南中,我们将向您展示如何设置使用上述两种功能的 Docker 构建。您需要采取的步骤会略有不同,具体取决于您将用于管道的GitLab Runner 执行器类型。我们将在下面介绍 Shell 和 Docker 执行器。
如果您使用的是 Shell 执行程序,请确保您已在托管运行程序的机器上安装了 Docker。docker执行器通过使用Runner 主机上的二进制文件运行常规 shell 命令来工作。
前往您要为其构建映像的项目的 Git 存储库。.gitlab-ci.yml在存储库的根目录创建一个文件。该文件定义了将更改推送到项目时将运行的 GitLab CI 管道。
将以下内容添加到文件中:
stages:
- build
docker_build:
stage: build
script:
- docker build -t example.com/example-image:latest .
- docker push example.com/example-image:latest
这种简单的配置足以演示流水线驱动的映像构建的基础知识。GitLab 自动将您的 Git 存储库克隆到构建环境中,因此运行docker build将使用您的项目Dockerfile并使存储库的内容可用作构建上下文。
构建完成后,您可以docker push将映像添加到您的注册表中。否则,它将仅对运行构建的本地 Docker 安装可用。如果您使用的是私有注册表,docker login请先运行以提供正确的身份验证详细信息:
script:
- docker login -u $DOCKER_REGISTRY_USER -p $DOCKER_REGISTRY_PASSWORD
通过前往 GitLab Web UI 中的设置 > CI/CD > 变量来定义两个凭证变量的值。单击蓝色的“添加变量”按钮以创建一个新变量并分配一个值。GitLab 将使这些变量在用于运行作业的 shell 环境中可用。
GitLab Runner 的 Docker 执行器通常用于为每个作业提供一个完全干净的环境。该作业将在一个隔离的容器中执行,因此dockerRunner 主机上的二进制文件将无法访问。
Docker 执行器为您提供了两种可能的构建镜像的策略:使用 Docker-in-Docker,或者将主机的 Docker 套接字绑定到 Runner 的构建环境中。然后,您使用官方 Docker 容器镜像作为您的作业镜像,使docker命令在您的 CI 脚本中可用。
使用Docker-in-Docker (DinD) 来构建您的图像为您的每个作业提供了一个完全隔离的环境。执行构建的 Docker 进程将是 GitLab Runner 在主机上创建以运行 CI 作业的容器的子进程。
您需要注册您的 GitLab Runner Docker 执行程序并启用特权模式才能使用 DinD。注册跑步者–docker-privileged时添加标志:
sudo gitlab-runner register -n \
--url https://example.com \
--registration-token $GITLAB_REGISTRATION_TOKEN \
--executor docker \
--description "Docker Runner" \
--docker-image "docker:20.10" \
--docker-volumes "/certs/client" \
--docker-privileged
在 CI 管道中,将docker:dind图像添加为服务。这使得 Docker 作为链接到作业图像的单独图像可用。您将能够使用该docker命令使用docker:dind容器中的 Docker 实例构建映像。
services:
- docker:dind
docker_build:
stage: build
image: docker:latest
script:
- docker build -t example-image:latest .
使用 DinD 可为您提供完全隔离的构建,这些构建不会相互影响或您的主机。主要缺点是更复杂的缓存行为:每个作业都有一个新环境,以前构建的层将无法访问。您可以通过在构建之前尝试拉取图像的先前版本来部分解决此问题,然后使用–cache-from 构建标志使拉取的图像的层可用作缓存源:
docker_build:
stage: build
image: docker:latest
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $CI_REGISTRY_IMAGE:latest .
使用 Docker 执行程序时,将主机的 Docker 套接字挂载到作业环境中是一种替代选择。这为您提供了无缝缓存,并且无需将docker:dind服务添加到 CI 配置中。
docker-volumes要进行此设置,请使用将主机的 Docker 套接字绑定到/var/run/docker.sock内部作业容器的标志注册您的 Runner :
sudo gitlab-runner register -n \
--url https://example.com \
--registration-token $GITLAB_REGISTRATION_TOKEN \
--executor docker \
--description "Docker Runner" \
--docker-image "docker:20.10" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
现在,使用映像运行的作业docker将能够docker正常使用二进制文件。操作实际上将发生在您的主机上,成为作业容器的兄弟而不是子容器。
这实际上类似于在主机的 Docker 安装中使用 shell 执行器。图像将驻留在主机上,便于无缝使用常规docker build层缓存。
虽然这种方法可以带来更高的性能、更少的配置并且没有 DinD 的限制,但它也有其独特的问题。其中最突出的是安全隐患:作业可以在您的 Runner 主机上执行任意 Docker 命令,因此您的 GitLab 实例中的恶意项目可能会运行docker run -it malicious-image:latest或docker rm -f $(docker ps -a)产生破坏性后果。
GitLab还警告说,当作业同时运行时,套接字绑定可能会导致问题。当您依赖使用特定名称创建的容器时,就会发生这种情况。如果一个作业的两个实例并行运行,第二个实例将失败,因为容器名称已经存在于您的主机上。
如果您预计这些问题中的任何一个都会很麻烦,您应该考虑使用 DinD。虽然通常不再推荐DinD ,但它对于运行并发 CI 作业的面向公众的 GitLab 实例更有意义。
GitLab 项目可以选择集成注册表,您可以使用它来存储图像。您可以通过导航到项目侧栏中的 Packages & Registries > Container Registry 来查看注册表的内容。如果您没有看到此链接,请转到“设置”>“常规”>“可见性、项目、功能和权限”并激活“容器注册表”切换来启用注册表。
GitLab 会自动在您的 CI 作业中设置环境变量,让您可以引用项目的容器注册表。调整script部分以登录注册表并推送您的图像:
script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:latest
GitLab 为您的每个 CI 作业生成一组安全的凭据。环境变量将包含一个访问令牌,作业可以使用该令牌以用户 C I J O B T O K E N 身 份 连 接 到 注 册 表 g i t l a b − c i − t o k e n 。 注 册 服 务 器 U R L 以 CI_JOB_TOKEN身份连接到注册表gitlab-ci-token。注册服务器 URL 以 CIJOBTOKEN身份连接到注册表gitlab−ci−token。注册服务器URL以CI_REGISTRY.
最后一个变量 C I R E G I S T R Y I M A G E 提 供 了 项 目 容 器 注 册 表 的 完 整 路 径 。 这 是 您 的 图 像 标 签 的 合 适 基 础 。 您 可 以 扩 展 此 变 量 以 创 建 子 存 储 库 , 例 如 CI_REGISTRY_IMAGE提供了项目容器注册表的完整路径。这是您的图像标签的合适基础。您可以扩展此变量以创建子存储库,例如 CIREGISTRYIMAGE提供了项目容器注册表的完整路径。这是您的图像标签的合适基础。您可以扩展此变量以创建子存储库,例如CI_REGISTRY_IMAGE/production/api:latest.
其他 Docker 客户端可以通过使用访问令牌进行身份验证从注册表中提取图像。您可以在项目的 Settings > Access Tokens 屏幕上生成这些。添加read_registry范围,然后使用显示的凭据到docker login项目的注册表。
GitLab 的 Dependency Proxy为您从 Docker Hub 拉取的上游图像提供了一个缓存层。它仅在图像实际发生更改时才提取图像内容,从而帮助您保持在Docker Hub 的速率限制内。这也将提高构建的性能。
通过前往设置 > 包和注册表 > 依赖代理,在 GitLab 组级别激活依赖代理。.gitlab-ci.yml启用后,在您的文件中添加前缀图像引用,$CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX以通过代理将它们拉出:
docker_build:
stage: build
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/docker:latest
services:
- name: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/docker:dind
alias: docker
这里的所有都是它的!GitLab Runner 会自动登录到依赖代理注册表,因此无需手动提供您的凭据。
GitLab 现在将缓存您的图像,从而提高性能以及对网络中断的恢复能力。请注意,services定义也必须进行调整 - 环境变量不适用于之前使用的内联形式,因此name必须指定完整图像,然后在您的部分alias中引用一个命令。script
虽然我们现在已经为工作阶段直接使用的图像设置了代理,但还需要做更多的工作来在Dockerfile构建中添加对基本图像的支持。像这样的常规指令不会通过代理:
FROM ubuntu:latest
要添加最后一部分,请使用 Docker 的构建参数在单步执行 Dockerfile 时使依赖代理 URL 可用:
ARG GITLAB_DEPENDENCY_PROXY
FROM ${GITLAB_DEPENDENCY_PROXY}/ubuntu:latest
然后修改您的docker build命令以定义变量的值:
script:
>
- docker build \
--build-arg GITLAB_DEPENDENCY_PROXY=${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX} \
-t example-image:latest .
现在你的基础镜像也将通过依赖代理被拉取。
Docker 镜像构建很容易集成到您的 GitLab CI 管道中。在初始 Runner 配置之后,docker build您docker push的作业script部分中的命令就是您在存储库中创建映像所需的全部内容Dockerfile。GitLab 的内置容器注册表为您提供项目图像的私有存储。
除了基本构建之外,值得集成 GitLab 的依赖代理以提高性能并避免达到 Docker Hub 速率限制。您还应该通过评估您选择的方法是否允许不受信任的项目在您的 Runner 主机上运行命令来检查安装的安全性。虽然它有自己的问题,但当您的 GitLab 实例可公开访问或被大量用户群访问时,Docker-in-Docker 是最安全的方法。