Buildpackのしくみ

仕組みということで、だんだん中のほうに入っていくんですけれども。フェーズで分けると3つあります。

先ほどcf pushという感じでCLI打ったんですけれども、その段階でソースコード一式がPaaSの上にアップロードされました。アップロードされたソースに対して何をするかと言うと、detectっていう処理をやるんですね。

先ほどデプロイしたときに、「このアプリはNode.jsで書かれていて……」みたいな設定って一切してなかったと思うんですけど、それが何の言語かフレームワークかっていうのを検出する仕組みをBuildpackが持ってます。

detectというところでそれを検出して、その次にcompileというフェーズに移ります。実際は、さっきの例だとNode.jsが動くような諸々の処理を内部でやって、最終的にコンテナイメージが作られます。HerokuであればSlugって言いますし、Cloud FoundryであればDropletという名前なんです。

フェーズの話をしたんですけれども、実装はけっこうシンプルで。単純に実行可能なスクリプトが置いてあるんですね。

detectという名前のスクリプト、releaseという名前のスクリプト、compileという名前のスクリプト。

シンプルなdetectのスクリプト例を持ってきました。PythonのBuildpackなんですけど、bashで書かれていて、requirement.txtっていうファイルがあるかとか、setup.pyっていうPythonファイルがあるかとか、Pipfileがあるかとかを検索しています。

そのあたりをbashで回して、マッチすれば最終的にecho Pythonみたいな文字列を返して、終了コード0になります。マッチしなければここでexit 1って書いてあることからわかるように、このスクリプトが終了コード1を返します。

Buildpackという大きな仕組みで全部をカバーするんじゃなくて、PythonのBuildpack、JavaのBuildpack、PHPのBuildpackみたいにそれぞれに分かれてて、デプロイするプラットフォーム側にインストールされてます。

Cloud FoundryもいろんなBuildpackを内部に持っているんですけれども、cf pushでデプロイすると内部でこれらのBuildpackのdetectというスクリプトが順次実行されていきます。

先ほどの例だとNode.jsだったので……あ、この絵図にはないんですけれども、Node.jsのBuildpackのdetect群が、最終的にexitコード0を返します。それによってプラットフォーム側は、「なるほど、このデプロイされたコードはNode.jsだった」みたいな判断ができるような仕組みになっています。

detectスクリプトはぶっちゃけ、bashでなくてもなんでもいいです。

Rubyで書いてるケースもあれば、Pythonになっているケースもあります。

Compile

そのあとcompileですね。

さっきも話したように、Node.jsを動かすにはいろんなことをしなきゃいけません。Node.js自体のRuntimeをセットアップするという作業もいりますし。npmみたいなパッケージマネージャーのほうから持ってきてインストールするみたいな作業もありますよね。

このあたりはちょっと複雑なので、bashでもぜんぜん書けるんですが、Rubyなどのスクリプト言語を使って書いてるケースが多かったりします。

最後のreleaseというスクリプトはあまり重要じゃないので飛ばします。

というわけで、このあたりのフェーズはおわかりいただけたと思います。

Cloud Native Buildpacksとは何か?

Cloud Foundryの場合、内部でDockerと同じrunCというコンテナランタイムを使った仕組みが動いています。

それで、Buildpackを使ってイメージを作って、Diegoって呼ばれる環境でコンテナを動かすみたいな内部のアーキテクチャになっています。

ということで、ここまでの話で重要なポイントをまとめます。

HerokuとかCloud Foundryは、開発者が楽できるような仕組みを提供してくれています。

Buildpackのdetectという仕組みがよくできていて、これによって「どの言語で」みたいに指定しなくてもできるようになっています。compileというフェーズで、最終的にはコンテナイメージをいい感じで作ってくれて、内部で使われるという仕組みになっています。

これらの仕組みを考えると、もしかしたら「あれ?」って思う方もいらっしゃるかもしれません。これでみなさんがDockerとかで使ってるDocker Image、OCIのイメージ作ればいいんじゃね? みたいに思うじゃないですか。

そういう発想で出てきたのが、このCloud Native Buildpacksという仕組みです。

去年の1月ごろから僕のところのPivotalとHerokuと共同で始まったプロジェクトが、Cloud Native Buildpacksプロジェクトというものです。

Cloud FoundryとHeroku、もともとは同じものから出発しているんですけれども。ちょっと仕様が分裂してるところもあったので、仕様を統一してCloud Native BuildpacksによってOCI準拠のイメージを作るような仕組みになりました。

