DockerコンテナのGitlabでCI/CD



# 目的
現在CentOSの仮想マシンにDockerをインストールし、その上でGitLabを運用している。そこにCI/CDの機能を追加し、作成しているウェブアプリに対してCommitがあったら自動でビルド、起動したい。できれば全てコンテナでやりたい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/159980/0a8b55bb-bb92-72c0-49bb-8613225fcbe6.png)

何もわからずやみくもに始めた結果、かなり手こずってしまったので、整理した結果を残します。
ウェブ上には同じテーマの話がたくさんありますが、gitlab, runner, 対象アプリの3つが同一のDockerホスト上という条件を満たすものがそんなにないように思われました。そもそもその前提を意識して調べないとはまります(はまりました)。

# 流れ
そもそも、上記の絵を実現できるのか?という話がありますが、CI/CDの機能でコンテナを起動させることに関しては、以下のリンクに説明があります。
[Building Docker images with GitLab CI/CD](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html)
CI/CDでコンテナをデプロイするには3つの手法があります。

1. shell executorを使う
2. docker-in-docker executorを使う
3. Dockerホストのsocket bindingを使う

この中で、目的の構成になるのは3だけです。1,2はDockerの層がもう1つ増えます(Runnerの上にアプリが乗る)。厳密には3も層は増えるのですが、アプリのコンテナは大元のホストマシン上に乗ります。
基本的に3の指示に従うのですが、ここの指示はrunnerがコンテナである前提ではないので、プラスアルファが必要です。
全体の流れとしては、以下のようになります。

1. Pipelineの定義
2. Runnerの作成
3. Pipelineの実行


# 環境
Host: CentOS7.4.1708
Docker Engine: 17.12.1-ce
GitLab: Community Edition 10.5.3

# Pipelineの定義
PipelineとはJobの集まりで、Jobが個々のタスクです。今回はJob1つでコンテナのビルドと起動を行います。
Gitlabの対象とするレポジトリのルートフォルダに、以下のようなファイルを作成することでPipelineを定義できます。このファイルがある状態で、gitにcommit/pushすることでPipelineが起動して、そこで定義されたJobが実行されます。

```yaml:.gitlab-ci.yml
before_script:
   - docker info

build:
  script:
    - docker build -t citest .
    - docker stop citest
    - docker rm citest
    - docker run -d -p 6003:6000 --name citest
      -v /srv/citest/autosupport/:/DDAuto/autosupport/
      -v /srv/citest/config/:/DDAuto/config/
      -v /srv/citest/proc/:/DDAuto/proc/
      -v /srv/citest/result/:/DDAuto/result/
      -v /srv/citest/upload/:/DDAuto/upload/  -itd --privileged citest
```

内容としては、citestというイメージをビルドして、起動するだけです。
これを今の時点でPushすると、Runnerがないため、PipelineはGitlab上に登録されますが、Pendingのままとなります。
前提として、ビルドに使用するDockerfileも同様にルートフォルダに配置しています。
内容にはあまり関係ないですが、一応以下のようになっています。

```txt:Dockerfile
FROM ddauto_base
ARG project_dir=/DDAuto/
RUN mkdir $project_dir
COPY ./ $project_dir
WORKDIR $project_dir
RUN /usr/bin/pip3.6 install -r requirements.txt
ENV LC_ALL=en_US.UTF-8
CMD ["python", "ui.py"]
```

# Runnerの作成
これも基本は公式通りです。
[GitLab Runner](https://docs.gitlab.com/runner/)
ポイントは
- docker run時にもregister時にもsocket bindを行うこと(-v /var/run/docker.sock:/var/run/docker.sock)。runはホストマシン上で、registerはコンテナ内で実行しています。
- runnerにdockerをあらかじめインストールすること
- gitlab-runnerユーザをdockerグループに入れること
- `--docker-privileged`をつけること(もしかするといらないのかも)
- clone_urlを指定すること

```sh
[root@ddauto server_base]# docker run -d --name gitlab-runner-dd --restart always \
--privileged \
-v /srv/gitlab-runner-dd/config:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
[root@ddauto server_base]# docker exec -it gitlab-runner-dd bash
root@e3a06d6dd227:/#
root@e3a06d6dd227:/# curl -sSL https://get.docker.com/ | sh
(中略)
root@e3a06d6dd227:/# usermod -aG docker gitlab-runner
root@e3a06d6dd227:/# gitlab-runner register -n \
   --url http://172.17.0.2/ \
   --registration-token SxMGjtm28uUQF9fhxBYJ \
   --executor docker \
   --docker-privileged \
   --description "My Docker Runner" \
   --docker-image "docker:stable" \
   --docker-volumes /var/run/docker.sock:/var/run/docker.sock
```

runの時だけソケットバインドすればよいかと思って最初飛ばしてしまったのですが、そうするとPipeline実行時にdockerコマンドを打てませんでした。

```shell
Running with gitlab-runner 11.7.0 (8bb608ff)
  on runner-sock 00a0f021
Using Docker executor with image docker:stable ...
Pulling docker image docker:stable ...
Using docker image sha256:73d492654a095a2f91078b2dfacd0cfe1a1fe25412fac54b4eb2f5a9609ad418 for docker:stable ...
Running on runner-00a0f021-project-3-concurrent-0 via e3a06d6dd227...
Fetching changes...
HEAD is now at 8cd04b5 Test for CI
Checking out 8cd04b5f as master...
Skipping Git submodules setup
$ docker info
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
ERROR: Job failed: exit code 1
```

最後のclone_urlですが、今回コンテナ同士なので通常gitlabが持っている自身のアクセス先の情報(通常外部IPアドレスかホスト名だと思います)にrunnerから疎通ができません。そのため、以下のrunnerの設定ファイルを編集して、clone_urlという変数を追加しています。(これはきちんと設計した適切な構成なら起こらなそうですが)
今回はあまりよい方法ではありませんが、`docker network inspect bridge`から調べたgitlabの内部IPアドレスを直接指定しています。

[clone_urlについて](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-clone_url-works)

```toml:/srv/gitlab-runner-dd/config/config.toml
[[runners]]
  name = "My Docker Runner"
  url = "http://172.17.0.2/"
  token = "bfcd1340376dcd0c03705874bcd398"
  executor = "docker"
  clone_url = "http://172.17.0.2"
  [runners.docker]
    tls_verify = false
    image = "docker:stable"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
    shm_size = 0
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
```

Pipelineの最初で、自動的にレポジトリからクローンする動きになるのですが、
これを入れないと以下のようにno route to hostで失敗していました。以下は試行錯誤の途中で※shell executorになっていますが、基本は同じです。

```
Running with gitlab-runner 11.7.0 (8bb608ff)
  on test-runner c99ed8e8
Using Shell executor...
Running on 0529c4062cfb...
Cloning repository...
Cloning into '/home/gitlab-runner/builds/c99ed8e8/0/DPS/DD-Automation'...
fatal: unable to access 'http://gitlab-ci-token:xxxxxxxxxxxxxxxxxxxx@10.118.48.33/DPS/DD-Automation.git/': Failed to connect to 10.118.48.33 port 80: No route to host
```

# Pipelineの実行
ここまでくれば、GitlabのUIからCreate Pipelineとするか、適当にPushすれば、Pipelineが動いてコンテナが起動するはずです。