使用 Docker 构建 Docker 镜像
Tier: 基础版,专业版,旗舰版
Offering: JihuLab.com,私有化部署
您可以将 极狐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 执行器。例如:
shellsudo gitlab-runner register -n \ --url "https://jihulab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor shell \ --description "My Runner" -
在安装了 极狐GitLab Runner 的服务器上,安装 Docker Engine。 查看 支持的平台 列表。
-
将 gitlab-runner 用户添加到 docker 组:
shellsudo usermod -aG docker gitlab-runner -
验证 gitlab-runner 可以访问 Docker:
shellsudo -u gitlab-runner -H docker info -
在 极狐GitLab 中,将 docker info 添加到 .gitlab-ci.yml 以验证 Docker 是否正常工作:
yaml1default: 2 before_script: 3 - docker info 4build_image: 5 script: 6 - docker build -t my-docker-image . 7 - 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,这得到了 JihuLab.com 实例 Runner 的支持。
您应该始终固定镜像的某个具体版本,例如 docker:24.0.5。 如果您使用类似 docker:latest 的标签,您将无法控制所使用的版本。 当发布新版本时,这可能导致不兼容的问题。
使用 Docker 执行器配合 Docker-in-Docker
您可以使用 Docker 执行器在 Docker 容器中运行作业。
在 Docker 执行器中启用 TLS 的 Docker-in-Docker
Docker 守护进程支持基于 TLS 的连接。TLS 是 Docker 19.03.12 及更高版本的默认设置。
此任务启用了 --docker-privileged,这实际上会禁用容器的安全机制并使您的主机面临权限 提升的风险。此操作可能导致容器逃逸。更多信息,请参见 运行时权限和 Linux 能力。
要使用启用 TLS 的 Docker-in-Docker:
-
安装 极狐GitLab Runner。
-
在命令行中注册 极狐GitLab Runner。使用 docker 和 privileged 模式:
shell1sudo gitlab-runner register -n \ 2 --url "https://jihulab.com/" \ 3 --registration-token REGISTRATION_TOKEN \ 4 --executor docker \ 5 --description "My Docker Runner" \ 6 --tag-list "tls-docker-runner" \ 7 --docker-image "docker:24.0.5-cli" \ 8 --docker-privileged \ 9 --docker-volumes "/certs/client"- 此命令注册一个新的 Runner,使用 docker:24.0.5-cli 镜像(如果在作业级别未指定)。 要启动构建和服务容器,它使用了 privileged 模式。 如果要使用 Docker-in-Docker, 您的 Docker 容器必须始终使用 privileged = true。
- 此命令为服务和构建容器挂载 /certs/client,这是 Docker 客户端使用该目录中的证书所需的。 更多信息,请参阅 Docker 镜像文档。
上述命令会创建类似以下示例的 config.toml 条目:
toml1[[runners]] 2 url = "https://jihulab.com/" 3 token = TOKEN 4 executor = "docker" 5 [runners.docker] 6 tls_verify = false 7 image = "docker:24.0.5-cli" 8 privileged = true 9 disable_cache = false 10 volumes = ["/certs/client", "/cache"] 11 [runners.cache] 12 [runners.cache.s3] 13 [runners.cache.gcs] -
您现在可以在作业脚本中使用 docker。您应该包含 docker:24.0.5-dind 服务:
yaml1default: 2 image: docker:24.0.5-cli 3 services: 4 - docker:24.0.5-dind 5 before_script: 6 - docker info 7 8variables: 9 # 当您使用 dind 服务时,必须指示 Docker 与服务内部启动的守护进程通信。 10 # 该守护进程通过网络连接可用,而不是默认的 /var/run/docker.sock 套接字。 11 # Docker 19.03 通过设置 DOCKER_HOST 在 12 # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03/docker-entrypoint.sh#L23-L29 中自动执行此操作。 13 # 14 # 'docker' 主机名是服务容器的别名,如 15 # https://gitlab.cn/docs/ci/services/#accessing-the-services 中所述。 16 # 17 # 指定 Docker 在哪里创建证书。Docker 在启动时自动创建它们,并创建 18 # `/certs/client` 以在服务容器和作业容器之间共享, 19 # 这得益于 config.toml 中的卷挂载。 20 DOCKER_TLS_CERTDIR: "/certs" 21 22build: 23 stage: build 24 tags: 25 - tls-docker-runner 26 script: 27 - docker build -t my-docker-image . 28 - docker run my-docker-image /script/to/run/tests
在 Docker-in-Docker 和构建容器之间使用共享卷的 Unix socket
在 Docker 执行器中启用 TLS 的 Docker-in-Docker 方式中,volumes = ["/certs/client", "/cache"] 里定义的目录 在构建之间是 持久存在的。 如果多个使用 Docker 执行器 Runner 的 CI/CD 作业启用了 Docker-in-Docker 服务,那么每个作业 都会写入该目录路径。这种方法可能导致冲突。
为了解决这种冲突,可以在 Docker-in-Docker 服务和构建容器之间共享的卷上使用 Unix socket。 这种方法可以提高性能,并在服务端和客户端之间建立安全的连接。
以下是一个示例 config.toml,其中包含构建容器和服务容器之间共享的临时卷:
toml1[[runners]] 2 url = "https://jihulab.com/" 3 token = TOKEN 4 executor = "docker" 5 [runners.docker] 6 image = "docker:24.0.5-cli" 7 privileged = true 8 volumes = ["/runner/services/docker"] # 构建容器和服务容器之间共享的临时卷。
Docker-in-Docker 服务会创建一个 docker.sock。Docker 客户端通过 Docker Unix socket 卷连接到 docker.sock。
yaml1job: 2 variables: 3 # 此变量由 DinD 服务和 Docker 客户端共享。 4 # 对于服务,它将指示 DinD 在此处创建 `docker.sock`。 5 # 对于客户端,它告诉 Docker 客户端要连接到哪个 Docker Unix socket。 6 DOCKER_HOST: "unix:///runner/services/docker/docker.sock" 7 services: 8 - docker:24.0.5-dind 9 image: docker:24.0.5-cli 10 script: 11 - docker version
在 Docker 执行器中禁用 TLS 的 Docker-in-Docker
有时确实有正当理由需要禁用 TLS。 例如,您无法控制正在使用的 极狐GitLab Runner 配置。
-
在命令行中注册 极狐GitLab Runner。使用 docker 和 privileged 模式:
shell1sudo gitlab-runner register -n \ 2 --url "https://jihulab.com/" \ 3 --registration-token REGISTRATION_TOKEN \ 4 --executor docker \ 5 --description "My Docker Runner" \ 6 --tag-list "no-tls-docker-runner" \ 7 --docker-image "docker:24.0.5-cli" \ 8 --docker-privileged上述命令会创建类似以下示例的 config.toml 条目:
toml1[[runners]] 2 url = "https://jihulab.com/" 3 token = TOKEN 4 executor = "docker" 5 [runners.docker] 6 tls_verify = false 7 image = "docker:24.0.5-cli" 8 privileged = true 9 disable_cache = false 10 volumes = ["/cache"] 11 [runners.cache] 12 [runners.cache.s3] 13 [runners.cache.gcs] -
在作业脚本中包含 docker:24.0.5-dind 服务:
yaml1default: 2 image: docker:24.0.5-cli 3 services: 4 - docker:24.0.5-dind 5 before_script: 6 - docker info 7 8variables: 9 # 当使用 dind 服务时,必须指示 Docker 与服务内部启动的守护进程通信。 10 # 该守护进程通过网络连接可用,而不是默认的 /var/run/docker.sock 套接字。 11 # 12 # 'docker' 主机名是服务容器的别名,如 13 # https://gitlab.cn/docs/ci/services/#accessing-the-services 中所述。 14 # 15 DOCKER_HOST: tcp://docker:2375 16 # 17 # 这指示 Docker 不要通过 TLS 启动。 18 DOCKER_TLS_CERTDIR: "" 19 20build: 21 stage: build 22 tags: 23 - no-tls-docker-runner 24 script: 25 - docker build -t my-docker-image . 26 - docker run my-docker-image /script/to/run/tests
在 Docker 执行器中启用代理的 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 文件 以指定卷挂载。
yaml1runners: 2 tags: "tls-dind-kubernetes-runner" 3 config: | 4 [[runners]] 5 [runners.kubernetes] 6 image = "ubuntu:20.04" 7 privileged = true 8 [[runners.kubernetes.volumes.empty_dir]] 9 name = "docker-certs" 10 mount_path = "/certs/client" 11 medium = "Memory" -
在作业中包含 docker:24.0.5-dind 服务:
yaml1default: 2 image: docker:24.0.5-cli 3 services: 4 - name: docker:24.0.5-dind 5 variables: 6 HEALTHCHECK_TCP_PORT: "2376" 7 before_script: 8 - docker info 9 10variables: 11 # 当使用 dind 服务时,必须指示 Docker 与服务内部启动的守护进程通信。 12 # 该守护进程通过网络连接可用,而不是默认的 /var/run/docker.sock 套接字。 13 DOCKER_HOST: tcp://docker:2376 14 # 15 # 'docker' 主机名是服务容器的别名,如 16 # https://gitlab.cn/docs/ci/services/#accessing-the-services 中所述。 17 # 18 # 指定 Docker 在哪里创建证书。Docker 在启动时自动创建它们,并创建 19 # `/certs/client` 以在服务容器和作业容器之间共享, 20 # 这得益于 config.toml 中的卷挂载。 21 DOCKER_TLS_CERTDIR: "/certs" 22 # 这些通常由入口点指定,但是 23 # Kubernetes 执行器不会运行入口点 24 DOCKER_TLS_VERIFY: 1 25 DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" 26 27build: 28 stage: build 29 tags: 30 - tls-dind-kubernetes-runner 31 script: 32 - docker build -t my-docker-image . 33 - 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]] 部分。
- 将端口从 2376 更改为 2375,使用 DOCKER_HOST: tcp://docker:2375。
- 指示 Docker 在禁用 TLS 的情况下启动,使用 DOCKER_TLS_CERTDIR: ""。
例如:
-
使用 Helm Chart,更新 values.yml 文件:
yaml1runners: 2 tags: "no-tls-dind-kubernetes-runner" 3 config: | 4 [[runners]] 5 [runners.kubernetes] 6 image = "ubuntu:20.04" 7 privileged = true -
您现在可以在作业脚本中使用 docker。您应该包含 docker:24.0.5-dind 服务:
yaml1default: 2 image: docker:24.0.5-cli 3 services: 4 - name: docker:24.0.5-dind 5 variables: 6 HEALTHCHECK_TCP_PORT: "2375" 7 before_script: 8 - docker info 9 10variables: 11 # 当使用 dind 服务时,必须指示 Docker 与服务内部启动的守护进程通信。 12 # 该守护进程通过网络连接可用,而不是默认的 /var/run/docker.sock 套接字。 13 DOCKER_HOST: tcp://docker:2375 14 # 15 # 'docker' 主机名是服务容器的别名,如 16 # https://gitlab.cn/docs/ci/services/#accessing-the-services 中所述。 17 # 18 # 这指示 Docker 不要通过 TLS 启动。 19 DOCKER_TLS_CERTDIR: "" 20build: 21 stage: build 22 tags: 23 - no-tls-dind-kubernetes-runner 24 script: 25 - docker build -t my-docker-image . 26 - 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 下创建一个子目录 并将其用作挂载点。
yamlvariables: MOUNT_POINT: /builds/$CI_PROJECT_PATH/mnt script: - mkdir -p "$MOUNT_POINT" - docker run -v "$MOUNT_POINT:/mnt" my-docker-image
使用 Docker socket 绑定
要在 CI/CD 作业中使用 Docker 命令,您可以将 /var/run/docker.sock 绑定挂载到 构建容器中。然后,Docker 即可在镜像上下文中使用。
如果您绑定了 Docker socket,您就不能将 docker:24.0.5-dind 作为服务使用。卷绑定也会影响服务, 使它们不兼容。
使用 Docker 执行器配合 Docker socket 绑定
要使用 Docker 执行器挂载 Docker socket,请将 "/var/run/docker.sock:/var/run/docker.sock" 添加到 [runners.docker] 部分的 Volumes 中。
-
要在注册 Runner 时挂载 /var/run/docker.sock,请包含以下选项:
shell1sudo gitlab-runner register \ 2 --non-interactive \ 3 --url "https://jihulab.com/" \ 4 --registration-token REGISTRATION_TOKEN \ 5 --executor "docker" \ 6 --description "docker-runner" \ 7 --tag-list "socket-binding-docker-runner" \ 8 --docker-image "docker:24.0.5-cli" \ 9 --docker-volumes "/var/run/docker.sock:/var/run/docker.sock"上述命令会创建类似以下示例的 config.toml 条目:
toml1[[runners]] 2 url = "https://jihulab.com/" 3 token = RUNNER_TOKEN 4 executor = "docker" 5 [runners.docker] 6 tls_verify = false 7 image = "docker:24.0.5-cli" 8 privileged = false 9 disable_cache = false 10 volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] 11 [runners.cache] 12 Insecure = false -
在作业脚本中使用 Docker:
yaml1default: 2 image: docker:24.0.5-cli 3 before_script: 4 - docker info 5 6build: 7 stage: build 8 tags: 9 - socket-binding-docker-runner 10 script: 11 - docker build -t my-docker-image . 12 - docker run my-docker-image /script/to/run/tests
使用 Kubernetes 执行器配合 Docker socket 绑定
要使用 Kubernetes 执行器挂载 Docker socket,请将 "/var/run/docker.sock" 添加到 [[runners.kubernetes.volumes.host_path]] 部分的 Volumes 中。
-
要指定卷挂载,请使用 Helm Chart 更新 values.yml 文件。
yaml1runners: 2 tags: "socket-binding-kubernetes-runner" 3 config: | 4 [[runners]] 5 [runners.kubernetes] 6 image = "ubuntu:20.04" 7 privileged = false 8 [runners.kubernetes] 9 [[runners.kubernetes.volumes.host_path]] 10 host_path = '/var/run/docker.sock' 11 mount_path = '/var/run/docker.sock' 12 name = 'docker-sock' 13 read_only = true -
在作业脚本中使用 Docker:
yaml1default: 2 image: docker:24.0.5-cli 3 before_script: 4 - docker info 5build: 6 stage: build 7 tags: 8 - socket-binding-kubernetes-runner 9 script: 10 - docker build -t my-docker-image . 11 - docker run my-docker-image /script/to/run/tests
Docker socket 绑定的已知问题
使用 Docker socket 绑定时,您避免了在特权模式下运行 Docker。但是, 这种方法的代价是:
-
当您共享 Docker 守护进程时,您实际上禁用了 容器的安全机制,并使您的主机面临权限提升的风险。 这可能导致容器逃逸。例如,如果您在一个项目中运行 docker rm -f $(docker ps -a -q),它会删除 极狐GitLab Runner 的容器。
-
并发作业可能无法正常工作。如果您的测试 创建的容器具有特定名称,它们可能会相互冲突。
-
由 Docker 命令创建的任何容器都是 Runner 的兄弟节点,而不是 子节点。这可能会给您的工作流程带来问题。
-
将源仓库中的文件和目录共享到容器中可能无法 按预期工作。卷挂载是在主机 机器的上下文中完成的,而不是在构建容器中。例如:
shelldocker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
您无需像使用 Docker-in-Docker 执行器时那样包含 docker:24.0.5-dind 服务:
yaml1default: 2 image: docker:24.0.5-cli 3 before_script: 4 - docker info 5 6build: 7 stage: build 8 script: 9 - docker build -t my-docker-image . 10 - docker run my-docker-image /script/to/run/tests
对于复杂的 Docker-in-Docker 设置,例如 使用 CodeClimate 进行代码质量扫描,您必须匹配主机和容器路径才能正确执行。更多详细信息,请参见 使用私有化 Runner 进行基于 CodeClimate 的扫描。
使用 Docker pipe 绑定
Windows 容器运行针对 Windows 服务端内核和用户态编译的 Windows 可执行文件( 可以是 windowsservercore 或 nanoserver)。要构建和运行 Windows 容器,需要一个支持容器的 Windows 系统。 更多信息,请参见 Windows 容器下载。
因为 Windows 容器 不支持 Docker-in-Docker 方式, 所以您无法在容器内运行一个嵌套的 Docker 引擎。 要在 Windows 容器内构建或管理 Docker 镜像,请使用 Docker pipe 绑定(也称为 Docker-outside-of-Docker 或 DooD)。
Docker pipe 绑定具有安全隐患。当您绑定挂载 \\\\.\\pipe\\docker_engine 时, 容器对主机的 Docker 守护进程拥有完全的行政管理权限。容器内的进程 可以启动或停止其他容器,管理镜像,并可能获得主机系统上的提升权限。
要使用 Docker pipe 绑定,您必须在主机 Windows Server 操作系统上安装并运行 Docker 引擎。 更多信息,请参见 在 Windows Server 上安装 Docker Community Edition (CE)。
要在您的基于 Windows 容器的 CI/CD 作业中使用 Docker 命令,您可以将 \\\\.\\pipe\\docker_engine 绑定挂载到启动的执行器容器中。然后,Docker 即可在镜像上下文中使用。
Windows 中的 Docker pipe 绑定 类似于 Linux 中的 Docker socket 绑定,并且具有类似的 已知问题, 如 Docker socket 绑定的已知问题 中所述。
使用 Docker pipe 绑定的必要前提是在主机 Windows Server 操作系统上安装并运行 Docker 引擎。 请参见:在 Windows Server 上安装 Docker Community Edition (CE)
使用 Docker 执行器配合 Docker pipe 绑定
您可以使用 Docker 执行器 在基于 Windows 的容器中运行作业。
要使用 Docker 执行器挂载 Docker pipe,请将 "\\\\.\\pipe\\docker_engine:\\\\.\\pipe\\docker_engine" 添加到 [runners.docker] 部分的 Volumes 中。