Kanikoを使ってみる

# Kanikoとは
Kanikoは、Dockerコンテナ内でDockerイメージをビルドできるツールです。

Dockerコンテナ内で `docker` コマンドを実行すると、 DinD( Docker in Docker) となり、基本的には避けたほうがよいとされます。

とはいえ、KubernetesのPod内でDockerイメージをビルドしたいケース、例えばCDツールのパイプライン上でイメージをビルドしたいケースに困ります。

Kanikoはそういったケースに対応するために開発されたツールで、Dockerデーモンに依存せずイメージをビルドできるため、DinD問題を回避できます。


# Kanikoの動作原理
Kaniko自体は、ツール単体ではなく、配布されているExecutorイメージをそのまま利用することが推奨されています。

[公式ドキュメント](https://github.com/GoogleContainerTools/kaniko#how-does-kaniko-work)によると、


> The kaniko executor image is responsible for building an image from a Dockerfile and pushing it to a registry. Within the executor image, we extract the filesystem of the base image (the FROM image in the Dockerfile). We then execute the commands in the Dockerfile, snapshotting the filesystem in userspace after each one. After each command, we append a layer of changed files to the base image (if there are any) and update image metadata.


Executor イメージは、 `Dockerfile` ファイルの内容を読み取り、

- FROMに指定されたbaseイメージのファイルシステムを抽出
- FROM以降のDockerfileコマンドを順に実行
- コマンド実行ごとに、変更のあったファイルを検知して、baseイメージにレイヤーを追加

という手順でDockerイメージを作成します。
Dockerコンテナ内での `docker` コマンド実行（ すなわち、dockerデーモンを利用)しないので、DinDになりません。
親コンテナの元で新たにコンテナを立てるのではなく、親コンテナの中でDockerfileを順にシミュレートしていく、というイメージでしょうか。

# Kanikoを使ってみる
早速使ってみます。手順はドキュメントにありますが、特に難しいところはありませんでした。
今回はGKE上のKubernetesクラスタ上でイメージを生成します。

## 最終的なファイル構成

```
./
├── build_context # ビルドするイメージのコンテキスト
│   └── Dockerfile
├── pod.yaml # Kaniko Executor Pod
└── sa-key.json # Service Account Credential
```

## Kaniko用のサービスアカウント発行
KanikoはイメージのビルドとリポジトリへのPushまで行うため、GCPのコンテナレジストリへの書き込み権限を持つサービスアカウントを作成します。

```sh
$ MY_GCP_PROJECT=[GCP PROJECT NAME]
$ gcloud iam service-accounts create kaniko-tutorial-sa
Created service account [kaniko-tutorial-sa].

$ gcloud projects add-iam-policy-binding $MY_GCP_PROJECT --member serviceAccount:kaniko-tutorial-sa@${MY_GCP_PROJECT}.iam.gserviceaccount.com --role=roles/storage.admin 
Updated IAM policy for project [xxxx].

$ gcloud iam service-accounts keys create ./sa-key.json --iam-account kaniko-tutorial-sa@${MY_GCP_PROJECT}.iam.gserviceaccount.com

$ kubectl create secret generic kaniko-secret --from-file=sa-key.json                                       
secret/kaniko-secret created
```

## Build Context の作成
KanikoにビルドさせるイメージのDockerfileや関連ファイルは、tarballにまとめます。これを「Build Context」と呼びます。

今回はシンプルにDockerfileだけでビルドします。適当なディレクトリにDockerfileを書きます。

```yaml:build_context/Dockerfile
FROM python:3.6

CMD ["echo", "OK!"]
```

tarballにして、GCSに保存します

```sh
$ tar -cvz -C build_context -f build_context.tar.gz  ./
a .
a ./Dockerfile

$ gsutil cp ./build_context.tar.gz gs://$GCS_BUCKET/kaniko-tutorial/
Copying file://./build_context.tar.gz [Content-Type=application/x-tar]...
/ [1 files][  487.0 B/  487.0 B]
Operation completed over 1 objects/487.0 B.
```

これでBuild Contextの準備ができました。

## Kaniko Executor Pod によるイメージのビルド
あとは、Executor Podをデプロイするだけです。

```yaml:pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kaniko
spec:
  containers:
  - args:
    - --dockerfile=./Dockerfile
    - --context=gs://$(BUCKET)/kaniko-tutorial/build_context.tar.gz
    - --destination=gcr.io/$(PROJECT)/$(IMAGE):$(TAG)
    env:
    - name: GOOGLE_APPLICATION_CREDENTIALS
      value: /secret/sa-key.json
    - name: PROJECT
      value: <PROJECT NAME>
    - name: BUCKET
      value: <BUCKET NAME>
    - name: IMAGE
      value: kaniko-tutorial-image
    - name: TAG
      value: "1.0"
    image: gcr.io/kaniko-project/executor:latest
    imagePullPolicy: Always
    name: kaniko
    volumeMounts:
    - mountPath: /secret
      name: kaniko-secret
  volumes:
  - name: kaniko-secret
    secret:
      defaultMode: 420
      secretName: kaniko-secret
```
特にspec.containers.argsに指定するのは、

