Google発のコンテナアプリケーション開発支援ツール「Skaffold」や「Kaniko」を使ってみる

3月にGoogleがKubernetes向けの新たなツール「Skaffold」を公開した。このツールはKubernetesクラスタ上で稼動させるアプリケーションの継続的開発を支援するコマンドラインツールで、アプリケーションのビルド、プッシュ、実装ワークフローを支援する機能を備えている。本記事ではこのSkaffoldや、クラスタ内でコンテナイメージをビルドするためのツールである「Kaniko」の使い方を紹介する。

ビルドやイメージの作成、デプロイを自動化できる「Skaffold」

今回紹介するSkaffoldは、Googleが開発しオープンソースで提供するコンテナクラスタ向けビルド・デプロイ自動化ツールだ。GoogleはKubernetesを利用してコンテナを管理できるクラウドサービスを提供しており、そこでの利用が想定されているが、それ以外のKubernetesクラスタ環境でも利用できる。

Skaffoldが持つ機能自体はシンプルで、次の一連の作業を実行するというものになっている。

  1. 用意したDockerfileに基づいてコンテナイメージを作成する
  2. 作成したコンテナイメージをDockerレジストリにプッシュする
  3. プッシュしたイメージを使ってKubernetes上にコンテナを作成する

これらの作業はdockerコマンドやKubernetesのフロントエンドであるkubectlコマンドだけでも実現できるが、Skaffoldではコマンド1つでこれらの一連の作業をまとめて実行できる点が特徴となる。また、コンテナ作成に必要となるファイルなどを監視し、それらが更新されたら自動的に処理を実行させることもできる(図1)。

図1 Skaffoldのリポジトリに掲載されているSkaffoldの機能イメージ

Skaffoldのインストールと利用のための準備

SkaffoldのソースコードはGitHubリポジトリで公開されている。また、GitHubのリリースページではLinux(amd64)およびmacOS、Windows向けのバイナリも公開されている。使用するアーキテクチャに対応するバイナリをダウンロードし、パスの通ったディレクトリにコピーして「skaffold」(Windowsの場合は「skaffold.exe」)というファイル名にリネームすればインストールは完了だ。

使用するDockerレジストリの登録

Skaffoldを利用するには作成したコンテナイメージをプッシュするDockerレジストリや、コンテナを実際に実行するKubernetesクラスタの準備も必要だ。Dockerレジストリについては、Dockerが提供するDockerHubだけでなく、Googleがクラウド形式で提供するリポジトリやプライベートリポジトリなども利用可能だ。なお、プライベートリポジトリの構築手順は「継続的デリバリ」(CD)を実現できるコンテナクラスタ管理ツール「Spinnaker」記事やKubernetesによるDockerコンテナ管理入門記事で紹介しているので、独自のプライベートリポジトリを構築したい場合はこちらを参考にして欲しい。

また、Skaffoldはskaffoldコマンドを実行したユーザーのホームディレクトリ以下にある「.docker/config.json」ファイルに記述された認証情報を使ってDockerレジストリにアクセスする。そのため、事前に「docker login」コマンドを使ってDockerレジストリのアカウント情報を登録しておく必要がある。

たとえばDockerHubを利用する場合、あらかじめDockerHubのWebサイトでアカウントを作成しておいた上で、次のようにアカウント情報の登録を行っておく。

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: ←ユーザー名を入力
Password: ←パスワードを入力
Login Succeeded

また、DockerHub以外のリポジトリを利用する場合は「docker login <ホスト名>:<ポート番号>」と実行して同様にアカウント情報を登録しておく。

$ docker login <ホスト名>:<ポート番号>
Username: ←ユーザー名を入力
Password: ←パスワードを入力
Login Succeeded

なお、認証が不要なローカルリポジトリを利用する場合は「docker login」コマンドでのログインは不要だが、設定ファイルが存在しないとSkaffoldの実行時にエラーとなってしまう。その場合、次のようにして空の設定ファイルを作成しておけば良い。

Linux/macOS環境の場合
$ echo {} > ~/.docker/config.json

Windows環境の場合
> echo {} > %HOME%\.docker\config.json

Kubernetesへのアクセスに使用する情報を登録する

コンテナを実際に実行するKubernetesクラスタの情報は、「kubectl config」コマンドで登録する。ただしminikubeコマンドを利用して作成されたKubernetesクラスタを利用する場合、この作業が自動的に行われるためこの作業は不要だ。

Kuernetesクラスタでは複数の認証方法があるが、たとえばローカルのKubernetes環境であれば次のようにしてアカウントを登録できる。

$ kubectl config set-credentials myself --username=<アカウント名> --password=<パスワード>
$ kubectl config set-cluster local-server --server=<Kubernetes APIサーバーのURL>
$ kubectl config set-context default-context --cluster=local-server --user=myself
$ kubectl config use-context default-context
$ kubectl config set contexts.default-context.namespace default

