LoginSignup
15
16

More than 3 years have passed since last update.

GitLab の CI/CD で Docker in Docker

Last updated at Posted at 2020-04-17

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 コンテナとして立てている。
Untitled Diagram-Copy of Page-2.png

バージョンはこんな感じ。

  • 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である(privilegedtrueにすることで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 が走ったときのコンテナの構成図

Untitled Diagram-Page-2 (1).png

関係ない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 の中でやってくれていることを素手でやることになるため、作る部分としてはここ ↓。

dind.png

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 にも立ち上がってもらい、dockerversionを確認する。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 は難しい。以上終わり!

参考

15
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
16