KubernetesでDockerイメージをビルドする
Vitalis Ogbonna
2022年5月3日
0 分で読めますKubernetes上でCI/CDプラットフォームを運用するエンジニアが増えています。この方法なら、自動化によって時間を節約し、一貫性のあるデプロイを実現できるうえ、マイクロサービスの管理や監視も容易になります。ただし、Kubernetesクラスターでコンテナイメージをビルドするには、回避策が必要となる技術的な課題があります。
この記事では、CI/CDプロセス向けにKubernetesクラスターでDockerイメージをビルドする方法をいくつかご紹介します。また、各方法を使うメリットとデメリットについても解説します。
KubernetesでDockerイメージをビルドするツール
コンテナイメージをビルドしたことがある方なら、docker buildのようなコマンドを実行したことがあるでしょう。そのプロセスを自動化する段階では、CI/CDツールでdocker buildを実行するスクリプトを作成したかもしれません。シンプルなCIサーバーではこれで問題ありませんが、いずれKubernetesベースのCIプラットフォームを導入したくなるでしょう。
課題は、KubernetesクラスターからDockerデーモンに自由にアクセスできないことです。そのため、アプリケーションの整合性やインフラストラクチャの安全性を損なわないようにしながら、代替手段を使う必要があります。
利用できるツールはいくつかあります。
Buildahは、Open Container Initiative(OCI)イメージのビルドに特化しています。Dockerfileの各コマンドに相当するコマンドを備えています。root権限を必要とせず、Dockerfileを使う場合も使わない場合もイメージをビルドできます。
imgは、単体で動作する、デーモン不要かつ非特権のDockerfileおよびOCI互換コンテナイメージビルダーです。キャッシュ効率に優れ、イメージビルダーとして内部でBuildKitのDAGソルバーを使用するため、複数のビルドステージを並行して実行できます。
kanikoは、コンテナまたはKubernetesクラスター内で、Dockerfileからコンテナイメージをビルドします。Dockerデーモンに依存せず、Dockerfileの各コマンドを完全にユーザー空間で実行します。
Docker in Dockerは、Docker内でDockerを実行するための手法です。Dockerコンテナに
/var/run/docker.sockファイルをボリュームとしてマウントし、KubernetesクラスターでDockerコンテナをビルドします。Sysbox Enterprise Edition(Sysbox-EE)はNestyboxが提供する製品で、rootlessコンテナ上でDocker、systemd、Kubernetesなどのワークロードを仮想マシンのように実行できます。
BuildKit CLIは、Kubernetesクラスター内で単一アーキテクチャおよびマルチアーキテクチャのOCIイメージとDockerイメージをビルドします。docker buildコマンドをkubectl buildに置き換えて、Kubernetesクラスター内でイメージを作成します。
Javaコンテナ向けのJibは、DockerfileやDockerのインストールを必要とせずにコンテナイメージをビルドします。Maven用とGradle用のJibプラグインが用意されています。また、Javaライブラリとしても利用できます。
KOは、Goアプリケーション向けの高速でシンプルなコンテナイメージビルダーです。ローカルマシンでgo buildを実行することでイメージをビルドするため、Dockerをインストールする必要はありません。
ここでは、KubernetesクラスターでDockerイメージをビルドする代表的な2つの方法、Docker in Dockerとkanikoに焦点を当てます。
Docker in Docker
Docker in Docker(DIND)方式は、コードのビルドが成功した後にイメージをビルドしてプッシュするCI/CDパイプラインで広く使われています。また、Jenkinsをデプロイパイプラインに組み込む際(たとえば、サンドボックス環境でのテスト時)にも使われます。
DIND方式は便利で簡単に見えるかもしれません。しかし、Dockerの元従業員でDINDの開発にも携わったJérôme Petazzoni氏は、Dockerが社内プロセスを加速する目的でこの方式を作ったと述べ、本番環境での利用を避けるべき理由としてセキュリティ上の懸念を示唆しています。
Dockerはもともと、DIND方式でコンテナを特権モードで実行するように設計しました。Dockerデーモンはrootとして動作するため、コンテナもホスト上でrootとして実行されます。Dockerソケットにアクセスできる人は誰でもroot権限を持ち、あらゆるソフトウェアの実行、新しいユーザーの作成、コンテナに接続されたあらゆるものへのアクセスが可能になります。そのため、コンテナはアーキテクチャ全体に広がる可能性のある攻撃に対して脆弱になります。
さらに、KubernetesではDockershimのサポートが正式に削除されたため、すべてのKubernetesノードにDockerを追加しない限り、ホストへのdocker.sockのマウントは今後機能しなくなる可能性があります。AWSには、クラスター内でのDockerソケットの使用を検出する便利なツールがあります。
DIND方式には、ストレージドライバーとの互換性に関する問題もあります。また、ビルドを開始するたびにDockerイメージをプルする必要があるため、イメージキャッシュの管理も困難です。
こうした問題を解決する方法の1つは、イメージのビルドにコンテナランタイムを必要としないツールを使うことです。その一例が、KubernetesクラスターでDockerイメージをビルドするためのGoogleのオープンソースソリューション、kanikoです。
kaniko
kanikoは、コンテナまたはKubernetesクラスター内でDockerfileからコンテナイメージをビルドします。Dockerデーモンに依存せず、Dockerfileの各コマンドを完全にユーザー空間で実行します。そのため、標準的なKubernetesクラスターなど、Dockerデーモンを簡単かつ安全に実行できない環境でもコンテナイメージをビルドできます。
ここでは、kanikoのワークフローを説明するために、無料で一般公開されているツールを使います。このチュートリアルを進めるには、次のものが必要です。
Kubernetesを有効にしたDocker DesktopをPCにインストール
kanikoポッドが認証してDockerイメージをプッシュするための、有効なDocker Hubアカウント
kanikoがDockerfileにアクセスするためのGitHubアカウント
kanikoの仕組みを説明するために、GitHub上のこのサンプルプロジェクトを使います。git clone https://github.com/agavitalis/kaniko-kubernetes.gitを実行してクローンし、一緒に進めてください。
このサンプルプロジェクトには、2つのファイルとREADME.mdファイルがあります。
#sample project directory
kaniko-build-demo
dockerfilepod.ymlREADME.md
dockerfileには、イメージのビルドコマンドとして次のコードが記述されています。
kanikoの設定コードでは、kanikoイメージエグゼキューターに最新バージョンを指定しています。また、Dockerfileとイメージリポジトリの場所、およびKubernetesにあるイメージレジストリの認証情報名を指定しています。
kanikoの仕組み
kanikoの仕組みは非常にシンプルです。kanikoエグゼキューターイメージgcr.io/kaniko-project/executor:latestがDockerfileのコマンドを実行してイメージをビルドします。指定されたDockerfileを読み取り、FROMコマンドで定義されたベースイメージ(ここではubuntu)を指定されたコンテナのファイルシステムにプルし、その後、イメージをレジストリにプッシュします。
FROMディレクティブからベースイメージをプルした後、kanikoはDockerfileの各コマンドを個別に実行し、実行のたびにユーザー空間のスナップショットを取得します。その後、実行ごとにスナップショットのレイヤーをベースレイヤーに追加します。
これらの設定はpod.ymlファイルで指定します。
context:Dockerfileの場所です。この例では、リポジトリのルートディレクトリにDockerfileがあります。プライベートGitリポジトリで認証するには、GIT_USERNAMEとGIT_PASSWORD(APIトークン)変数を使います。destination:<dockerhub-username>を自分のユーザー名に置き換えると、kanikoがDocker Hubレジストリにイメージをプッシュします。docker-file:contextからの相対パスで指定するDockerfileの場所です。
kanikoの動作原理がわかったところで、Kubernetes Secretを作成しましょう。次に、それを使ってイメージをビルドしてデプロイします。
Docker Hub用のKubernetes Secretを作成する
kanikoがDocker Hubにアクセスできるように、Kubernetes Secretを作成する必要があります。そのために、次の情報が必要です。
docker-server:イメージをホストするDockerレジストリサーバーです。Docker Hubを使う場合は、値をhttps://index.docker.io/v1/にします。docker-username:Dockerレジストリのユーザー名です。docker-password:Dockerレジストリのパスワードです。docker-email:Dockerレジストリに登録されているメールアドレスです。
各変数を適切な値に置き換えて、次のコマンドを実行します。
上記のコマンドは、このSecretをkanikoポッドにマウントします。ビルドしたイメージをDockerレジストリにプッシュするとき、簡単に認証できるようになります。次のような確認メッセージが表示されます。

