GitLab CI で docker-compose を使う

数時間苦闘したのでメモ。

## .gitlab-ci.yml

```yaml
build:
  stage: build
  image: docker:stable
  services:
    - docker:dind
  script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true
    - docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG app
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG

test:
  stage: test
  image: tmaier/docker-compose:19.03
  services:
    - docker:dind
  script:
    - docker version
    - docker-compose version
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - sed -i "s|build:|# build:|" docker-compose.yml
    - sed -i "s|context:|# context:|" docker-compose.yml
    - sed -i "s|app_image|$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG|" docker-compose.yml
    - docker-compose -p $CI_COMMIT_SHA config
    - docker-compose -p $CI_COMMIT_SHA pull
    - docker-compose -p $CI_COMMIT_SHA run test
  after_script:
    - docker-compose -p $CI_COMMIT_SHA down -v
    - docker-compose -p $CI_COMMIT_SHA rm -f
```

- build ジョブは標準的な手順のまま。[^2]
- test では
  - image: [docker/compose](https://hub.docker.com/r/docker/compose/) もあったが、 variables を設定してもサーバーとうまく接続できなかったので、 [tmaier/docker-compose](https://hub.docker.com/r/tmaier/docker-compose) を使うようにした[^1]。このイメージで直接テストを行うのは難があるので、テストスクリプトを実行して終了するサービス test を追加している。
  - sed: ローカルでは build context で作っているのでそれをコメントアウトしている。イメージは build ジョブで作ったものを使用するようにしている。
  - $CI_COMMIT_SHA: 複数のジョブまたはパイプラインが同時に実行される可能性がある場合は `-p project_name` で一意にしないとおそらく破綻する。
  - after_script: コンテナとボリュームとネットワークの後始末。

[^1]: https://stackoverflow.com/questions/39868369/run-docker-compose-build-in-gitlab-ci-yml
[^2]: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html

## docker-compose.yml

そのまま。
ローカルで test イメージを起動したくないときは `docker-compose up -d app` などする。

```yaml
version: "3.0"
services:
  redis:
    image: redis:5.0
    volumes:
      - redis_data:/data

  app:
    build:
      context: app
    image: app_image
    depends_on:
      - redis
    ports:
      - "80:80"
    command: "/app/main.py"

  test:
    image: app_image
    depends_on:
      - app
    volumes:
      - ./:/work:ro
    command: "/work/test.sh"

volumes:
  redis_data:
```

## CI Runner の config.toml

関係する部分を抜粋。

```toml
[[runners]]
  name = "xxx"
  url = "xxx"
  token = "xxx"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.docker]
    tls_verify = false
    image = "ubuntu:18.04"
    privileged = true
    volumes = ["/certs/client", "/cache"]
```

## 検証環境

CI のログから。

```
$ docker version
 Client: Docker Engine - Community
  Version:           19.03.5
  API version:       1.40
  Go version:        go1.12.12
  Git commit:        633a0ea838
  Built:             Wed Nov 13 07:22:05 2019
  OS/Arch:           linux/amd64
  Experimental:      false
 Server: Docker Engine - Community
  Engine:
   Version:          19.03.6
   API version:      1.40 (minimum version 1.12)
   Go version:       go1.12.16
   Git commit:       369ce74a3c
   Built:            Thu Feb 13 01:32:22 2020
   OS/Arch:          linux/amd64
   Experimental:     false
  containerd:
   Version:          v1.2.12
   GitCommit:        35bd7a5f69c13e1563af8a93431411cd9ecf5021
  runc:
   Version:          1.0.0-rc10
   GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
  docker-init:
   Version:          0.18.0
   GitCommit:        fec3683
 $ docker-compose version
 docker-compose version 1.25.1, build a82fef0
 docker-py version: 4.1.0
 CPython version: 2.7.16
 OpenSSL version: OpenSSL 1.1.1d  10 Sep 2019
```