使用 Docker 构建 Docker 镜像
- Tier: Free, Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
您可以将 GitLab CI/CD 与 Docker 结合使用来创建 Docker 镜像。 例如,您可以创建应用程序的 Docker 镜像, 对其进行测试,然后推送到容器注册表。
要在 CI/CD 作业中运行 Docker 命令,您必须配置
GitLab Runner 以支持 docker 命令。此方法需要 privileged 模式。
如果您想在 Runner 上不启用 privileged 模式的情况下构建 Docker 镜像,
可以使用 Docker 替代方案。
在 CI/CD 作业中启用 Docker 命令
要在 CI/CD 作业中启用 Docker 命令,您可以使用:
使用 Shell 执行器
要在 CI/CD 作业中包含 Docker 命令,您可以将 Runner 配置为
使用 shell 执行器。在此配置中,gitlab-runner 用户运行
Docker 命令,但需要权限才能执行。
-
安装 GitLab Runner。
-
注册 一个 Runner。 选择
shell执行器。例如:sudo gitlab-runner register -n \ --url "https://gitlab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor shell \ --description "My Runner" -
在安装了 GitLab Runner 的服务器上,安装 Docker Engine。 查看支持的平台列表。
-
将
gitlab-runner用户添加到docker组:sudo usermod -aG docker gitlab-runner -
验证
gitlab-runner是否可以访问 Docker:sudo -u gitlab-runner -H docker info -
在 GitLab 中,将
docker info添加到.gitlab-ci.yml以验证 Docker 是否正常工作:default: before_script: - docker info build_image: script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
您现在可以使用 docker 命令(如果需要,可以安装 Docker Compose)。
当您将 gitlab-runner 添加到 docker 组时,您实际上是授予了 gitlab-runner 完整的 root 权限。
有关更多信息,请参阅 docker 组的安全性。
使用 Docker-in-Docker
“Docker-in-Docker” (dind) 表示:
- 您注册的 Runner 使用 Docker 执行器 或 Kubernetes 执行器。
- 执行器使用 Docker 提供的 Docker 容器镜像 来运行 您的 CI/CD 作业。
Docker 镜像包含所有 docker 工具,并且可以在特权模式下
在镜像上下文中运行作业脚本。
您应该使用启用 TLS 的 Docker-in-Docker, 这受到 GitLab.com 实例 Runner 的支持。
您应该始终固定镜像的特定版本,例如 docker:24.0.5。
如果您使用 docker:latest 这样的标签,您无法控制使用的版本。
这在新版本发布时可能会导致兼容性问题。
使用 Docker 执行器与 Docker-in-Docker
您可以使用 Docker 执行器在 Docker 容器中运行作业。
Docker 执行器中启用 TLS 的 Docker-in-Docker
Docker 守护进程支持通过 TLS 连接。在 Docker 19.03.12 及更高版本中,TLS 是默认设置。
此任务启用 --docker-privileged,这实际上禁用了容器的安全机制,并使您的主机面临权限
提升的风险。此操作可能导致容器逃逸。有关更多信息,请参阅
运行时权限和 Linux 功能。
要使用启用 TLS 的 Docker-in-Docker:
-
安装 GitLab Runner。
-
从命令行注册 GitLab Runner。使用
docker和privileged模式:sudo gitlab-runner register -n \ --url "https://gitlab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor docker \ --description "My Docker Runner" \ --docker-image "docker:24.0.5" \ --docker-privileged \ --docker-volumes "/certs/client"- 此命令注册一个新的 Runner 来使用
docker:24.0.5镜像(如果在作业级别未指定)。 它使用privileged模式来启动构建和服务容器。 如果您想使用 Docker-in-Docker, 您必须在 Docker 容器中始终使用privileged = true。 - 此命令为服务容器和构建容器挂载
/certs/client, 这是 Docker 客户端使用该目录中证书所必需的。有关更多信息,请参阅 Docker 镜像文档。
前面的命令创建一个类似于以下示例的
config.toml条目:[[runners]] url = "https://gitlab.com/" token = TOKEN executor = "docker" [runners.docker] tls_verify = false image = "docker:24.0.5" privileged = true disable_cache = false volumes = ["/certs/client", "/cache"] [runners.cache] [runners.cache.s3] [runners.cache.gcs] - 此命令注册一个新的 Runner 来使用
-
您现在可以在作业脚本中使用
docker。您应该包含docker:24.0.5-dind服务:default: image: docker:24.0.5 services: - docker:24.0.5-dind before_script: - docker info variables: # 当您使用 dind 服务时,您必须指示 Docker 与 # 在服务内部启动的守护进程通信。守护进程通过 # 网络连接而不是默认的 /var/run/docker.sock 套接字可用。 # Docker 19.03 会自动执行此操作 # 通过设置 DOCKER_HOST 在 # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03/docker-entrypoint.sh#L23-L29 # # 'docker' 主机是服务容器的别名,如 # https://docs.gitlab.com/ee/ci/services/#accessing-the-services 中所述。 # # 指定 Docker 创建证书的位置。Docker # 在启动时自动创建它们,并创建 # `/certs/client` 以在服务和作业容器之间共享, # 这要归功于 config.toml 中的卷挂载 DOCKER_TLS_CERTDIR: "/certs" build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
在 Docker-in-Docker 和构建容器之间使用共享卷上的 Unix 套接字
在 Docker 执行器中启用 TLS 的 Docker-in-Docker
方法中,volumes = ["/certs/client", "/cache"] 中定义的目录是
在构建之间持久化的。
如果多个使用 Docker 执行器 Runner 的 CI/CD 作业启用了 Docker-in-Docker 服务,则每个作业
都会写入目录路径。这种方法可能会导致冲突。
要解决此冲突,请在 Docker-in-Docker 服务和构建容器之间使用共享卷上的 Unix 套接字。 这种方法提高了性能,并在服务和客户端之间建立了安全连接。
以下是构建容器和服务容器之间临时共享的示例 config.toml:
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
executor = "docker"
[runners.docker]
image = "docker:24.0.5"
privileged = true
volumes = ["/runner/services/docker"] # 构建和服务容器之间临时共享的卷。Docker-in-Docker 服务创建一个 docker.sock。Docker 客户端通过 Docker Unix 套接字卷连接到 docker.sock。
job:
variables:
# 此变量由 DinD 服务和 Docker 客户端共享。
# 对于服务,它将指示 DinD 在此处创建 `docker.sock`。
# 对于客户端,它告诉 Docker 客户端连接到哪个 Docker Unix 套接字。
DOCKER_HOST: "unix:///runner/services/docker/docker.sock"
services:
- docker:24.0.5-dind
image: docker:24.0.5
script:
- docker versionDocker 执行器中禁用 TLS 的 Docker-in-Docker
有时有正当理由禁用 TLS。 例如,您无法控制正在使用的 GitLab Runner 配置。
假设 Runner 的 config.toml 类似于:
[[runners]]
url = "https://gitlab.com/"
token = TOKEN
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:24.0.5"
privileged = true
disable_cache = false
volumes = ["/cache"]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]您现在可以在作业脚本中使用 docker。您应该包含
docker:24.0.5-dind 服务:
default:
image: docker:24.0.5
services:
- docker:24.0.5-dind
before_script:
- docker info
variables:
# 使用 dind 服务时,您必须指示 docker 与
# 在服务内部启动的守护进程通信。守护进程通过
# 网络连接而不是默认的 /var/run/docker.sock 套接字可用。
#
# 'docker' 主机是服务容器的别名,如
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services 中所述
#
DOCKER_HOST: tcp://docker:2375
#
# 这指示 Docker 不要通过 TLS 启动。
DOCKER_TLS_CERTDIR: ""
build:
stage: build
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/testsDocker 执行器中启用代理的 Docker-in-Docker
您可能需要配置代理设置才能使用 docker push 命令。
有关更多信息,请参阅 使用 dind 服务时的代理设置。
使用 Kubernetes 执行器与 Docker-in-Docker
您可以使用 Kubernetes 执行器 在 Docker 容器中运行作业。
Kubernetes 中启用 TLS 的 Docker-in-Docker
要在 Kubernetes 中使用启用 TLS 的 Docker-in-Docker:
-
使用 Helm chart,更新
values.yml文件 以指定卷挂载。runners: config: | [[runners]] [runners.kubernetes] image = "ubuntu:20.04" privileged = true [[runners.kubernetes.volumes.empty_dir]] name = "docker-certs" mount_path = "/certs/client" medium = "Memory" -
您现在可以在作业脚本中使用
docker。您应该包含docker:24.0.5-dind服务:default: image: docker:24.0.5 services: - name: docker:24.0.5-dind variables: HEALTHCHECK_TCP_PORT: "2376" before_script: - docker info variables: # 使用 dind 服务时,您必须指示 Docker 与 # 在服务内部启动的守护进程通信。守护进程通过 # 网络连接而不是默认的 /var/run/docker.sock 套接字可用。 DOCKER_HOST: tcp://docker:2376 # # 'docker' 主机是服务容器的别名,如 # https://docs.gitlab.com/ee/ci/services/#accessing-the-services 中所述。 # # 指定 Docker 创建证书的位置。Docker # 在启动时自动创建它们,并创建 # `/certs/client` 以在服务和作业容器之间共享, # 这要归功于 config.toml 中的卷挂载 DOCKER_TLS_CERTDIR: "/certs" # 这些通常由入口点指定,但是 # Kubernetes 执行器不运行入口点 # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4125 DOCKER_TLS_VERIFY: 1 DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Kubernetes 中禁用 TLS 的 Docker-in-Docker
要在 Kubernetes 中使用禁用 TLS 的 Docker-in-Docker,您必须将前面的示例调整为:
- 从
values.yml文件中删除[[runners.kubernetes.volumes.empty_dir]]部分。 - 使用
DOCKER_HOST: tcp://docker:2375将端口从2376更改为2375。 - 使用
DOCKER_TLS_CERTDIR: ""指示 Docker 启动时禁用 TLS。
例如:
-
使用 Helm chart,更新
values.yml文件:runners: config: | [[runners]] [runners.kubernetes] image = "ubuntu:20.04" privileged = true -
您现在可以在作业脚本中使用
docker。您应该包含docker:24.0.5-dind服务:default: image: docker:24.0.5 services: - name: docker:24.0.5-dind variables: HEALTHCHECK_TCP_PORT: "2375" before_script: - docker info variables: # 使用 dind 服务时,您必须指示 Docker 与 # 在服务内部启动的守护进程通信。守护进程通过 # 网络连接而不是默认的 /var/run/docker.sock 套接字可用。 DOCKER_HOST: tcp://docker:2375 # # 'docker' 主机是服务容器的别名,如 # https://docs.gitlab.com/ee/ci/services/#accessing-the-services 中所述。 # # 这指示 Docker 不要通过 TLS 启动。 DOCKER_TLS_CERTDIR: "" build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Docker-in-Docker 的已知问题
Docker-in-Docker 是推荐的配置,但您应该注意以下问题:
-
docker-compose命令:此配置中默认不提供此命令。 要在作业脚本中使用docker-compose,请遵循 Docker Compose 安装说明。 -
缓存:每个作业都在新环境中运行。因为每个构建都有自己的 Docker 实例,所以并发作业不会导致冲突。 但是,由于没有层缓存,作业可能会变慢。请参阅 Docker 层缓存。
-
存储驱动:默认情况下,早期版本的 Docker 使用
vfs存储驱动, 它为每个作业复制文件系统。Docker 17.09 及更高版本使用--storage-driver overlay2,这是 推荐的存储驱动。有关详细信息,请参阅 使用 OverlayFS 驱动。 -
根文件系统:由于
docker:24.0.5-dind容器和 Runner 容器不共享其 根文件系统,您可以使用作业的工作目录作为 子容器的挂载点。例如,如果您有要与 子容器共享的文件,您可以在/builds/$CI_PROJECT_PATH下创建 一个子目录,并将其用作挂载点。有关更详细的解释,请参阅 问题 #41227。variables: MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt script: - mkdir -p "$MOUNT_POINT" - docker run -v "$MOUNT_POINT:/mnt" my-docker-image
使用 Docker 执行器与 Docker socket 绑定
要在 CI/CD 作业中使用 Docker 命令,您可以将 /var/run/docker.sock 绑定挂载到
容器中。然后 Docker 在镜像上下文中可用。
如果您绑定 Docker 套接字,您不能使用 docker:24.0.5-dind 作为服务。卷绑定也会影响服务,
使它们不兼容。
要在镜像上下文中使 Docker 可用,您需要挂载
/var/run/docker.sock 到启动的容器中。要使用 Docker
执行器执行此操作,将 "/var/run/docker.sock:/var/run/docker.sock" 添加到
[runners.docker] 部分中的卷。
您的配置应类似于以下示例:
[[runners]]
url = "https://gitlab.com/"
token = RUNNER_TOKEN
executor = "docker"
[runners.docker]
tls_verify = false
image = "docker:24.0.5"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
[runners.cache]
Insecure = false要在注册 Runner 时挂载 /var/run/docker.sock,包含以下选项:
sudo gitlab-runner register -n \
--url "https://gitlab.com/" \
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "docker:24.0.5" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock对于 docker-windows 执行器,使用类似于以下示例的配置:
[[runners]]
url = "https://gitlab.example.com/"
token = RUNNER_TOKEN
executor = "docker-windows"
[runners.docker]
tls_verify = false
image = "docker:windowsservercore-ltsc2022"
privileged = false
disable_cache = false
volumes = ["//./pipe/docker_engine://./pipe/docker_engine", "/cache"]
[runners.cache]
Insecure = false对于像 使用 CodeQuality 进行代码质量扫描 这样的复杂 Docker-in-Docker 设置,您必须匹配主机和容器路径才能正确执行。有关更多详细信息,请参阅 使用私有 Runner 进行基于 CodeClimate 的扫描。
为 docker:dind 服务启用注册表镜像
当 Docker 守护进程在服务容器内启动时,它使用 默认配置。您可能需要配置一个 注册表镜像 以 提高性能并确保您不会超过 Docker Hub 速率限制。
.gitlab-ci.yml 文件中的服务
您可以向 dind 服务添加额外的 CLI 标志来设置注册表
镜像:
services:
- name: docker:24.0.5-dind
command: ["--registry-mirror", "https://registry-mirror.example.com"] # 指定要使用的注册表镜像GitLab Runner 配置文件中的服务
如果您是 GitLab Runner 管理员,您可以指定 command 来配置 Docker 守护进程的注册表镜像。
dind 服务必须为
Docker 或
Kubernetes 执行器 定义。
Docker:
[[runners]]
...
executor = "docker"
[runners.docker]
...
privileged = true
[[runners.docker.services]]
name = "docker:24.0.5-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]Kubernetes:
[[runners]]
...
name = "kubernetes"
[runners.kubernetes]
...
privileged = true
[[runners.kubernetes.services]]
name = "docker:24.0.5-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]GitLab Runner 配置文件中的 Docker 执行器
如果您是 GitLab Runner 管理员,您可以为每个 dind 服务使用
镜像。更新
配置
以指定卷挂载。
例如,如果您有一个 /opt/docker/daemon.json 文件,内容如下:
{
"registry-mirrors": [
"https://registry-mirror.example.com"
]
}更新 config.toml 文件以将文件挂载到
/etc/docker/daemon.json。这为 GitLab Runner 创建的每个
容器挂载文件。配置被
dind 服务检测到。
[[runners]]
...
executor = "docker"
[runners.docker]
image = "alpine:3.12"
privileged = true
volumes = ["/opt/docker/daemon.json:/etc/docker/daemon.json:ro"]GitLab Runner 配置文件中的 Kubernetes 执行器
如果您是 GitLab Runner 管理员,您可以为每个 dind 服务使用
镜像。更新
配置
以指定ConfigMap 卷挂载。
例如,如果您有一个 /tmp/daemon.json 文件,内容如下:
{
"registry-mirrors": [
"https://registry-mirror.example.com"
]
}使用此文件的内容创建一个 ConfigMap。您可以使用以下命令执行此操作:
kubectl create configmap docker-daemon --namespace gitlab-runner --from-file /tmp/daemon.json您必须使用 Kubernetes 执行器用于创建作业 Pod 的命名空间。
创建 ConfigMap 后,您可以更新 config.toml
文件以将文件挂载到 /etc/docker/daemon.json。此更新
为 GitLab Runner 创建的每个
容器挂载文件。dind 服务检测到此配置。
[[runners]]
...
executor = "kubernetes"
[runners.kubernetes]
image = "alpine:3.12"
privileged = true
[[runners.kubernetes.volumes.config_map]]
name = "docker-daemon"
mount_path = "/etc/docker/daemon.json"
sub_path = "daemon.json"Docker socket 绑定的已知问题
当您使用 Docker socket 绑定时,您避免了在特权模式下运行 Docker。但是, 此方法的含义是:
-
通过共享 Docker 守护进程,您实际上禁用了所有 容器的安全机制,并使您的主机面临权限 提升的风险。这可能导致容器逃逸。例如,如果某个项目 运行了
docker rm -f $(docker ps -a -q),它将删除 GitLab Runner 容器。 -
并发作业可能无法工作。如果您的测试 创建具有特定名称的容器,它们可能会相互冲突。
-
任何由 Docker 命令创建的容器都是 Runner 的兄弟, 而不是 Runner 的子级。这可能会给您的 workflow 带来复杂性。
-
从源代码仓库将文件和目录共享到容器中可能无法 按预期工作。卷挂载是在主机 上下文中完成的,而不是在构建容器中。例如:
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
您不需要包含 docker:24.0.5-dind 服务,就像您使用
Docker-in-Docker 执行器时那样:
default:
image: docker:24.0.5
before_script:
- docker info
build:
stage: build
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests在 Docker-in-Docker 中注册表认证
当您使用 Docker-in-Docker 时,标准认证方法 不起作用,因为服务启动了一个新的 Docker 守护进程。您应该注册表认证。
使用 Docker 层缓存使 Docker-in-Docker 构建更快
使用 Docker-in-Docker 时,Docker 每次创建构建时都会下载镜像的所有层。您可以通过 使用 Docker 层缓存使构建更快。
使用 OverlayFS 驱动
GitLab.com 上的实例 Runner 默认使用 overlay2 驱动。
默认情况下,当使用 docker:dind 时,Docker 使用 vfs 存储驱动,
它在每次运行时复制文件系统。您可以通过使用不同的驱动来避免这种磁盘密集型操作,例如 overlay2。
要求
-
确保使用较新的内核,最好是
>= 4.2。 -
检查
overlay模块是否已加载:sudo lsmod | grep overlay如果没有看到结果,则模块未加载。要加载模块,请使用:
sudo modprobe overlay如果模块已加载,您必须确保模块在重启时加载。 在 Ubuntu 系统上,通过将以下行添加到
/etc/modules来实现:overlay
每个项目使用 OverlayFS 驱动
您可以通过在 .gitlab-ci.yml 中使用 DOCKER_DRIVER
CI/CD 变量 为每个项目单独启用驱动:
variables:
DOCKER_DRIVER: overlay2为每个项目使用 OverlayFS 驱动
如果您使用自己的 runners,
您可以通过在 config.toml 文件的
[[runners]] 部分 中设置 DOCKER_DRIVER
环境变量为每个项目启用驱动:
environment = ["DOCKER_DRIVER=overlay2"]如果您正在运行多个 runners,您必须修改所有配置文件。
有关更多信息,请参阅 runner 配置 和 使用 OverlayFS 存储驱动。
Docker 替代方案
您可以在不启用 Runner 上的特权模式的情况下构建容器镜像:
Buildah 示例
要将 Buildah 与 GitLab CI/CD 一起使用,您需要一个 runner, 它具有以下执行器之一:
在此示例中,您使用 Buildah 来:
- 构建 Docker 镜像。
- 将其推送到 GitLab 容器注册表。
在最后一步,Buildah 使用项目根目录下的
Dockerfile 构建 Docker 镜像。最后,它将镜像推送到
项目的容器注册表:
build:
stage: build
image: quay.io/buildah/stable
variables:
# 使用 buildah 的 vfs。Docker 默认提供 overlayfs,但 Buildah
# 无法在另一个 overlayfs 文件系统上堆叠 overlayfs。
STORAGE_DRIVER: vfs
# 以 docker 格式而不是标准 OCI 格式写入所有镜像元数据。
# 较新版本的 docker 可以处理 OCI 格式,但较旧版本,如
# Fedora 30 附带的版本,无法处理该格式。
BUILDAH_FORMAT: docker
FQ_IMAGE_NAME: "$CI_REGISTRY_IMAGE/test"
before_script:
# 从[预定义的 CI/CD 变量](../variables/_index.md#predefined-cicd-variables)
# 中获取 GitLab 容器注册表凭据以注册到注册表。
- echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
script:
- buildah images
- buildah build -t $FQ_IMAGE_NAME
- buildah images
- buildah push $FQ_IMAGE_NAME如果您使用部署到 OpenShift 集群的 GitLab Runner Operator,请尝试 使用无根容器构建镜像的 Buildah 教程。
使用 GitLab 容器注册表
构建 Docker 镜像后,您可以将其推送到 GitLab 容器注册表。