GitLab CI で kaniko を使って Docker Image をビルドしてみる

# はじめに
皆さんは「[kaniko](https://github.com/GoogleContainerTools/kaniko)」をご存じでしょうか？何やら某プロレタリア文学を彷彿とさせる名称[^1]をしていますが、これは Docker in Docker (以下DinD) を使わずに Docker Image をビルドしてくれるとっても便利なソフトウェアです。なお開発元は Google のようです。DinD を使わないおかげで、よりセキュアに Docker Image をビルドすることができます。また、キャッシュもサポートしているため、ビルド時間の短縮効果も見込めます。

この記事では、そんな kaniko を GitLab CI で動かして、各種 Docker Registry に全自動でプッシュする方法をご紹介します。

# れっつ GitLab CI
今回は以下の4種類のプッシュ先における `.gitlab-ci.yml` の書き方をご紹介します。それぞれお作法が違いますので、その点に注意しつつ解説します。

- Docker Hub
- GitLab Container Registry
- AWS ECR
- GCP GCR

## 大まかなビルドの流れ
基本的に `docker image build` するときと同様で、以下のような順序でビルドならびにプッシュをします。ただし、いつもと違ってビルドとプッシュが1コマンドで行えるように変更されています。

1. kaniko-project/executor イメージの取得
1. Container Registry へのログイン
1. image のビルド & プッシュ

なお、`kaniko-project/executor` の `ENTRYPOINT` が `/kaniko/executor` に設定されている都合上、このイメージ自体を使ってビルドを行う際に不都合が生じる[^2]ため、シェルが含まれる `debug` タグのついたイメージを使った上で、`entrypoint` を強制的に上書きします。つまり、ビルドしたいステージで指定する Docker イメージは少し長くなりますがこのように記述することになります。

```yaml
image:
  name: gcr.io/kaniko-project/executor:debug
  entrypoint: [""]
```

また、ビルドとプッシュは `kaniko-project/executor` イメージに含まれる `/kaniko/executor` というバイナリを使って行います。このとき、`--context` オプションに `$CI_PROJECT_DIR` を、 `--dockerfile` オプションに `$CI_PROJECT_DIR/Dockerfile` をセットすることで、どのディレクトリでビルドを行うのかをこのコマンドに伝えなければなりません。なお、ここで使われている環境変数は GitLab CI によってデフォルトで提供されているものなので、自分で設定する必要がありません。

さらに `--destination` でイメージ名とタグ付けを行うようにしましょう。この `--destination` オプションは複数渡すことができますので、`latest` と `dev` など同じイメージに対して複数のタグ付けを同時に行う際に活用してください。

```bash
$ /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination IMAGE_NAME:latest --destination IMAGE_NAME:TAG
```

## Docker Hub
まずは Docker Hub へのプッシュ方法です。先に Docker Hub へのユーザー名とパスワードをコロンで繋いだものを base64 エンコードした文字列を用意しておきます。これを `DOCKERHUB_TOKEN` として GitLab CI の variable に保存しておいてください。`.gitlab-ci.yml` へのベタ書きは危ないのでやめておきましょう。

```bash
$ echo -n USER:PASSWORD | base64
```
続いてこのトークンを使って Docker Hub にログインする設定を `/kaniko/.docker/config.json` に書き込みます。今回は `echo` コマンドを使って流し込んでしまいます。あとは上で説明したとおりに `executor` コマンドを走らせてあげればビルドとプッシュを行ってくれます。

```yaml
stages:
  - build

build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"https://index.docker.io/v2/\":{\"auth\":\"${DOCKERHUB_TOKEN}\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination IMAGE_NAME:TAG
```

## GitLab Container Registry
こちらも認証情報を `echo` コマンドで `/kaniko/.docker/config.json` に書き込んでいますが、GitLab CI が認証情報をあらかじめ環境変数に詰めてくれているので、特別な設定は不要になっています。

```yaml
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
```

## AWS ECR
まず ECR にプッシュできる権限を持った IAM ユーザーを作り、その `Access Key ID` と `Secret Access Key` を取得しておきます。次にそれらの値をそれぞれ `AWS_ACCESS_KEY_ID` ならびに `AWS_SECRET_ACCESS_KEY` の名前をつけて GitLab CI の variables として保存しておきます。kaniko は内部に `amazon-ecr-credential-helper` を含んでいるため、AWS CLI を導入しなくても、以上の環境変数から自動的に認証を行ってくれます。

ただし、AWS の認証を行うための設定を `/kaniko/.docker/config.json` に書き込む必要がありますので、以下のように `echo` コマンドで流し込みます。蛇足ですが、ECR のアドレスは長くなりがちなので、適当な環境変数に切り出しておくと取り回しが良いと思います。

```yaml
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  variables:
    ECR_URL: 0000111122223333.dkr.ecr.ap-northeast-1.amazonaws.com/image_name
  script:
    - echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination ${ECR_URL}:TAG
```

## GCP GCR
こちらも AWS と似た手順となります。まず GCR にプッシュできる権限を持ったサービスアカウントを発行しておき、その json キーファイルの中身をまるごと `GCP_TOKEN` などの名前で GitLab CI の variables に保存しておきます。CI の中でこの環境変数から適当な場所に JSON ファイルを書き戻し、`GOOGLE_APPLICATION_CREDENTIALS` 環境変数にそのパスをセットしてあげることで、認証が自動で行われます。ここは Google 謹製なだけあって簡単に行えるように作られています。

```yaml
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  variables:
    GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcloud-service-key.json
  before_script:
    - mkdir /tmp
    - echo ${GCP_TOKEN} > /tmp/gcloud-service-key.json
  script:
    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination "asia.gcr.io/${PROJECT_ID}/image_name:TAG"
```

# おわりに
この記事では Container Registry ごとの認証周りを中心に kaniko を使った Docker Image のビルド方法について解説してきました。公式ドキュメントでは Kubernetes 環境を前提とした利用方法を中心に紹介されていて、Gitlab CI を含む各種 CI でどのように kaniko を使えば良いのかという資料が掲載されていませんでしたので、まとめてみた次第です。皆さんも kaniko を使ったセキュアな Docker Image ビルド環境を作ってみてください！

# 参考資料
- [GoogleContainerTools/kaniko: Build Container Images In Kubernetes](https://github.com/GoogleContainerTools/kaniko)
- [Building images with kaniko and GitLab CI/CD](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html)

[^1]: [Issue](https://github.com/GoogleContainerTools/kaniko/issues/61)はあるのですが名称の由来は依然不明です。
[^2]: そのままだとコマンドが一瞬動いてすぐコンテナが終了してしまいます。