gitlab-ci and docker

最近在學 CI/CD 相關的課題,研究了一下 Jenkins 但是覺得太過肥大。GitLab 本體雖然也是肥肥的 ruby,不過 gitlab-ci 預設的 gitlab-multirunner 到是 go 寫的,相當輕量;能與 GitLab 深度整合這點也還不錯。

gitlab-multirunner 支援 docker。由於我的 GitLab 本體和 ci 都是裝在 docker 裡面的。為了要在 build job 中做 docker build,所以一開始考慮用 docker-in-docker (dind)。不過 dind 有效能和 cgroup namespace 等等問題,所以後來改成將外部 docker socket 掛入 container 入的方式。這衍生了另一個問題:gitlab-multirunner 會針對每一個 job 下載必要的 image、開啟複數個 container,讓外部的 docker 看起來十分複雜,造成管理的困難。

這篇記錄了我的另一個解法:啟動另一個獨立的 docker daemon 專供 build/test/deploy job 使用。

註:以下適用 docker 1.10 以後的版本,舊版「理論上可以」,但我沒有測試,不保證能通用

準備工作

由於 Docker 本身的設計並不適合讓兩個 docker daemon 共用資源,所以要先把網路、儲存設備先準備好。

網路

Docker 預設使用 docker0 這個橋接介面,所以我們針對第二個 docker daemon 建立一個新的橋接器,再用 -b 指定給它:

brctl addbr docker1
ip addr add 10.9.0.1/16 dev docker1
ip link set dev docker1 up

或是用 systemd

/etc/systemd/network/docker1.netdev:

[NetDev]
Name=docker1
Kind=bridge

/etc/systemd/network/docker1.network:

[Match]
Name=docker1

[Network]
Address=10.9.0.1/16

Reload

systemctl daemon-reload
systemctl restart systemd-networkd

ip 要記得調開,不要跟已知的網段 (包括第一個 docker) 重覆。

儲存設備

mkdir -p /docker2/graph /docker2/tmp

graph 目錄是存放 image/container 用的,tmp 則是暫存目錄;pid/socket 存放在 /docker2

Wrapper

#!/bin/bash

DOCKER_HOST=unix:///docker2/docker.sock
DOCKER_TMPDIR=/docker2/tmp

export DOCKER_HOST DOCKER_TMPDIR

exec docker "$@"

另存成 /usr/local/bin/docker2

註:不要忘了加執行權限

init 設定

為了簡便,第二個 docker 會使用 loop devicemapper。

systemd

先把 /lib/systemd/system/docker.socket/lib/systemd/system/docker.service 複製一份到 /etc/systemd/system,然後把複本檔名裡的 docker 改成 docker2

docker2.socket 要改的地方有兩個: (說明部份也可以視需要更改)

  • PartOf: 把 docker 改成 docker2
  • ListenStream: 改成 /docket2/docker.sock

docker2.service 要改的有三個: (說明部份也可以視需要更改)

  • After: 把 docker 改成 docker2
  • Requires: 把 docker 改成 docker2
  • ExecStart: 改成 /usr/bin/docker daemon -H fd:// -g /docker2/graph -s devicemapper -p /docker2/docker.pid -b docker1。視需要加上其他參數,比如 --dns--insecure-registry 等等

啟動試試

systemctl daemon-reload
systemctl start docker2
docker2 info

正常的話就可以設為預設啟動了

systemctl enable docker2

建立 gitlab-ci runner

docker run -d --name runner1 --hostname runner1 -v /docker2/docker.sock:/var/run/docker.sock --privileged --restart always gitlab/gitlab-runner
docker exec -it runner1 gitlab-runner register -n -r "gitlab-runner-register-token" --url "http://your.gitlab.url/ci" --executor docker --docker-image gitlab/dind --docker-privileged --name runner1 --docker-volumes /var/run/docker.sock:/var/run/docker.sock

說明一下 gitlab-runner register 的參數

  • -r: register token
  • --url: GitLab 本體提供的 API 位址,如果本體跟 runner 跑在同一台機器的 docker 裡的話,建立 runner container 的時候可能要用 --link 連起來
  • --executor: 指定 job executor
  • --docker-image: 預設用來執行 job 的 image
  • --docker-privileged: 不加這個就不能存取外部的 docker 了
  • --docker-volumes: 把 runner 裡的 docker socket 掛進 job container 裡。由於在建立 runner container 的時候我們把第二個 docker 的 socket 指定給 runner 了,所以 job container 存取到的會是第二個 docker

完成

指定一個 build job 給新的 runner,應該就可以用 docker2 ps -adocker2 images 看到 job container 及相關資訊。

Comments