Cloud Native Buildpacks(CNB)のビルド作業の2つのフェーズ

伊藤裕一氏(以下、伊藤):このCNBによるビルド作業は、大まかに2つのフェーズがあります。1つはソースコードがあって、そのソースコードをBuildpackでビルドします。このBuildpackはpackコマンドで、CNBが使えます。pack builder suggestとやると、サジェストされるBuildpackの一覧が出てきます。

これで指定したBuildpackでソースコードを判定して、JavaならJavaのビルド方法、PythonならPythonのビルド方法でイメージを作って、ベースOSはUbuntuなどですが、作成したアプリのイメージとガッチャンコして、Dockerのイメージを作るかたちでコンテナイメージが作成されています。

このスタックですが、基本的にBuildpackの中で定義されていて、あまり自分でいじることはなく、Buildpackに応じて勝手に選ばれる使い方がほとんどです。このCNBの中でどういう感じでビルドが行われているかですが、大きく分けると4つのフェーズに分かれています。

Cloud Native Buildpacks内のビルドの4つのフェーズ

まず1つはBuildpackの中にある定義なんです。どの言語を使っているのかを検知してくれる仕組みがあり、これでどのビルド方法を使うかが判定できます。次にAnalysisです。Dockerfileはキャッシュの仕組みがあります。2度目のビルドでは1回目のビルドで得た結果を再利用することがあると思いますが、CNBでも同じことをしています。

それをするために、前回ビルドしたものと今回のビルドしたものとを照らし合わせて、必要なところだけを次のBuildフェーズで実施します。ビルドを実施したら最終的に作成された差分レイヤーを組み合わせることで、コンテナのイメージが完成するような一連の流れになっています。

この内部的な動きですが、CNBを使うことにおいてはあまり気にする必要はありません。詳細が必要な人は、Buildpack Author's GuideやBuildpack Interface Specificationなどのドキュメントを読んでもらえれば、より細かいことがわかるかと思います。

Detectionですが、先ほどGoogleのBuildpackを作成したときにサポートされる言語として、JavaやGo、Pythonなどがありました。これらを順番にチェックしていきます。最初にAPMとありますが、この言語かどうか、Yes・No。この言語か、Yes・No。この言語が、Yes・Noとやって、Yesと判断されたものでビルドする仕組みになっています。

この仕組み、要は判断する基準に自分の書くソースコードが当てはまっている必要があります。そのため、例えばJavaScriptのNode.jsのコードを書く場合であっても、ただ単に何か1個〇〇.jsというファイルを適当にポンと1個置くだけではなく、Node.jsに沿った書き方で、例えば「こういうライブラリがありますよ」といった情報を置いてあげないとビルドがちゃんとされません。フィットするようにプログラムを書いてあげる必要があります。

そんなに難しいものではないので、最初にHello,World的なものでCNBがちゃんとdetectできることが判断できたら、それをベースに本番用のアプリケーションを書くなどをしてもらえればいいんじゃないかなと思います。

次にAnalysisです。これもDockerfileの差分キャッシュと同じような感じで、前回キャッシュしたものと今回ビルドをする際に変更が発生している箇所を照らし合わせて、それで何をビルドするのかを判断するためのものです。その判断はメタ情報を起用しています。

次のBuildのフェーズですが、これはDockerfile方式の差分の積み上げ方式と若干異なるかたちでビルドをしています。そのため、例えばDockerfileでもう1回たくさんビルドしないといけないシナリオの場合でも、CNBであれば、そこがさほど発生しないという、より短縮できるシナリオがあります。他にも、たくさんのノードでビルドする際に、他のノードが作ったキャッシュを参照する機能もあります。

最後のExportのフェーズです。ここはビルドや前回のキャッシュから取ってきたイメージを、全部ガチャンコしてコンテナイメージを作成するフェーズですが、次にビルドする際に使うためのメタデータを取っておくような作業も実施しています。

こういったことを行うことで、CNBが使えます。導入は非常に簡単で、CNBを動かしたい環境にDockerをインストールして、あとはpackコマンドで下のドキュメントどおりにインストールすれば終了です。

kpackについて

次にkpackについて話します。kpackは、CNBをKubernetes上で動かす仕組みです。CNBは、例えばソースコードを自分のいる場所に移動して、それでビルドコマンドを走らせる。最終的にイメージを使おうと思ったらレジストリに自分でPushする必要はあります。kpackは、そのコードのPullとPushを全部Kubernetes上で一緒に実現します。

それから自分でどうビルドするのかをコマンドで指定するのではなく、YAMLで定義をする。そうすることで、管理者の人がたくさんの環境で管理をする際に、全部の環境に対してよりガバナンスを利かせやすいソフトウェアになっています。

他にもCNBであればBuildpackの中で使うOSを指定されますが、kpackであれば自分でより自由に選択できます。CNBに比べて発行するコマンドの数は少なくなるので、CIの作業を自動化させやすいといった強みもあります。

kpackの使い方デモ

これもデモをお見せします。今、/minikubeの上にいる状態です。ここで最初にdocker image pullしていますが、このイメージはまだリポジトリにないので、failしています。このイメージをkpackで作成するためにKubernetes的にkpackのコンテナイメージの定義を行って、イメージの名前とイメージのリポジトリとリポジトリに接続するクレデンシャル。

CNBの定義が書かれているBuilderと、ソースコードをどこから取ってくるかのGitの指定です。これを指定して、リソースをkubectl applyで適用すると、このイメージリソースが作成されます。