去年の10月にCNCF Sandboxプロジェクト入りもしてまして。今後、CNCFの下で開発が進められていくということになります。

ライブデモ

というわけでデモしていきたいと思います。

これからお見せするのは、さっきのデモ動画にもあったNode.jsが入ったディレクトリです。

見ていただいたらわかるように、ここにDockerfileみたいなイメージを作る仕組は、入っていません。

じゃあどうするか? ここでpackというコマンドがあります。pack build、ここで作りたいイメージ名、testappとかしますけど。

こういったコマンドを打って実行すると、自動的に内部で処理が走って。今は「Node.jsアプリです」とか指定してないですよね。でも自動的にNode.jsということを判断して、「Node.js Buildpackですね。PaaS」みたいな。

内部でいろんな解析をやって、時間を短くしたかったのであえて内部でキャッシュを作ってきてるんですけども(笑)。キャッシュにヒットしたらそれを使って。ほら、できました。もうこれだけで終わりです。

dockerimagesコマンドをやってみると……見えます?

まさにイメージできてる。動かしてみましょうか? docker run。こんな感じで、そのイメージを使って動かしてみますね。

動いたようです。アクセスしてみましょう。ローカルホストの4000番。ほら動いてる。ね? Dockerfileなんていらなかったんだ……と。

(会場笑)

このpackコマンド、例えばオプションで-publishとか付けると、これだけで自動的にリポジトリにpushしてくれたりもします。

速いほうなんですけどね。こういったライブデモで待つ時間は、けっこうドキドキしますよね。ネットが止まったらどうしようとか。ちゃんといくよね? あ、終わりましたね。

DockerHubにアクセスしてみましょうか。家を出る前に念のためにリハーサルやってきて、1時間前とかなってるんですけども。ちょっとリロードしてみますね。ほらfew seconds agoとか。実際にDockerHubへのpushまで、さっきのコマンド一発で終わってます。

アプリ開発なので、イメージを1回作って終わりっていうことはないでしょう。どんどんアップデートしていかなきゃいけませんと。例えば今回は、例としてわかりやすいように、デモアプリにCloud Foundryのロゴがいるんですけれども。もはやBuildpackはCloud Foundryのものだけではないので、個人的には複雑なんですけれども。ちょっとこれを消してみましょうか。

保存して、もう1回pack buildコマンドを叩いてみます。すると、内部的にキャッシュが効いてるので比較的速いです。Cloud FoundryとかHerokuをやってる方は、古い規格のBuildpackは一から全部作ってたので、けっこう時間がかかってたんですけれども。

Cloud Native Buildpacksになっていい感じにキャッシュが効くようになったので、けっこう速いです。なんかごにょごにょやっていて、writing imageとか出ていて……終わりましたね。

動かしてみましょうか。docker runします。はい。動かして、リロードすると……ほら! Cloud Foundryのロゴが消えたというデモになります。もう「Dockerfileなんかいらんかったんや」って感じですね。

従来のBuildpackとの違い

これまでの仕様はオープンだったんですけれども、CNCF入りするくらいなので、よりしっかりと定められました。

それは、GitHub上から確認できます。さっきも話したんですけれども、重複してた仕様が1つに入って、内容的にはBuildpack API v3みたいな扱いになっています。

さっきあったdetectを実行してcompileを実行してみたいなフェーズも仕様として定められていて、Lifecycleと言うんですけれども。

そのLifecycleのあたりのリファレンス実装なんかも、実際にGitHubにあげられて開発が進んでいます。

さっき叩いたpackコマンド、これは利用方法の1つにすぎません。

packコマンドには、今も紹介したLifecycleのリファレンス実装が、そのまま内部的には使われています。

Lifecycleなんかを実装した大きな箱のことを、プラットフォームと呼んでいまして。対応するプラットフォームとしては、何度も言っているCloud FoundryとHerokuというものが入っています。

それだけじゃなくて、Knative。

ご存じの方もいらっしゃるかもしれないんですけれども。Knativeという仕組みが今どんどん作られていっていて。それも対応プラットフォームとして挙げられています。

ということでまとめに近いんですけれども、Cloud Native Buildpackによってもたらされるものとして「開発者の生産性の向上」。さっきやったような面倒くさいイメージを作るという話がないので、開発者から見ると生産性が向上するでしょう。このあたりは、PaaSで昔から言ってた文脈と一緒です。

