GitLab の CI/CD の中で docker
コマンドを実行するには、いくつか方法があるが、今回やるのは Docker in Docker(Dindとよく呼ばれている)の構成である。
Docker in Docker は、.gitlab-ci.yml
に ↓ こう書くだけで実現できるが、Docker in Docker の仕組みを知らなければ何がどうなって、どう実行されているかチンプンカンプンである。
$ vim .gitlab-ci.yml
image: docker:18.09.7
services:
- name: docker:18.09.7-dind
alias: docker
default:
script:
- echo "hello"
- docker run -itd --name tmp alpine:latest
- docker run -itd --name tmp2 alpine:latest
- docker container ls
only:
- master
ということで、これを頑張って解明していったため、そのプロセスを記事にしている。
Docker in Docker を作った環境
GitLab も GitLab Runner も EC2 の上に Docker コンテナとして立てている。
バージョンはこんな感じ。
- GitLab Docker Image : gitlab/gitlab-ce:12.7.8-ce.0
- GitLab Runner Docker Image : gitlab/gitlab-runner:ubuntu-v12.9.0
自明だが、EC2 上でls
したらこんな感じ。
$ sudo docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7cf38ee9ab29 gitlab/gitlab-runner:ubuntu-v12.9.0 "/usr/bin/dumb-init …" 9 minutes ago Up 9 minutes gitlab-runner
2bf98a756bf8 gitlab/gitlab-ce:12.7.8-ce.0 "/assets/wrapper" 3 days ago Up 33 minutes (healthy) 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:4567->4567/tcp, 0.0.0.0:10022->22/tcp gitlab
CI/CD が走り出すと、GitLab Runner Container が CI/CD 用のコンテナを作成していく。また、GitLab Runner Container はホスト(EC2)の/var/run/docker.sock
をマウントしているため、CI/CD 用のコンテナは、GitLab Runner Container や GitLab Repository と同じレイヤーに作成される。
GitLab Runner の立ち上げコマンドと設定はこんな感じ。特筆すべき点としては、privileged = true
である(privileged
をtrue
にすることでGitLab Runner が立ち上げる Docker コンテナは、特権モードになる。これをしておかないと Docker コンテナが Docker プロセスを作ることができない。)。
$ sudo 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 \
gitlab/gitlab-runner:ubuntu-v12.9.0
$ sudo docker exec -it gitlab-runner vim /etc/gitlab-runner/config.toml
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "7cf38ee9ab29"
url = "http://ec2-13-231-128-*.ap-northeast-1.compute.amazonaws.com/"
token = "VZExYHrr1ssjyoHUBEsM"
executor = "docker"
[runners.custom_build_dir]
[runners.cache]
[runners.cache.s3]
[runners.cache.gcs]
[runners.docker]
tls_verify = false
image = "docker:18.09.7"
privileged = true ← 注目!!
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0
このGitlab Runner の設定で、かつ、冒頭に書いた.gitlab-ci.yml
の CI/CD を走らせると CI/CD の中でdocker
コマンドが実行できる。
GitLab のWeb画面から CI/CD の実行ログを見るとこんな感じ。長いので CI/CD のscript
の実行されているところをだけを抜粋している。CI/CD の中でdocker
コマンドが実行され、コンテナが2つ作成されていることがわかる。大事なのは docker コマンドが実行できてるということ。
$ echo "hello"
hello
$ docker run -itd --name tmp alpine:latest
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
aad63a933944: Pulling fs layer
aad63a933944: Verifying Checksum
aad63a933944: Download complete
aad63a933944: Pull complete
Digest: sha256:b276d875eeed9c7d3f1cfa7edb06b22ed22b14219a7d67c52c56612330348239
Status: Downloaded newer image for alpine:latest
c8b4df07f0954ded608c061ee8efcc36028440357271dda05e77c4e11c1f5a81
$ docker run -itd --name tmp2 alpine:latest
ae2559d3af2512490c494ba85ca4d64dcd7f873c36b0dc26b8ff8d76d9d5c44d
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae2559d3af25 alpine:latest "/bin/sh" Less than a second ago Up Less than a second tmp2
c8b4df07f095 alpine:latest "/bin/sh" 1 second ago Up Less than a second tmp
CI/CD が走ったときのコンテナの構成図
関係ないGitLab Repository は省いている(本当はGitLab Runner Container の横にある)。
- GitLab Runner Container : GitLab Runner
- GitLab CI/CD Container(Docker Client) : 真ん中の Docker コンテナ。GitLab Runner によって立ち上げられる CI/CD のコンテナ
- GitLab Dind Container(Docker Server) : 右の Docker コンテナ。同じくGitLab Runner によって立ち上げられるコンテナだが、CI/CD のコンテナではなく、
.gitlab-ci.yml
に書いてある通りservices
としてのコンテナ
バランスの問題で GitLab CI/CD Container(Docker Client) の上に Docker コンテナを3つ書いたが、さっきの.gitlab-ci.yml
では、docker run
を2回しているため、2つが正しい。
GitLab CI/CD Container(Docker Client) は、Docker Client として動作するため、Docker Image は、docker:18.09.7
を使っている。docker:18.09.7
のイメージは、Docker Daemon 以外の Docker の機能を持っている。GitLab Dind Container(Docker Server) は、Docker Server として動作するため、Docker Image は、docker:18.09.7-dind
を使っている。docker:18.09.7-dind
は、Docker Daemon を持っている。
冒頭の.gitlab-ci.yml
に書いていたscript
だと、docker run
コマンドを要求しているのは、GitLab CI/CD Container(Docker Client) であり、実際にdocker run
を実行しているのは Docker Daemon を持つGitLab Dind Container(Docker Server)
になる。
素手で Docker in Docker を作る
GitLab には、.gitlab-ci.yml
に数行書けば、Docker in Docker の構成をとることができるが、意外と素手で作っていくと面倒である。理解を深めるためにもやってみる。GitLab Runner が CI/CD の中でやってくれていることを素手でやることになるため、作る部分としてはここ ↓。
Docker Client は、Docker Server に対して命令を出すが、Docker Server のエンドポイントはtcp://docker:2376
にある。そのため、Docker Client は名前解決できないとエンドポイントに到達できないため、Docker Client と Docker Server は同一ネットワーク内にいる必要がある。ということでネットワークを作成。
$ sudo docker network create some-network
$ sudo docker network inspect some-network
Docker Client と Docker Server 間の通信はSSLが推奨されており、SSLの場合は、tcp://docker:2376
が使用される。SSLじゃない場合は、tcp://docker:2375
となる。たしか、冒頭で設定したGitLab Runner はSSLじゃないが、せっかく素手でやるので、SSLで行こう。
Docker Server のコンテナを立ち上げると、立ち上げコマンドの中にSSLをするための証明書や公開鍵を自動で生成してくれる。docker:18.09.7-dind
のソースコードを見ればわかる(github)。
Docker Server が作成した公開鍵やらは、Docker Client 側に渡してあげる必要があるため、ホストにマウントしてあげる(ここで言うホストはEC2のこと)。後で Docker Client も同じところをマウントして、公開鍵を渡す。
$ mkdir certs
/home/ec2-user/certs
$ mkdir ca
/home/ec2-user/certs/ca
$ mkdir client
/home/ec2-user/certs/client
Docker Server を立ち上げる。
$ sudo docker run --privileged --name some-docker -d \
--network some-network --network-alias docker \
-e DOCKER_TLS_CERTDIR=/certs \
-v /home/ec2-user/certs/ca:/certs/ca \
-v /home/ec2-user/certs/client:/certs/client \
docker:18.09.7-dind
Docker Client にも立ち上がってもらい、docker
のversion
を確認する。docker version
コマンドの実行と同義。
$ sudo docker run --rm --network some-network \
-e DOCKER_TLS_CERTDIR=/certs \
-v /home/ec2-user/certs/client:/certs/client:ro \
docker:18.09.7 version
Client: Docker Engine - Community
Version: 19.03.8
API version: 1.40
Go version: go1.12.17
Git commit: afacb8b7f0
Built: Wed Mar 11 01:22:56 2020
OS/Arch: linux/amd64
Experimental: false
Server: Docker Engine - Community
Engine:
Version: 19.03.8
API version: 1.40 (minimum version 1.12)
Go version: go1.12.17
Git commit: afacb8b7f0
Built: Wed Mar 11 01:30:32 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
実際に実行したときは Docker Image のバージョンを指定せずに、lastet
で言ったため、表示されているバージョンと実行コマンドのバージョンに差異があることは許してほしい。
Server
のバージョンが出てきているということは、問題なく Docker Server 側の Docker Daemon に命令を出すことができている、ということになる。もし、 Docker Daemon に到達できなければ、いつものこいつが出てくるはず。
Server:
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
これで Client からdocker
コマンドの実行が可能になったため、Client の中で好きに Docker コンテナを作成し、Docker in Docker を楽しむことができる。
$ sudo docker run -it --rm --network some-network \
-e DOCKER_TLS_CERTDIR=/certs \
-v /home/ec2-user/certs/client:/certs/client:ro \
docker:latest sh
$ sudo docker run -it --rm --name tmp-docker alpine:latest sh
$ sudo docker container ls
おまけ
Docker Server として使っているdocker:18.09.7-dind
は、Docker Daemon を持っているため、わざわざ Docker Client と Docker Server にコンテナを分けずに、docker:18.09.7-dind
のイメージを使うだけで CI/CD の中でdocker
コマンドを使うことができる,
と StackOverFlowで回答している人がいるし、実際にできた。
image: docker:dind
test:
script:
- dockerd &
- sleep 5
- docker run hello-world
https://stackoverflow.com/questions/47280922/role-of-docker-in-docker-dind-service-in-gitlab-ci
https://gitlab.com/saraedum/sandbox/blob/dind/.gitlab-ci.yml
まとめ
Docker Server は、Docker Daemon とか Dockerd と書いたほうが適切かもしれない。ので、各自脳内変換して読み進めてほしい。
Docker は難しい。以上終わり!
参考