kaniko でコンテナイメージをビルドする

#初めに

コンテナを利用するにあたり、避けて通れないのがコンテナイメージのビルドです。
簡単にビルドするのであれば、 docker と Dockerfile を利用してビルドするのがよいとおもいます。
ただ、 docker を使っていない 各種クラウドのマネージドサービスや、kubernetes 環境の場合、`docker build` を使うことはできません。
最近、kubernetes しかない環境でコンテナイメージをビルドする必要ができたため、
以前から名前だけ知っていた [kaniko](https://github.com/GoogleContainerTools/kaniko) というビルドツールを使ってみました。

#kaniko 概要

kaniko は google が開発しているビルドツールで コンテナ内で稼働します。
他に、以下のような特徴があります。

- Dockerfile を使ってビルドする
- docker デーモンが無くてもビルドできる
- 特権は不要
- ビルド結果は指定したコンテナレジストリに格納する


kaniko の動作の概要としては、下図のようになります。

1. kaniko イメージを用い、Pod （コンテナ）を作成する
2. Pod 内で稼働する executor が GitHub （またはその他の格納場所）からビルドコンテキストを取得する
3. Dockerfile を読み取り、 FROM で指定された base image を取得する
4. Dockerfile 内のコマンドに沿って Image Layer を追加していき、新しいコンテナイメージをビルドする
5. 指定したコンテナレジストリに push する （secret に登録した認証情報を利用する）

|![kaniko01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/f4235503-ee5b-179c-d71a-5518277f9d2e.png)
|:-:|

※ [ビルドコンテキスト](https://github.com/GoogleContainerTools/kaniko#kaniko-build-contexts) と呼ばれる ビルドに必要な各種ファイル類は、上図では GitHub に入れていますが、 GitHub 以外に S3、GCS、ローカルディレクトリ、ローカルファイル（.tar.gz）、はては標準入力（.tar.gz を流し込む）まで利用できます。


#ビルド手順

ビルドの動作としては、上記 1.～5. ですが、実際の手順は以下のようになります。

- ビルド関連のファイルを GitHub に配置
- push 先コンテナレジストリの認証情報を secret として定義
- kaniko Pod を作成（ここで 上記 1.～5. が実行される）

1点目の GitHub に配置するところは割愛し、 secret の作成から記載していきます。

##コンテナレジストリ用 secret の作成

最終的にイメージを push するコンテナレジストリの認証情報を secret として作成します。
今回は docker hub を利用します。
secret のタイプはよく使う `generic` ではなく、`docker-registry` タイプです。　初めて使いました。
以下の例では、`regcred` という名前の secret を作成しています。

```shell
kubectl create secret docker-registry regcred  \
      --docker-server=https://index.docker.io/v1/ \
      --docker-username=ユーザー名 \
      --docker-password=パスワード \
      --docker-email=メールアドレス
```
※ユーザー名などは、 docker hub のアカウントの ユーザー名、パスワード、メールアドレスです。
　また、 docker hub を利用する場合の `--docker-server` は上記の内容を指定すればよいようです。
**<font color="red">※ secret 内のパスワードデータ等は base64 エンコードされているだけですので、デコードすれば読み取れます。取扱いに注意が必要です。**</font>

##kaniko 用 YAML の準備

kaniko Pod を作成するため、 YAML ファイルを準備します。(ここでは pod.yamlとします)

```yaml:pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kaniko
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:latest
    args: ["--dockerfile=Dockerfile",
            "--context=git://github.com/s*******-*****/*****.git",
            "--destination=s*******/liberty-counter"]
    volumeMounts:
      - name: kaniko-secret
        mountPath: /kaniko/.docker
  restartPolicy: Never
  volumes:
    - name: kaniko-secret
      secret:
        secretName: regcred
        items:
          - key: .dockerconfigjson
            path: config.json
```

- gcr.io/kaniko-project/executor:latest
    - kaniko イメージです。オフィシャルイメージ以外の利用はサポートされていません
- --dockerfile=Dockerfile
    - Dockerfile のパスを指定します。
    - ローカルファイル等の場合は volumeMounts でコンテナにマップされているパスを指定します。（/workspace/Dockerfile など）
    - この例では、GitHub リポジトリのトップに Dockerfile があるため、Dockerfile としています。
- --context
    - GitHub リポジトリの URL を指定します。 接頭辞は `https://` ではなく `git://` です。
    - プライベートリポジトリを指定する場合は、git://GIT_TOKEN&#64;github.com/...  とするか、環境変数 GIT_TOKEN にリポジトリアクセス用のトークンを設定します
- --destination
    - push 先コンテナレジストリの ID/リポジトリ名を指定します

また、 secret を volumeMount する部分は下図のようなイメージです。
（左が secret の yaml 出力、右が Pod の yaml です）
一言で言うと、secret 内の .dockerconfigjson の値を Pod 内の /kaniko/.docker/config.json にマップしています。

|![kaniko02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/72b4d088-5f9a-2178-fe22-9a1d37c62f01.png)
|:-:|

## kaniko Pod の作成・コンテナイメージのビルド

Pod を作成すると、Dockerfile に沿ってイメージがビルドされ、コンテナレジストリに push されます

```shell-session
# kubectl apply -f pod.yaml
```

Pod の STATUS が Completed になれば完了です。

```shell-session
# kubectl get pods -o wide
NAME     READY   STATUS      RESTARTS   AGE    IP               NODE       NOMINATED NODE   READINESS GATES
kaniko   0/1     Completed   0          110s   10.200.211.221   k8swkr21   <none>           <none>
```

Pod のログを確認します。（空行部分は、ログが一部省略されています）

````shell-session
# kubectl logs -f kaniko

Enumerating objects: 29, done.
Counting objects: 100% (29/29), done.
Compressing objects: 100% (22/22), done.
Total 29 (delta 3), reused 28 (delta 2), pack-reused 0

INFO[0009] Retrieving image manifest docker.io/websphere-liberty:webProfile8
INFO[0009] Retrieving image docker.io/websphere-liberty:webProfile8 from registry index.docker.io

INFO[0012] Retrieving image manifest docker.io/websphere-liberty:webProfile8

INFO[0012] Unpacking rootfs as cmd ADD   --chown=1001:0  ./files /config requires it.
INFO[0031] LABEL maintainer "s*******@example.com"
INFO[0031] Applying label maintainer=s*******@example.com
INFO[0031] USER 1001
INFO[0031] cmd: USER
INFO[0031] Using files from context: [/kaniko/buildcontext/files]
INFO[0031] ADD   --chown=1001:0  ./files /config
INFO[0031] Taking snapshot of files...
INFO[0031] COPY  --chown=1001:0  ./war/LibertyCounter.war /config/dropins
INFO[0031] Taking snapshot of files...
INFO[0031] RUN     configure.sh
INFO[0031] Taking snapshot of full filesystem...
INFO[0033] cmd: /bin/sh
INFO[0033] args: [-c configure.sh]
INFO[0033] Running: [/bin/sh -c configure.sh]

INFO[0080] Taking snapshot of full filesystem...
INFO[0085] Pushing image to s*******/liberty-counter
INFO[0110] Pushed image to 1 destinations
````

ビルド後、push したコンテナレジストリを確認すると、コンテナイメージが格納されていることがわかります。

|![kaniko04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/74a14666-344d-694f-5737-1c187f4bd3b1.png)
|:-:|

## （参考）ローカルディレクトリを利用する場合

kaniko の[チュートリアル](https://github.com/GoogleContainerTools/kaniko#tutorial) にもありますが、ローカルディレクトリ上に配置することも可能です。
ローカルディレクトリに配置する場合、 PV/PVC で volumeMount し、コンテナ内からアクセスできるようにする必要があります。

以下は チュートリアルの例に簡単な説明を付け加えた内容です。

まず、 適当なディレクトリ配下に Dockerfile と関連するファイル類を配置します。
（ここでは、 `/data/tools/kaniko01` 以下に配置しています）
そのディレクトリを Persistent Volume として定義します。（ hostPath として /data/tools/kaniko01 を指定しています）
※ hostPath なので、ワーカーノードから見えるところに配置しておく必要があります。

```yaml:volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: dockerfile
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: local-storage
  hostPath:
    path: /data/tools/kaniko01
```

次に Persistent Volume Claim を作成して、先程の PV を claim します。

```yaml:volume-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: dockerfile-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: local-storage
```

PV、PVC の状態は以下のように STATUS が Bound になっています。

```shell-session
# kubectl get pv -o wide
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS    REASON   AGE   VOLUMEMODE
dockerfile   10Gi       RWO            Retain           Bound    default/dockerfile-claim   local-storage            7d    Filesystem

# kubectl get pvc -o wide
NAME               STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS    AGE   VOLUMEMODE
dockerfile-claim   Bound    dockerfile   10Gi       RWO            local-storage   7d    Filesystem
```

このPVC を Pod から利用します。
基本的には、secret の利用も含め最初に記載した pod と同じ形態ですが、
kaniko に渡す引数は以下の点が異なります。

- --dockerfile
    - /workspace/Dockerfile としています。これは Dockerfile があるパスをコンテナの /workspace 以下にマウントするためです。
- --context
    - dir://workspace です。ローカルディレクトリを使うため、 `dir://` で、パスは workspace を指定しています。

```yaml:pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kaniko
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:latest
    args: ["--dockerfile=/workspace/Dockerfile",
            "--context=dir://workspace",
            "--destination=s*******/hello-kaniko"]
    volumeMounts:
      - name: kaniko-secret
        mountPath: /kaniko/.docker
      - name: dockerfile-storage
        mountPath: /workspace
  restartPolicy: Never
  volumes:
    - name: kaniko-secret
      secret:
        secretName: regcred
        items:
          - key: .dockerconfigjson
            path: config.json
    - name: dockerfile-storage
      persistentVolumeClaim:
        claimName: dockerfile-claim
```

PV/PVC/Pod の関係は下図のようになります。
なお、Pod はどの worker ノード上で稼働するかわかりませんので、 PV はそれを考慮して共有のストレージ上に定義しておく必要があります。

|![kaniko05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310495/d76b897a-4288-b4ba-e8af-a35c9188e6af.png)
|:-:|

ここまで準備ができればあとは、 pod を作成し、ビルドするだけです。
内容的には前述の内容と同じですので割愛します。

#終わりに

コンテナイメージをビルドする際 docker は便利ですが、最近はdocker が無いコンテナ環境も多くなっています。
そういった環境下では kaniko が利用可能であることがわかりました。
kaniko Pod を作成し、GitHub 上のビルドコンテキストを取得、ビルドする、という方式は
将来的に CI/CD 等に拡張しやすいのではないかと思います。
今回、私にとっては (docker のない) kubernetes 環境のみでビルドが可能になる点がメリットになりました。
（ただし、普通は専用のビルド環境があると思いますので、限定されたケースかもしれません）
この先 kaniko を利用される方の参考になればうれしいです。
また、コメントやアドバイスなどがあればコメント欄でお知らせいただければ幸いです。


---
参考）
■ kaniko
https://github.com/GoogleContainerTools/kaniko

----