登録後、kubectlコマンドでノードの情報が取得できれば設定は完了だ。

$ kubectl get node
NAME        STATUS    ROLES     AGE       VERSION
以下、ノードの情報が表示される

Skaffoldを使ってみる

Skaffoldを利用するには、次の3つのファイルが必要だ。

  • コンテナイメージを作成するためのDockerfile
  • デプロイ設定を記述したKubernetesのマニフェストファイル
  • Skaffoldの設定ファイル(skaffold.yaml)

まずDockerfileだが、これについては特別な設定などは必要なく、手動で「docker build」コマンドを実行する場合のDockerfileと同じものが利用できる。

Kubernetesのマニフェストファイルは、コンテナイメージの作成およびプッシュ後に実行されるものとなる。こちらも特別な設定などは不要だ。また、ファイル名についても任意のものが使用できる。

最後の「skaffold.yaml」が、Skaffoldを利用するに当たってもっとも重要となる設定ファイルだ。このファイルにはDockerfileから作成されたコンテナイメージのイメージ名、ビルドやプッシュの実行後に実行するKubernetesのマニフェストファイル名などをYAML形式で指定する。シンプルな設定例としては、以下のようになる。

apiVersion: skaffold/v1alpha2
kind: Config
build:
  artifacts:
    - imageName: <イメージ名>
      workspace: .  ←作業ディレクトリとしてカレントディレクトリを指定
deploy:
  kubectl:
    manifests:
      - k8s-*  ←カレントディレクトリ内の「k8s-」で始まる全ファイルを対象とする

ここで「イメージ名」は作成するイメージ名を「<リポジトリホスト名>:<ポート番号>/<ユーザー名>/<イメージ名>」で指定する。また、イメージのビルドおよびプッシュ完了後に実行するマニフェストファイルとして「k8s-*」を指定している。これにより、イメージファイルのビルドおよびプッシュ完了後、カレントディレクトリ内にある「k8s-」で始まるファイルを使ってコンテナのデプロイが行われるようになる。

「dev」モードでSkaffoldを実行する

設定ファイルを用意したら、skaffold.yamlファイルを用意したディレクトリ内で次のように引数として「dev」を付けてskaffoldコマンドを実行する。

$ skaffold dev

するとコンテナイメージのビルドが実行され、成功したら続いて指定したDockerレジストリに作成されたイメージがアップロードされ、それを元にKubernetes上でコンテナが作成される、という一連の作業が実行される。

また、devモードではこれらの作業においてエラーが発生した場合、Skaffoldの実行自体は終了せず、待機状態となる。たとえば次の例は、Dockerfileでの設定に問題があり、正しくコンテナイメージをビルドできなかった場合の例だ。

$ skaffold dev
Starting build...
Sending build context to Docker daemon  9.216kB
  
  
Step 7/13 : RUN atp-get install -y gunicorn python-lucene python-mysql.connector python-yaml
 ---> Running in 7b8f2c32178c
/bin/sh: 1: atp-get: not found
WARN[0001] run: build: build step: running build for artifact: running build: The command '/bin/sh -c atp-get install -y gunicorn python-lucene python-mysql.connector python-yaml' returned a non-zero code: 127
Watching for changes...

ここでは、コンテナイメージのビルドの失敗後、「Watching for changes...」というメッセージを出力して待機状態になっていることが分かる。この状態で(skaffoldコマンドを終了せずに)問題となっているDockerfileを修正して保存すると、Skaffoldはこれを自動的に検出して再度コンテナイメージのビルド作業を実行し、成功すればその後イメージのプッシュやコンテナの作成などを続けて実行する。

ちなみに、skaffold.yaml内でコンテナイメージのビルドに使用するDockerfileを指定することも可能だ。これは、次のように「artifacts:」以下の「dockerfilePath:」というパラメータで指定できる。

apiVersion: skaffold/v1alpha2
kind: Config
build:
  artifacts:
    - imageName: <イメージ名>
      workspace: .  ←作業ディレクトリとしてカレントディレクトリを指定
      dockerfilePath: <使用するDockerファイルのパス>
deploy:
  kubectl:
    manifests:
      - k8s-*  ←カレントディレクトリ内の「k8s-」で始まる全ファイルを対象とする

また、skaffoldコマンドの実行時に「-f <使用するSkaffoldの設定ファイル>」オプションで「skaffold.yaml」以外の設定ファイルを使用するように設定することもできる。

「run」モードで実行する