kpackがKubernetes上でシステムのサービスとして動いているので、イメージリソースが作成されるとビルドするためのPodを立ち上げて、そのPodの中でビルド作業が実施されています。今はこのPodの中のログを表示していますが、CNBの際のログとほとんど同じで、コードをdetectしてアナライズして、ビルドして最終的にエクスポートしています。

これでコンテナのレジストリに登録されます。今回はDockerHubに登録しているので、kpackを動かす前はPullの作業は失敗していましたが、kpackがイメージをPushし終わっているので、Pullに成功している。そのPullしたイメージを今Dockerとして起動します。

これはKubernetesで起動してもいいです。linksで展開したminikube上のDockerのサービス、コンテナに対して接続すると、レスポンスがちゃんと返ってきたので、kpackがイメージをGitから取ってきてビルドしてDockerHubにPushしていることが確認できました。

kpackがビルドを実施する流れ

このkpackがビルドを実施する流れです。最初にkpackの管理系を作成する必要があります。これはインストールの手順に書いてあるものですが、この管理系の導入をKubernetes上で行うと、kpackのリソースが作成されるかどうか、存在するかどうかを監視している。

イメージのリソースが作成されると、そのイメージのリソースに定義されているとおりにビルドを実施する、一時的なPodをデプロイメントで作成します。そのPodがGitで指定したリポジトリからPullしてきて、Pullしてきたソースコードに対してCNBを適用して、ビルドを実施する。

コードの判定は、最終的にイメージが作成されたら、指定したDockerのコンテナイメージのレジストリに対してPushを行う流れになります。

このkpackは内部的にKubernetesのリソースをいくつか使っています。使っているリソースは、「CNBで何を使っているのか」を理解していれば非常にわかりやすい、そのままなリソースになります。最初に使っているリソースとしては、Buildpackを今まで指定していたんですけど、そのBuildpackを指定するためのStoreという定義とリソース。

あとは作成したいアプリケーションを動かすためのベースOSのイメージとして、Stackで定義を行います。この2つを組み合わせてBuilderの定義を作成し、そのBuilderの定義を使ってイメージをビルドするためのGitやBuilderの指定、どこにPushするのかを指定します。

それらをするためには、当然GitHubやレジストリにアクセスするクレデンシャル情報が必要になるので、クレデンシャル情報を含んでいるSecretを定義して、Builderやイメージの中で使うかたちになっています。このイメージの定義を作成したら、先ほどのkpackのビルドをするためのPodが作成されて、手順どおりにPull、ビルド、Pushが行われるかたちです。

kpackの導入手順

kpackの導入手順はそれほど難しくなく、下にドキュメントがありますが、これに沿って導入するだけです。今回みたいにDockerHubを使ってもらってもいいですが、PushやPullという作業が頻繁に発生するので、プライベートのレジストリを構築したほうが速度的にいいんじゃないのかなと思います。

ここらへんは当たり前の話ですが、作成したあとにKubernetesのクラスタを用意したら、ページからダウンロードできるYAMLを適用すると、kpackの管理系が作られるサービスが動き始めます。それが動き始めたあとでレジストリにアクセスするためのSecretを作成したり、StackとStore、Builderを定義する。

準備が完了したので、あとはイメージのリソースを作成すると、そのイメージのリソースに定義されたとおりにビルドするかたちになります。たくさんビルドする場合は、恐らくイメージのリソースは何個も作ったり更新したりを繰り返しますが、ここのSecretやStack、Store、Builderは1回定義したものは何回でも使い回しができるので、最初に1回作るとき、あとは定義を更新したい場合に変更するだけになります。

Cloud Native BuildpacksやkpackでCI構築をする方法

ここからは、CNBやkpackを使って、CIを構築する例の話をします。細かい手順は書きません。例えばCNBを使うと、最初にCNBを使うためのLinuxのホスト。これはDockerが動いているものを用意して、まずアプリケーションのソースコードをGitのブランチにPushする。

CIサーバーがGitブランチを監視してもいいし、GitのブランチがCIサーバーに対して「Pushされたよ」と通知を行ってもいいですが、Pushされたことを起因にして、CIサーバーが起動を開始します。

最初のステップとしてソースコードをビルドしないといけないので、コードをGitのブランチからPullしてきて、Pullされたコードに対してpackコマンドでビルドを実施する。ビルドができるとイメージが作成されるので、その作成されたイメージをレジストリに対してdocker pushコマンドで送り込む一連の流れをCI/CDで実施できます。

次にkpackの例。これもGitのブランチにPushされたら全部がkickされるところまでは同じですが、このKubernetesとkpackの仕組みを使ってGitのブランチからPull、あとはコンテナレジストリへのPushもできます。CI/CDサーバーがやる役割としては、コマンドをたくさん発行するのではなく、その一連の作業をkickするためのイメージのYAMLを更新して、applyする。それだけで一連の作業が開始されます。

CNBやkpackを使うと、最初にお話したDockerfileによるビルドの問題などを回避できますが、それを実際に使う上で、サポートなどがほしいというようなシナリオ。kpackをもう少し便利に使いたい場合には、弊社のVMwareの製品として、Tanzu Build Serviceというものが提供されているので、その導入を検討してもらえれば幸いです。

以上で、CNBとkpackに関する説明を終えたいと思います。ご清聴ありがとうございました。