- 在 CI/CD 作业中启用 Docker 命令
- 使用 Docker-in-Docker 中的镜像库进行身份验证
- 使用 Docker 层缓存使 Docker-in-Docker 构建速度更快
- 使用 OverlayFS 驱动程序
- 使用 GitLab Container Registry
- 故障排查
使用 Docker 构建 Docker 镜像
您可以将 GitLab CI/CD 与 Docker 结合使用来创建 Docker 镜像。 例如,您可以创建应用程序的 Docker 镜像,对其进行测试,然后将其发布到容器镜像库。
要在 CI/CD 作业中运行 Docker 命令,您必须配置 GitLab Runner 以支持 docker
命令。
在 CI/CD 作业中启用 Docker 命令
要为 CI/CD 作业启用 Docker 命令,您可以使用:
如果不想在特权模式下执行 runner,而想使用 docker build
,也可以使用 kaniko
或 buildah
。
使用 shell executor
要在 CI/CD 作业中包含 Docker 命令,您可以将 runner 配置为使用 shell
executor。在此配置中,gitlab-runner
用户运行 Docker 命令,但需要权限才能这样做。
- 安装 GitLab Runner。
-
注册 一个 runner。 选择
shell
executor。例如: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
: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 executor 或 Kubernetes executor。
- Executor 使用 Docker 提供的 Docker 容器镜像 来运行您的 CI/CD 作业。
Docker 镜像安装了所有的 docker
工具,并且可以在特权模式下,在镜像的上下文中运行作业脚本。
我们建议您使用 Docker-in-Docker with TLS enabled。
您应该始终指定镜像的特定版本,例如 docker:20.10.16
。
如果使用像 docker:stable
这样的标签,你就无法控制使用哪个版本。
可能会导致不可预测的行为,尤其是在发布新版本时。
将 Docker executor 与 Docker-in-Docker 一起使用
您可以使用 Docker executor 在 Docker 容器中运行作业。
在 Docker executor 中启用 TLS 的 Docker-in-Docker
Docker 守护进程支持通过 TLS 的连接。在 Docker 20.10.16 及更高版本中,TLS 是默认设置。
--docker-privileged
。当您这样做时,您实际上是在禁用容器的所有安全机制并将您的主机暴露给特权升级。这样做会导致 container breakout。有关更多信息,请参阅有关运行时权限和 Linux 功能 的官方 Docker 文档。要在启用 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:20.10.16" \ --docker-privileged \ --docker-volumes "/certs/client"
- 这个命令注册一个新的 runner 来使用
docker:20.10.16
镜像。要启动构建和服务容器,它使用privileged
模式。如果您想使用 Docker-in-Docker,您必须始终在 Docker 容器中使用privileged = true
。 - 此命令为服务和构建容器挂载
/certs/client
,这是 Docker 客户端使用该目录中的证书所必需的。有关使用 TLS 的 Docker 如何工作的更多信息,请参阅 https://hub.docker.com/_/docker/#tls。
前面的命令创建了一个类似于这样的
config.toml
条目:[[runners]] url = "https://gitlab.com/" token = TOKEN executor = "docker" [runners.docker] tls_verify = false image = "docker:20.10.16" privileged = true disable_cache = false volumes = ["/certs/client", "/cache"] [runners.cache] [runners.cache.s3] [runners.cache.gcs]
- 这个命令注册一个新的 runner 来使用
-
您现在可以在作业脚本中使用
docker
。注意包含了docker:20.10.16-dind
服务:image: docker:20.10.16 variables: # When you use the dind service, you must instruct Docker to talk with # the daemon started inside of the service. The daemon is available # with a network connection instead of the default # /var/run/docker.sock socket. Docker 19.03 does this automatically # by setting the DOCKER_HOST in # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03/docker-entrypoint.sh#L23-L29 # # The 'docker' hostname is the alias of the service container as described at # https://docs.gitlab.com/ee/ci/services/#accessing-the-services. # # Specify to Docker where to create the certificates. Docker # creates them automatically on boot, and creates # `/certs/client` to share between the service and job # container, thanks to volume mount from config.toml DOCKER_TLS_CERTDIR: "/certs" services: - docker:20.10.16-dind before_script: - docker info build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
在 Docker executor 中禁用 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:20.10.16"
privileged = true
disable_cache = false
volumes = ["/cache"]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
您现在可以在作业脚本中使用 docker
。请注意包含 docker:20.10.16-dind
服务:
image: docker:20.10.16
variables:
# When using dind service, you must instruct docker to talk with the
# daemon started inside of the service. The daemon is available with
# a network connection instead of the default /var/run/docker.sock socket.
#
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
#
# If you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
# the variable must be set to tcp://localhost:2375 because of how the
# Kubernetes executor connects services to the job container
# DOCKER_HOST: tcp://localhost:2375
#
DOCKER_HOST: tcp://docker:2375
#
# This instructs Docker not to start over TLS.
DOCKER_TLS_CERTDIR: ""
services:
- docker:20.10.16-dind
before_script:
- docker info
build:
stage: build
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
将 Kubernetes executor 与 Docker-in-Docker 一起使用
您可以使用 Kubernetes executor 在 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:19.03.13-dind
服务:image: docker:19.03.13 variables: # When using dind service, you must instruct Docker to talk with # the daemon started inside of the service. The daemon is available # with a network connection instead of the default # /var/run/docker.sock socket. DOCKER_HOST: tcp://docker:2376 # # The 'docker' hostname is the alias of the service container as described at # https://docs.gitlab.com/ee/ci/services/#accessing-the-services. # If you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier, # the variable must be set to tcp://localhost:2376 because of how the # Kubernetes executor connects services to the job container # DOCKER_HOST: tcp://localhost:2376 # # Specify to Docker where to create the certificates. Docker # creates them automatically on boot, and creates # `/certs/client` to share between the service and job # container, thanks to volume mount from config.toml DOCKER_TLS_CERTDIR: "/certs" # These are usually specified by the entrypoint, however the # Kubernetes executor doesn't run entrypoints # https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4125 DOCKER_TLS_VERIFY: 1 DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" services: - docker:19.03.13-dind 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-compose
命令:默认情况下,此命令在此配置中不可用。要在您的作业脚本中使用docker-compose
,请遵循docker-compose
安装说明。 - 缓存:每个作业都在新环境中运行。并发作业工作正常,因为每个构建都有自己的 Docker engine 实例,并且它们不会相互冲突。但是,作业可能会变慢,因为没有层缓存。
-
存储驱动程序:默认情况下,早期版本的 Docker 使用
vfs
存储驱动程序,它为每个作业复制文件系统。Docker 17.09 及更高版本使用--storage-driver overlay2
,这是推荐的存储驱动程序。有关详细信息,请参阅使用 OverlayFS 驱动程序。 -
根文件系统:因为
docker:20.10.16-dind
容器和 runner 容器不共享它们的根文件系统,您可以使用作业的工作目录作为子容器的挂载点。例如,如果您想与子容器共享文件,您可以在/builds/$CI_PROJECT_PATH
下创建一个子目录并将其用作挂载点。variables: MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt script: - mkdir -p "$MOUNT_POINT" - docker run -v "$MOUNT_POINT:/mnt" my-docker-image
使用 Docker 套接字绑定
要在 CI/CD 作业中使用 Docker 命令,您可以将 /var/run/docker.sock
绑定挂载到容器中。然后 Docker 在镜像的上下文中可用。
docker:19.03 .12-dind
作为服务。服务进行了卷绑定,使这些服务不兼容。将 Docker executor 与 Docker 套接字绑定一起使用
要使 Docker 在镜像上下文中可用,您需要将 /var/run/docker.sock
挂载到启动的容器中。要使用 Docker executor 执行此操作,您需要将 "/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:20.10.16"
privileged = false
disable_cache = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
[runners.cache]
Insecure = false
您也可以在注册 runner 时通过提供以下选项来执行此操作:
sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
--description "My Docker Runner" \
--docker-image "docker:20.10.16" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
为 docker:dind
服务启用镜像库镜像
当 Docker 守护进程在服务容器内启动时,它使用默认配置。您可能需要配置镜像库镜像 以提高性能,并确保您不会达到 Docker Hub 速率限制。
.gitlab-ci.yml
文件中的服务
您可以将额外的 CLI 标志附加到 dind
服务以设置镜像库镜像:
services:
- name: docker:19.03.13-dind
command: ["--registry-mirror", "https://registry-mirror.example.com"] # Specify the registry mirror to use
GitLab Runner 配置文件中的服务
如果您是 GitLab Runner 管理员,则可以指定 command
来为 Docker 守护程序配置镜像库镜像。必须为 Docker 或 Kubernetes executor 定义 dind
服务。
Docker:
[[runners]]
...
executor = "docker"
[runners.docker]
...
privileged = true
[[runners.docker.services]]
name = "docker:19.03.13-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]
Kubernetes:
[[runners]]
...
name = "kubernetes"
[runners.kubernetes]
...
privileged = true
[[runners.kubernetes.services]]
name = "docker:19.03.13-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]
GitLab Runner 配置文件中的 Docker executor
如果您是 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 executor
如果您是 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
创建 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 套接字绑定的限制
使用 Docker 套接字绑定时,可以避免在特权模式下运行 Docker。但是,这种方法:
- 通过共享 Docker 守护进程,您可以有效地禁用容器的所有安全机制,并使您的主机暴露在权限提升中,这可能导致 container breakout。例如,如果一个项目运行
docker rm -f $(docker ps -a -q)
,它将删除 GitLab Runner 容器。 - 并发作业可能不起作用;如果您的测试创建具有特定名称的容器,它们可能会相互冲突。
- 任何由 Docker 命令生成的容器都是 runner 的 siblings,而不是 runner 的子节点。这可能具有不适合您的工作流程的复杂性和限制。
-
将源仓库中的文件和目录共享到容器中可能无法按预期工作。卷挂载是在 host machine context 中完成的,而不是构建容器。例如:
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
您不需要像使用 Docker-in-Docker executor 时那样包含 docker:20.10.16-dind
服务:
image: docker:20.10.16
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 守护进程随服务一起启动。
选项 1:运行 docker login
在 before_script
中,运行 docker login
:
image: docker:19.03.13
variables:
DOCKER_TLS_CERTDIR: "/certs"
services:
- docker:19.03.13-dind
build:
stage: build
before_script:
- echo "$DOCKER_REGISTRY_PASS" | docker login $DOCKER_REGISTRY --username $DOCKER_REGISTRY_USER --password-stdin
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
要登录 Docker Hub,请将 $DOCKER_REGISTRY
留空或将其删除。
选项 2:在每个作业上挂载 ~/.docker/config.json
如果您是 GitLab Runner 的管理员,您可以将带有身份验证配置的文件挂载到 ~/.docker/config.json
。
然后,runner 选择的每个作业都已经过身份验证。如果您使用的是官方 docker:19.03.13
镜像,则主目录在 /root
下。
如果您挂载配置文件,任何修改 ~/.docker/config.json
的 docker
命令都会失败。例如,docker login
失败,因为文件被挂载为只读。 不要更改,否则可能会出现问题。
以下是一个遵循 DOCKER_AUTH_CONFIG
文档的 /opt/.docker/config.json
示例:
{
"auths": {
"https://index.docker.io/v1/": {
"auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
}
}
}
Docker
更新卷挂载,包含该文件。
[[runners]]
...
executor = "docker"
[runners.docker]
...
privileged = true
volumes = ["/opt/.docker/config.json:/root/.docker/config.json:ro"]
Kubernetes
使用该文件的内容创建一个 ConfigMap。 您可以使用以下命令执行此操作:
kubectl create configmap docker-client-config --namespace gitlab-runner --from-file /opt/.docker/config.json
更新卷挂载,包含该文件。
[[runners]]
...
executor = "kubernetes"
[runners.kubernetes]
image = "alpine:3.12"
privileged = true
[[runners.kubernetes.volumes.config_map]]
name = "docker-client-config"
mount_path = "/root/.docker/config.json"
# If you are running GitLab Runner 13.5
# or lower you can remove this
sub_path = "config.json"
选项 3:使用 DOCKER_AUTH_CONFIG
如果您已经定义了 DOCKER_AUTH_CONFIG
,则可以使用该变量并将其保存在~/.docker/config.json
中。
有多种方法可以定义此身份验证:
- 在 runner 配置文件中的
pre_build_script
中。 - 在
before_script
中。 - 在
script
中。
以下示例显示了 before_script
。
相同的命令适用于您实施的任何解决方案。
image: docker:19.03.13
variables:
DOCKER_TLS_CERTDIR: "/certs"
services:
- docker:19.03.13-dind
build:
stage: build
before_script:
- mkdir -p $HOME/.docker
- echo $DOCKER_AUTH_CONFIG > $HOME/.docker/config.json
script:
- docker build -t my-docker-image .
- docker run my-docker-image /script/to/run/tests
使用 Docker 层缓存使 Docker-in-Docker 构建速度更快
使用 Docker-in-Docker 时,每次创建构建时,Docker 都会下载镜像的所有层。Docker 的最新版本(Docker 1.13 及更高版本)可以在 docker build
步骤中使用预先存在的镜像作为缓存。这大大加快了构建过程。
Docker 缓存工作原理
当运行 docker build
时,Dockerfile
中的每个命令都会生成一个层。这些层作为缓存保留,如果没有任何更改,可以重复使用。更改一层会导致重新创建所有后续层。
您可以使用 --cache-from
参数指定要用作 docker build
命令的缓存源的标签镜像。可以使用多个 --cache-from
参数将多个镜像指定为缓存源。任何与 --cache-from
参数一起使用的镜像必须首先被拉取(使用 docker pull
),然后才能用作缓存源。
Docker 缓存示例
这是一个 .gitlab-ci.yml
文件,展示了如何使用 Docker 缓存:
image: docker:20.10.16
services:
- docker:20.10.16-dind
variables:
# Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build:
stage: build
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --tag $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
在 build
阶段的 script
部分:
- 第一个命令尝试从镜像库中拉取镜像,以便它可以用作
docker build
命令的缓存。 - 如果可用,第二个命令通过使用拉取的镜像作为缓存来构建 Docker 镜像(参见
--cache-from $CI_REGISTRY_IMAGE:latest
参数),并标记它。 - 最后两个命令将标记的 Docker 镜像推送到容器镜像库,以便它们也可以用作后续构建的缓存。
使用 OverlayFS 驱动程序
默认情况下,当使用 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,则可以通过在 [[runners]]
部分中设置 DOCKER_DRIVER
环境变量来为每个项目启用驱动程序 config.toml
文件:
environment = ["DOCKER_DRIVER=overlay2"]
如果您正在运行多个 runner,则必须修改所有配置文件。
使用 GitLab Container Registry
构建 Docker 镜像后,您可以将其推送到内置的 GitLab Container Registry。
故障排查
docker: Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?
当您使用 Docker-in-Docker v19.03 或更高版本时,这是一个常见错误。
出现此问题是因为 Docker 自动在 TLS 上启动。
Docker no such host
错误
您可能会收到一条错误消息:docker: error during connect: Post https://docker:2376/v1.40/containers/create: dial tcp: lookup docker on x.x.x.x:53: no such host
。
当服务的镜像名称包括镜像库主机名时,可能会发生此问题。例如:
image: docker:20.10.16
services:
- registry.hub.docker.com/library/docker:20.10.16-dind
服务的主机名是派生自完整镜像名称。
然而,预期是较短的服务主机名 docker
。
要允许服务解析和访问,请为服务名称 docker
添加显式别名:
image: docker:20.10.16
services:
- name: registry.hub.docker.com/library/docker:20.10.16-dind
alias: docker