gitlab-runner 中的 Docker-in-Docker

萊布尼茨發表於2023-05-17

筆者個人理解:gitlab-runner 安裝後就是一個監聽狀態的 runner,而透過 gitlab-runner register 註冊的“例項”其實只是預定義的配置節,當訊息抵達後,gitlab-runner 根據訊息內容選擇相應的配置節啟動執行執行緒。為了方便闡述和理解,本文也將每個配置節/執行執行緒稱為 runner 例項

runner executor

runner 例項的執行環境,一般用的較多的是 shelldocker,這兩者的區別無需贅述。

讓人困惑的是其它一些 executor:比如 Docker-SSHDocker-SSH+machine(從 GitLab Runner 10.0 開始, Docker-SSH 和 Docker-SSH+machine executors 被廢棄了,並且將在後續某個版本中移除);還有 Docker machine,這個概念原本是 Docker 提出的,但是後面被 Docker 棄用了,GitLab 為了向前相容保留了下來,也可以不用細究。

對於 docker executor 來說,runner 執行 job 的流程如下(摘自官網):

  1. The runner starts a Docker container using the defined entrypoint. The default from Dockerfile that may be overridden in the .gitlab-ci.yml file.
  2. The runner attaches itself to a running container.
  3. The runner prepares a script (the combination of before_script, script, and after_script).
  4. 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 指令,一般有兩種方式:

  1. 掛載宿主機 docker 環境。啟動容器時掛載 /var/run/docker.sock,這樣在容器內執行 docker 指令其實就等同於在容器外(宿主機中)執行 docker 指令。比如 docker build 構建一個映象,該映象並不存在於容器內部,而是在宿主機中。所以該方法並不是嚴格意義上的 docker in docker
  2. 容器內部有自己的一套 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。關鍵步驟如下:

  1. 註冊 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
  1. 接下來,可以選擇任一種方式實現 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
    

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? 錯誤。

參考資料

How To Run Docker in Docker Container 3 Easy Methods