Dockerイメージをビルドするためにkanikoポッドをデプロイする
それでは、Kubernetesクラスターでポッドを起動してビルドを開始しましょう。次のコマンドでポッドをデプロイします。

これによりイメージのビルドが始まり、指定したDockerレジストリにイメージがプッシュされます。
次のコマンドで、Kubernetesクラスター内の利用可能なポッドを一覧表示できます。

利用可能なポッドとそのステータス、経過時間が表示されます。(kanikoで誤った認証情報を使っている場合や、誤ったリポジトリにプッシュしようとしている場合は、エラーが表示されることがあります。)
既存のポッドを削除する場合は、kubectl delete pod <pod-name>コマンドを使います。
デプロイしたポッドの詳細を確認するには、次のコマンドを使います。

次のコマンドでビルドログを確認することもできます。

クラスターを調査、トラブルシューティングし、詳細情報を取得するためのコマンドについては、kubectlチートシートをご覧ください。
次にDocker Hubにアクセスして、処理が正常に完了し、イメージがDockerに正しくデプロイされたことを確認します。

kanikoを使って、KubernetesクラスターからDockerイメージを正常にビルドしてデプロイできました。
コンテナセキュリティを実現するSnyk
kanikoを使えば、KubernetesクラスターでDockerイメージを安全にビルドできます。定義したビルドコンテキストからDockerfileを取得してイメージを作成し、その結果をイメージレジストリにプッシュします。強力なキャッシュシステムにより、イメージをより速く作成できます。
KubernetesでDockerイメージを作成する際に安全でない方法を使っていなくても、Kubernetesのセキュリティ脆弱性のリスクは残ります。Snyk Containerは、クラウドネイティブアプリケーションの脆弱性を検出して修正する、信頼性の高いコンテナセキュリティソリューションです。SnykはDocker、GitHub、Kubernetes、Jenkinsなどのツールとシームレスに連携し、アプリケーションとインフラストラクチャの安全を守ります。
開発者ファーストのコンテナセキュリティ
Snykは、コンテナイメージとKubernetesワークロードの脆弱性を検出し、自動的に修正します。
