gitlab-runner 中的 Docker-in-Docker
笔者个人理解:gitlab-runner 安装后就是一个监听状态的 runner,而通过 gitlab-runner register
注册的“实例”其实只是预定义的配置节,当消息抵达后,gitlab-runner 根据消息内容选择相应的配置节启动执行线程。为了方便阐述和理解,本文也将每个配置节/执行线程
称为 runner 实例
。
runner executor
runner 实例的执行环境,一般用的较多的是 shell
和 docker
,这两者的区别无需赘述。
让人困惑的是其它一些 executor
:比如 Docker-SSH
和 Docker-SSH+machine
,还好从 GitLab Runner 10.0 开始, 这两者就被废弃了,并且将在后续某个版本中移除;还有 Docker machine
,这个概念原本是 Docker 提出的,但是后面同样被 Docker 弃用了,只是 GitLab 为了向前兼容保留了下来,也可以不用细究。
对于 docker executor 来说,runner 执行 job 的流程如下(摘自官网):
- The runner starts a Docker container using the defined entrypoint. The default from Dockerfile that may be overridden in the .gitlab-ci.yml file.
- The runner attaches itself to a running container.
- The runner prepares a script (the combination of before_script, script, and after_script).
- The runner sends the script to the container’s shell stdin and receives the output.
显然,第 1 步要启动容器,如果 runner 本身是以 docker 容器方式安装运行的,那么就涉及到 Docker-in-Docker
的概念了。
Docker-in-Docker
有些时候,我们需要在容器内部执行 docker 指令,一般有两种方式:
- 挂载宿主机 docker 环境。启动容器时挂载
/var/run/docker.sock
,这样在容器内执行 docker 指令其实就等同于在容器外(宿主机中)执行 docker 指令。比如docker build
构建一个镜像,该镜像并不存在于容器内部,而是在宿主机中。所以该方法并不是严格意义上的docker in docker
。 - 容器内部有自己的一套 docker 环境。使用
docker:dind
镜像,可以直接使用它作为主容器,或是作为其它容器的服务容器(其它容器与之通信)。它在docker
镜像(该镜像只包含客户端指令集)基础上安装了Docker Daemon
,因此可作为独立的 docker 环境使用,是真正意义上的docker in docker
。然而,除非你真正需要在容器内嵌套容器,或者某些场景下无法使用第 1 种方式,否则还是建议避免使用该方式。
这两种方式需要执行指令的容器处于 privileged mode
,有一定的安全风险(privileged=true
将使得容器内 root 拥有宿主 root 的权限,否则只是宿主机上的普通用户),因此市面上又出现了一种 Using Nestybox sysbox Docker runtime
的方法,感兴趣的朋友请查阅参考资料,此处按过不表。
in gitlab-runner
如前所述,以 docker 方式安装 runner,且 executor 采用 docker,那么就要 Docker-in-Docker
。因为 runner 只是启动新容器,不要求启动的容器在 runner 容器内部,我们可以采用第 1 种方式,如下:
docker run -d --name gitlab-runner --restart always \
-v /srv/gitlab-runner/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \ # 挂载 socket
gitlab/gitlab-runner:latest
另外,如果 docker executor 在 CI/CD job 中涉及到 docker 指令,那么也要 Docker-in-Docker
。关键步骤如下:
- 注册 runner 实例,并配置其启动的容器为 privileged mode(注意配置的是每次
job
执行时启动的容器,而非 runner 所在的容器,且 runner 并不一定是 docker 形式)。
sudo gitlab-runner register -n \
--executor docker \
--docker-image "docker:20.10.16" \
--docker-privileged \ # privileged mode
--other arguments
- 接下来,可以选择任一种方式实现 Docker-in-Docker:
- 在
config.toml
中增加卷映射volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
。 - 或者在
.gitlab-ci.yml
中指定docker:dind
如:
services: - docker:20.10.16-dind
- 在
这里再强调下:如果需要共享宿主机 docker 配置(如 /etc/docker/daemon.json),则务必采用第一种方式。
TLS 配置
如果在第 2 步采用 docker:dind
方式,那么由于涉及到容器间通信,需要选择是否启用 TLS。
若是,则在注册 runner 实例时,增加一个参数 --docker-volumes "/certs/client"
, 也可手动编辑 config.toml
,增加卷映射 volumes = ["/certs/client", "/cache"]
;然后在 .gitlab-ci.yml
中设置变量 DOCKER_TLS_CERTDIR: "/certs"
。
若否,则在 .gitlab-ci.yml
中设置变量 DOCKER_TLS_CERTDIR: ""
和 DOCKER_HOST: tcp://docker:2375
。
TLS 若未正确配置,会报 Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?
错误。