「一貫した操作でデプロイできる」ってあるんですけれども。JavaであってもPythonであってもどんな言語であっても同じやり方で、cf pushとかpack buildというコマンドを打つだけで動く状態まで持っていけると。これはこれで、非常に便利な点じゃないかなと思っています。これは開発者側からの視点です。

運用におけるメリット

ここはNoOps Meetupということで運用側の方々も多いんじゃないかなと思います。ということで、運用側から見た視点でのメリットを「持続可能な運用」という表現で書いてみています。Opsのガードレールのもとで開発者の生産性を高めるとか、スケーラブルなセキュリティみたいな書き方をしています。

ただ、もしかするとちょっとピンとこないかもしれません。ちょっとこのあたりを深掘りしていきます。

例えばさっきもあったこの絵が出るんですけれども、こういったbuildしてpushしてapplyするみたいなのを手動とかCIツールでやるというのが、コンテナを使った開発運用の流れですよね。これはこれで面倒なポイントはあれど、まあ機能しているとは思います。実際、こうされている方は非常に多いと思います。

ただ、運用という意味では、デプロイだけではいけないんですね。例えばこんな感じでpython:3.5.2のベースイメージを作って……みたいなDockerfileを書かれると思うんですけれども。

例えばこのベースイメージに脆弱性があったらどうしましょう。ここでapt-getしている例とか書いてますけれども。apt-getでインストールしたパッケージに脆弱性があったらどうしましょう?

どうするかと言うと、ベースイメージを書き換えてもう1度buildすると、apt-getが再度流れてきて更新はされるわけなんですけれども。

じゃあそれ、誰がやるの? と。

開発者にやらせると、開発者からすると開発で忙しいのに常にPythonの脆弱性がどうこうみたいな情報を気にしなくちゃいけないですし。脆弱性情報って、基本的に突然降って湧いてくるものが多い。

なので、開発している手を止めて脆弱性対応というのをやっていかなきゃいけませんと。これはこれで、開発生産性を下げる一因になります。

まだ、やってくれるならいいんですよ。「面倒だからやらない」とか「わかんないからやらない」みたいになってしまうのが、一番怖くて。結局後回しになって放置された場合って、ちょっと想像したくないですよね。

じゃあDockerfileをOpsが管理するかたちにすればいいかと言うと、それもそうじゃなくて。そもそも開発チームで開発した後、DockerBuildしてねみたいな感じでOpsチームにいちいち連絡するのも、バカな話です。

Opsから見ても、開発チームがどんどん増えていったら、もはや回らなくなって組織のスケーラビリティの問題が出てきます。さらにコミュニケーションコストも発生するので、これはこれでやってられないです。

Cloud Native Buildpacksでのワークフロー

どっちの手段を取っても、やっぱり効率って落ちちゃうんですよね。じゃあCloud Native Buildpacksを使ってやるとどうなるかと言うと、使うBuildpack自体は、Opsチームが管理すればいいんです。

開発チームは、それを使ってイメージ作ってね、みたいな規約にしておく。例えば脆弱性が見つかった場合どうするかと言うと、Opsチームがやるのは最新のBuildpackに更新することですと。そのあと自動でもいいと思いますし、手動でもいいとは思うんですけれども、Buildpackを再適用すれば、そのコンテナイメージはセキュアなかたちに変わりますよね。

こういった役割分担にすればスケーラブルで、かつセキュアな開発運用サイクルが回せるということになるんじゃないかなと思います。

デプロイも、今までのPaaSと同じでcf pushみたいなやり方ができますし、Knativeを使っても同じようにkubectl applyみたいなコマンドを使って、プラットフォームにデプロイできるようになります。

現状ですけれども、Cloud Native Buildpackはまだまだこれからという状態になっていまして。

Node.jsとかJavaのBuildpackはリリースされているんですけれども、そのほかの言語に関してはこれから期待してねっていうステータスです。

あとはKnativeという話もあったんですけど、サーバレス向けのコンテナイメージ作成にも、今後使われていくという話になっています。

最後のまとめです。

PaaSのノウハウがぎゅっと詰まっているのがBuildpackです。そのノウハウをコンテナ界隈にも広げて、アプリの開発と運用を楽にしてくれるのがCloud Native Buildpackです。

イメージを作成するだけだったら1回Dockerfieを作って終わりかもしれないんですけれども、それだけじゃなくてLifecycleまで意識して技術選定をやっていったらいいんじゃないかなと思います。

情報は、あとでまとめておきます。以上になります。ありがとうございました。

(会場拍手)