Gitlab CI Runner のジョブに AWS ECR のイメージを使う

Gitlab Runner で実行するジョブのイメージを Amazon ECR から Pull する方法（ジョブの中で `docker pull` という話ではなく）。

要するに `.gitlab-ci.yml` で下記のように ECR のイメージを指定できるようにするということです。

```yaml
test:
  stage: test
  image: 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com/oreore:latest
  script:
    - make test
```

前提として Gitlab Runner が [docker-credential-ecr-login (amazon-ecr-credential-helper)](https://github.com/awslabs/amazon-ecr-credential-helper) の Credentials store を使って ECR にログインできるようにする必要があるため、Gitlab Runner のイメージに `docker-credential-ecr-login` を追加したイメージを作成しました。

- [ngyuki/gitlab-runner - Docker Hub](https://hub.docker.com/r/ngyuki/gitlab-runner)

## 手順

```sh
# ディレクトリを準備
mkdir -p /opt/gitlab-runner/{ecr,runner}

# 環境変数で AWS 認証情報を指定
cat <<'EOS'> /opt/gitlab-runner/.env
AWS_ACCESS_KEY_ID=AKI...
AWS_SECRET_ACCESS_KEY=abc123...
EOS

# Gitlab Runner を開始
# --env-file で環境変数ファイルを指定
docker run --detach \
    --name gitlab-runner \
    --env-file /opt/gitlab-runner/.env \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume /opt/gitlab-runner/runner/:/etc/gitlab-runner/ \
    --volume /opt/gitlab-runner/ecr/:/root/.ecr/ \
    ngyuki/gitlab-runner:alpine

# Gitlab に Runner を登録
# DOCKER_AUTH_CONFIG で Credentials store を指定
docker exec gitlab-runner gitlab-runner register \
    --non-interactive \
    --url 'http://gitlab.example.com/' \
    --registration-token "${REGISTRATION_TOKEN:?}" \
    --name 'docker-runner-with-ecr' \
    --executor 'docker' \
    --docker-image 'alpine:latest' \
    --env 'DOCKER_AUTH_CONFIG={"credsStore":"ecr-login"}'

# docker-credential-ecr-login get で ECR 認証情報を `~/.ecr/cache.json` に保存
echo 999999999999.dkr.ecr.ap-northeast-1.amazonaws.com | docker exec -i gitlab-runner docker-credential-ecr-login get
```

## 詳細

### Docker の Credentials store の指定

Docker の Credentials store は Gitlab のプロジェクトの CI の`DOCKER_AUTH_CONFIG` 変数に以下のような JSON の内容をそのまま指定できます。

```json
{
    "credsStore": "ecr-login"
}
```

もしくは `gitlab-runner register` で `--env 'DOCKER_AUTH_CONFIG={"credsStore":"ecr-login"}'` のようにコマンドラインオプションを指定すれば、この Gitlab Runner が実行するすべてのジョブの変数に自動的に追加されるので、プロジェクトで個別に指定する必要はなくなります。

`DOCKER_AUTH_CONFIG` はもともとは次のようなプライベートレジストリの認証情報を指定するためのものですが、

```json
{
    "auths": {
        "registry.example.com:5000": {
            "auth": "b3JlX25vX2hpbWl0dQo="
        }
    }
}
```

ECR の場合 `aws ecr get-login` で取得したトークンは12時間で有効期限切れになるため、上のようにベタで指定することはできません。

[Add support for credentials store (!501) · Merge Requests](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/501/commits) で Gitlab Runner で Credentials store がサポートされるようになったので、前述のように `ecr-login` が指定できます。ただし、Docker CLI と同等ではないので下記のような `credHelpers` とかは対応していません。

```json
{
    "credHelpers": {
        "999999999999.dkr.ecr.ap-northeast-1.amazonaws.com": "ecr-login"
    }
}
```

`credsStore` のみがベタで対応されているようです。

- https://gitlab.com/gitlab-org/gitlab-runner/blob/master/helpers/docker/auth_config.go#L114
- https://gitlab.com/gitlab-org/gitlab-runner/blob/master/vendor/github.com/docker/cli/cli/config/configfile/file.go#L37

ただ、ひとつのプロジェクトで複数のレジストリで Credentials store を使い分けるとかでも無い限りは問題なさそうです。

なお、`~/.docker/config.json` に記述しても有効なので、`DOCKER_AUTH_CONFIG` の代わりに `~/.docker/config.json` を作成しても OK です。

### ECR 認証情報の取得

Gitlab Runner が Credentials store を使用するとき、`list` サブコマンドですべての認証情報を取得しようとします。

`docker-credential-ecr-login list` は `~/.ecr/cache.json` に記録されている ECR の認証情報の一覧を返します。`docker-credential-ecr-login get` で認証情報を取得すると `~/.ecr/cache.json` へキャッシュとして保存されるため、あらかじめ手動で認証情報を取得しておきます。

あるいは、`~/.ecr/cache.json` がなければ `docker-credential-ecr-login list` は `AWS_REGION` 環境変数で指定されたリージョンの ECR の認証情報を返すので、`docker-credential-ecr-login get` を手で実行する代わりに `AWS_REGION` 環境変数を指定しておいても OK です。

```sh
# 環境変数でリージョンも指定
# （docker-credential-ecr-login get は不要）
cat <<'EOS'> /opt/gitlab-runner/.env
AWS_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=AKI...
AWS_SECRET_ACCESS_KEY=abc123...
EOS
```

もしくは、環境変数 `AWS_SDK_LOAD_CONFIG=true` があれば `~/.aws/config` からでもリージョンを得られるので、`AWS_REGION` 環境変数の代わりに使用することもできます。

## さいごに

Credentials store を使わなくても cron とかで定期的に `$(aws ecr get-login --no-include-email)` して `~/.docker/config.json` の認証情報を更新すれば大丈夫だろうと思いますが、余分な処理が不要な分 `docker-credential-ecr-login` を使うほうがよいでしょう。

## 参考

- [Pull images from aws ecr or private registry (#1583) · Issues · GitLab.org / gitlab-runner · GitLab](https://gitlab.com/gitlab-org/gitlab-runner/issues/1583)
- [Add support for credentials store (!501) · Merge Requests · GitLab.org / gitlab-runner · GitLab](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/501/commits)