- dockerfile
  - build_context内のDockerfileパス
- context
  - build_contextのtarballへのパス
- destination
  - buildしたイメージのpush先

です。
ではPodをapply します。

```sh
$ kubectl apply -f pod.yaml

$ kubectl logs kaniko
INFO[0000] Downloading base image python:3.6
ERROR: logging before flag.Parse: E0107 14:28:55.817834       1 metadata.go:142] while reading 'google-dockercfg' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
ERROR: logging before flag.Parse: E0107 14:28:55.820515       1 metadata.go:159] while reading 'google-dockercfg-url' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
2019/01/06 14:28:55 No matching credentials were found, falling back on anonymous
INFO[0000] Taking snapshot of full filesystem...
INFO[0000] Skipping paths under /kaniko, as it is a whitelisted directory
INFO[0000] Skipping paths under /var/run, as it is a whitelisted directory
INFO[0000] Skipping paths under /secret, as it is a whitelisted directory
INFO[0000] Skipping paths under /dev, as it is a whitelisted directory
INFO[0000] Skipping paths under /proc, as it is a whitelisted directory
INFO[0000] Skipping paths under /sys, as it is a whitelisted directory
INFO[0000] CMD ["echo", "OK!"]
2019/01/06 14:28:57 existing blob: sha256:fcce419a96b1219a265bf7a933d66b585a6f8d73448533f3833c73ad49fb5e88
2019/01/06 14:28:57 existing blob: sha256:83aa5374cd047c9396a1117e05252608dd79e602f3c7fe99a65d86555b75c5ee
2019/01/06 14:28:57 existing blob: sha256:045b51e26e750443c84216071a1367a7aae0b76245800629dc04934628b4b1ea
2019/01/06 14:28:57 existing blob: sha256:c2677faec825930a8844845f55454ee0495ceb5bea9fc904d5b3125de863dc1d
2019/01/06 14:28:57 existing blob: sha256:cd8eada9c7bb496eb685fc6d2198c33db7cb05daf0fde42e4cf5bf0127cbdf38
2019/01/06 14:28:59 pushed blob sha256:0ec11879e44c9d6fd8d91cdcc55d26566629067abb04169bd5145cc6c32ff3fe
2019/01/06 14:28:59 pushed blob sha256:0368bc11a2b243597b00a308cc70dfc374f65aa9cabaae9a7f805e99bd9f2352
2019/01/06 14:28:59 pushed blob sha256:702768f4c52e7c06a9a4ed4d3ad924438157b83f63373be288c20c4e14c9d83e
2019/01/06 14:28:59 pushed blob sha256:135f9ac87f0e0c3b9a12cbb5e518a0210a575f7fb0690dd5288363d1f1bcc84f
2019/01/06 14:29:00 pushed blob sha256:9832b37d722c9c872ec8807992d786c116e6305bdf15334a4047e7c49bd8eadc
2019/01/06 14:29:00 gcr.io/xxx/kaniko-tutorial-image:1.0: digest: sha256:529ba0854a64317f0e7facf6c4be113c0de7ba726744ec134dc7e0a9b4c1b67 size: 1742
```
これでCloud Container Registryにイメージがアップされているはずです。

さっそく実行してみます。

```
$ docker run gcr.io/${MY_GCP_PROJECT}/kaniko-tutorial-image:1.0
Unable to find image 'gcr.io/xxxx/kaniko-tutorial-image:1.0' locally
1.0: Pulling from xxxx/kaniko-tutorial-image
cd8eada9c7bb: Pull complete
c2677faec825: Pull complete
fcce419a96b1: Pull complete
045b51e26e75: Pull complete
83aa5374cd04: Pull complete
135f9ac87f0e: Pull complete
9832b37d722c: Pull complete
702768f4c52e: Pull complete
0368bc11a2b2: Pull complete
Digest: sha256:529ba0854a64317f0e7facf6c4be113c0de7ba726744ec134dc7e0a9b4c1bd67
Status: Downloaded newer image for gcr.io/xxxx/kaniko-tutorial-image:1.0
OK!
```
ちゃんと動いていそうです。

# まとめ

KanikoをつかってKubernetes内でイメージをビルドしてみましたが、使い方は非常に簡単ですね。