devモードでは、イメージのビルドやプッシュ、コンテナの起動などの処理に失敗した場合でもskaffoldコマンドは終了せず、設定などが変更された場合に自動的にこれらの処理を繰り返すようになっている。いっぽう、「run」モードではすべての処理に成功した場合、もしくはいずれかの処理に失敗した場合に処理を終了する。これは、継続的インテグレーションツールなどと組み合わせて自動処理を行わせる場合などに有用だ。

runモードでSkaffoldを実行するには、次のように引数として「run」と、「-t <タグ名>」を付けてskaffoldコマンドを実行すれば良い。

$ skaffold run -t <タグ名>

devモードでは自動的にコンテナイメージにハッシュ値に基づくタグ名が付けられていたが、runモードではこのように「-t」オプションでタグ名を指定できるようになっている。

runモードでSkaffoldを実行すると、devモードの場合と同様にDockerfileに基づいたイメージの作成やDockerレジストリへのプッシュ、用意したマニフェストファイルに従ったコンテナの作成が実行される。正常に処理が完了すれば終了コード0を、そうでなければ終了コード1を返すようになっており、この値をチェックして別のツールから処理の成功/失敗を検出できる。

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

DockerやKubernetesを利用するための支援ツールとして、「Kaniko」というツールも紹介しておこう。こちらもGoogleが開発したもので、DockerコンテナやKubernetesクラスタ内でコンテナイメージをビルドするためのツールだ。

通常コンテナイメージをビルドするには「docker build」コマンドを利用するのだが、dockerコマンドを利用するには多くの場合特別な権限が必要となる。一般ユーザーがdockerコマンドを実行できるように設定することもできるが、通常Dockerコンテナはroot権限で実行されるため、これを悪用すると一般ユーザーがroot権限を得ることもできてしまう。Kanikoはこういった問題を解決するためのツールで、Googleがクラウド形式で提供するKubernetesクラスタ内でコンテナをビルドするために利用することを意図しているようだが、適切に設定すれば独自に構築したKubernetesクラスタ上でも利用することができる。

kanikoを使ってみる

kanikoはGitHub上でソースコードが公開されており、ここからソースコードをダウンロードして自前でビルドして使うこともできるが、あらかじめ環境一式が含まれたコンテナイメージを利用するほうが手軽だ。コンテナイメージはGoogleのDockerレジストリである「gcr.io」上で「gcr.io/kaniko-project/executor:latest」として公開されている。

たとえばローカルのDockerコンテナ内でイメージのビルドを行うには、「docker login」コマンドでビルドしたイメージのプッシュ先リポジトリにアクセスできる状態にした上で、次のようにdockerコマンドを実行すれば良い。

docker run -ti --rm -v <contextディレクトリ>:/workspace -v ~/.docker:/root/.docker gcr.io/kaniko-project/executor:latest -d <イメージ名> -c /workspace

ここで、イメージ名は「<プッシュ先リポジトリ>/<ユーザー名>/<イメージ名>:<タグ名>」の形式で指定する。また、「contextディレクトリ」というのはDockerfileや必要なファイルなどが格納されたディレクトリのことだ。この場合、contextディレクトリ内の「Dockerfile」ファイルを使ってイメージがビルドされるが、-fオプションを使って「-f <使用するDockerfile>」のように指定することで別のファイルを使ってビルドを実行することもできる。

たとえば「~/test」というディレクトリ内にあるDockerfileを使い、作成したイメージを「example.com」というリポジトリに「example.com/foo/bar:latest」というイメージ名でプッシュするには、次のように実行する。

$ docker run -ti --rm -v ~/test:/workspace -v ~/.docker:/root/.docker gcr.io/kaniko-project/executor:latest -d example.com/foo/bar:latest -c /workspace
INFO[0000] Unpacking filesystem of busybox...
INFO[0000] Whitelisted directories are [/kaniko /proc /dev /dev/pts /sys /sys/fs/cgroup /sys/fs/cgroup/systemd /sys/fs/cgroup/hugetlb /sys/fs/cgroup/cpuacct,cpu /sys/fs/cgroup/devices /sys/fs/cgroup/blkio /sys/fs/cgroup/freezer /sys/fs/cgroup/cpuset /sys/fs/cgroup/perf_event /sys/fs/cgroup/memory /dev/mqueue /workspace /root/.docker /etc/resolv.conf /etc/hostname /etc/hosts /dev/shm /dev/console /proc/bus /proc/fs /proc/irq /proc/sys /proc/sysrq-trigger /proc/kcore /proc/latency_stats /proc/timer_list /proc/timer_stats /proc/sched_debug /sys/firmware]
INFO[0008] Initializing source image busybox
INFO[0019] cmd: ENV
INFO[0019] Taking snapshot of files []...
INFO[0019] No files changed in this command, skipping snapshotting.
INFO[0019] No files were changed, appending empty layer to config.
INFO[0019] Pushing image to example.com/foo/bar:latest

ただ、このようにdockerコマンド経由でKanikoを実行することにあまり意味は無い。dockerコマンドが利用できるということは、直接「docker build」コマンドでビルドが実行できる環境だからだ。そこで続いては、dockerコマンドを利用できない環境においてKubernetesクラスタ内でビルドを行う例を紹介する。

Kubernetes上でkanikoを利用してクラスタ内でイメージをビルドする

Kubernetesのクラスタ内でイメージをビルドするには、次のようなマニフェストファイルを用意して実行すれば良い。

apiVersion: v1
kind: Pod
metadata:
  name: kaniko-test  ←適当な名称を指定
  labels:
    app: kaniko-test  ←適当な名称を指定
spec:
  volumes:
    - name: build-context
      hostPath:
        path: <Dockerfileなどが格納されているディレクトリ>
    - name: registory-certs
      hostPath:
        path: <Dockerのconfig.jsonファイルへのパス>
  containers:
    - name: kaniko-test  ←適当な名称を指定
      image: gcr.io/kaniko-project/executor:latest
      volumeMounts:
        - name: build-context
          mountPath: /workspace
        - name: registory-certs
          mountPath: /root/.docker/config.json
      args: ["-c", "/workspace", "-d", "<イメージファイル名>"]
  restartPolicy: Never  ←コンテナ終了後に自動再起動を行わないよう指定

この例ではKubernetesのボリューム機能を使い、コンテナ内にKubernetesを実行している仮想マシン上のディレクトリをマウントさせている。

ちなみにMinikube環境の場合、Minikubeを実行しているマシン(ホスト)の任意のディレクトリをKubernetesクラスタを実行している仮装マシン内にマウントするための「minikube mount」コマンドが用意されており、これを利用してDockerfileなどが含まれたディレクトリをクラスタ内からアクセスできるように設定できる。たとえばユーザーのホームディレクトリをMinikube環境内の「/var/lib/host_home」にマウントするには、次のようにする(図2)。

> minikube mount %HOME%:/var/lib/host_home
図2 ユーザーのホームディレクトリをMinikube環境内にマウントする例

なお、特定の環境ではネットワークアダプタのIPアドレスが取得できずマウントに失敗するという問題が確認されている(Minikubeのバグレポート)。その場合、「ネットワーク接続」内の「VirtualBox Host-Only Network」というネットワークデバイスを「VirtualBox Host-Only Ethernet Adapter」のようにリネームすると正しく動作するようだ(図3)。

図3 「ネットワーク接続」でネットワークデバイスを「VirtualBox Host-Only Ethernet Adapter」のようにリネームする

そのうえで、次のような設定ファイルを用意してホストからコンテナを作成すれば良い(図4)。

apiVersion: v1
kind: Pod
metadata:
  name: kaniko-test
  labels:
    app: kaniko-test
spec:
  volumes:
    - name: build-context
      hostPath:
        path: /var/lib/host_home/<ホスト内のcontextディレクトリ>
    - name: registory-certs
      hostPath:
        path: /var/lib/host_home/.docker/config.json
  containers:
    - name: kaniko-test
      image: gcr.io/kaniko-project/executor:latest
      volumeMounts:
        - name: build-context
          mountPath: /workspace
        - name: registory-certs
          mountPath: /root/.docker/config.json
      args: ["-c", "/workspace", "-d", "<プッシュ先リポジトリ/ユーザー名/イメージ名>"
  restartPolicy: Never
図4 Minikube環境上でDockerのイメージをビルドする例

なお、Minikubeのアドオンとして提供されるDocker RegistryはHTTPSでのアクセスに対応していないため、kanikoを使ってビルドしたイメージを直接アップロードすることはできない。

コンテナ内にディレクトリをマウントさせる代わりにcontextディレクトリの内容をtar.gz形式で圧縮し、Google Cloud Computingのbucketにアップロードして利用するという方法も利用できる。こちらについてはkanikoのドキュメントを参照して欲しい。

シンプルなツールだが使ってみると便利

Kubernetes環境向けのコンテナのテストやデバッグは、コンテナイメージのビルド後にイメージの転送という処理が追加で必要となるため、Docker単体でのテストやデバッグと比べてやや面倒だった。Skaffoldは実行する処理自体はシンプルだが、この作業を自動化できる便利なツールである。

また、Docker/Kubernetesのコンテナ内でコンテナをビルドできるKanikoも小さいツールではあるものの、気が利いたツールだと言える。Dockerコンテナ内でDockerコンテナをビルドするツールや手法はほかにもあるが、最小限の設定で利用できるのは便利だ。どちらのツールもうまく活用すれば、継続的インテグレーションや継続的デプロイを利用する際に役立つだろう。