コンテナ作成におけるInnerLoop、OuterLoopの考えかたの適用

星野真知氏:(スライドを示して)さて、これを紹介するまでが私のセッションですが、とはいえちょっとはテクニカルなことを話したほうがいいと思うので、この中でビルドクラスタを紹介したいと思います。

これは何かというと、InnerLoopとOuterLoopをつなぐ、ある意味門番の役割をするものだと思っています。このビルドの考えかたを持っている方は、けっこう少ないと思っています。何が特に特徴かというと、まず所有者が基本的にインフラ部隊ということです。

目的は何かというと、最終的にOuterLoopにやってくる開発物の品質を均一化することです。パッチなどの問題があった時も、開発の方々を巻き込むことなく、この世界で完結してパッチの適用などができます。こういうものを作ろうというのが、ビルドクラスタの目的です。

このビルドクラスタの中にも、いろいろな技術要素は説明できると思いますが、今日はその中でも1個だけ、コンテナを作るところに着目をして話を取り上げたいと思います。

(スライドを示して)これは、弊社の人間が「Javaでコンテナを作る時にどうしているか?」をアンケートしたものです。その他を抜いた数字が右にありますが、ソースコードからコンテナを作る時に、Dockerfileを使っている方が大多数と思っています。

ところが、先ほどのInnerLoop、OuterLoopの考えかたを適用する最初のステップとして、我々VMwareは、マストとは言いませんが、Dockerfileはなるべくやめるべきだと思っています。

Dockerfileをやめるべき3つのポイント

なぜか。まずDockerfileがどういうものなのかを左に例を書いていますが、まず、いくらでもエンジニアたちが好きなことを書けてしまうので、属人化します。(スライドを示して)特徴的なのが、この例の「ENTRYPOINT」の2個目の「Xmx」です。Javaの開発を見たことがある方はみんな知っているヒープサイズを2GBと入れていますが、これを何と入れるかは、開発者によってぜんぜん違うと思っています。

それ以外にもいろいろなオプションがあるので、これがどのように入っているかは、完全に開発者依存になってしまうというところです。

もう1個の問題は、ブラックボックスになってしまうところです。でき上がったイメージがどういうDockerfileで作られたかは、DockerfileをGitHubに置いている方はいいのかもしれませんが、普通その手段はなくなってきています。

もう1個の問題です。「FROM」のところにopenjdk:17-jdk-alpineと書いてあって、Javaの17が使われているのがわかりますが、いつのJavaの17なのかがわからないというところです。

コンテナのFROMのところは、基本的にミュータブル、変更可能であるのが問題になってきて、Java17は非常に長いライフサイクルを持っていますが、どのマイナーパッチが当たっているのか、1年前のものなのか、いやいや1週間前のものなのかが、これだけでは絶対に判断できないブラックボックスになってしまうところです。

もう1個の問題も何度か出てきますが、独占という問題があります。例えば、でき上がったコンテナに脆弱性ができたとしましょう。脆弱性をパッチする時に、毎回このDockerfileを作った人を呼び戻して、ビルドパイプラインを流すように指示しなくてはいけなくなってきてしまいます。テストはどうするんでしょうね。

テストも、「基本的にはインフラ起因でパッチを当ててくれ」という依頼にもかかわらず、ビルドもテストもアプリの方々にお願いし直さなければいけない問題が発生します。

こういうのが、このDockerfileにまつわる問題だと思っています。

Cloud Native Buildpacksのメリット

(スライドを示して)InnerLoop、OuterLoopの考えかた、特にこのコンテナを使った開発をしていく上で、まず非常に検討してほしいと思っているのが、Cloud Native Buildpacksと呼ばれているものです。

先ほどのDockerfileとの対比になるものですが、特徴は何かというと、まずDockerfileを使っていないということです。ソースやコンパイルしたものを投げつけると、ベストプラクティスという言葉は間違っていますが、標準イメージをopinionatedな方法でコンテナを作ってくれます。

後ほどちょっと触れますが、BOMによるホワイトボックス化や、Rebaseによる外部パッチ適用にも対応できる特徴を持っています。

このあたりを、もう少し細かく見ていきましょう。

(スライドを示して)Cloud Native Buildpacksは、今、実際にCloud Native Buildpacksのページに比較表があります。まずこの中のRebasingを取り上げたいと思います。

(スライドを示して)先ほどのDockerfileのパターン、もう少し細かく言うと、例えばDockerfileの中にFROMやJavaのレイヤー、アプリのレイヤーがあると思いますが、ベースイメージ側に脆弱性があってパッチしましょうという時に、どうしなければいけないか。

Dockerfileは、レイヤーが下位になればなるほど、そこの下位の変更をする時は、上位も全部ビルドし直す動きを基本的にしなければいけません。

そのため、ベースイメージの更新とともに、Javaの再更新、アプリケーション、もしかしたら再コンパイルも走らせなければいけないので、コンテナを全部再ビルドしなくてはいけません。

Cloud Native Buildpacksで作ると何がいいかというと、コンテナはちゃんと作る。レイヤーを使って、ファイルシステムが分かれるようになっています。各ファイルシステムに独立性が保証されています。Cloud Native Buildpacksは、ファイルシステムのみを差し替えられるようになっています。

ベースイメージに脆弱性が出たら、そのベースイメージのレイヤーだけを差し替える(ことができます)。ファイルシステムとして差し替えられるようになっていて、上位のレイヤーを触らないことができます。再コンパイルなどは走りません。

同じバイナリで、しかもJavaのランタイム系だったら、ランタイムがその違いを吸収してくれます。これは「絶対にテストしなくていいのか」と言われると、私も難しいです。お客さまごとに判断は変わってくるとは思います。

少なくとも、テクノロジーとしてなにか脆弱性があった場合に、インフラ起因でそこだけをパッチしていく技術が取れます。これはCloud Native Buildpacksの1個大きな強みだと思っています。

Bills of Material

(スライドを示して)もう1つ、「Bills of Material」という話をしたいと思います。

最近のバージョンだと説明が変わってきますが、旧バージョン方式で説明します。

Dockerfileで作ったイメージに、一応メタ情報というものがありますが、スライドのように、メタ情報はあまり大したことが書いていません。これが1つ、先ほど言ったブラックボックス(のこと)ですが、メタ情報以外のものがないので、この中で使われているバイナリのバージョンを見ようと思っても、直接ログインして、最悪の場合、findコマンドで探しに行くことが必要になってきます。

Cloud Native Buildpacksの大きな特徴は、ビルド時に、例えばOSSのライブラリ、ベースイメージで使われているUbuntuであればUbuntuのライブラリのバージョンを、メタ情報に残すことをやってくれます。

これがコマンドでできます。例えばpackというコマンドを使うと、実際のコンテナの中に入らずとも、そのコンテナの中にどういうライブラリ、どういうソフトウェアが、どういうバージョンが使われているかの一覧を取得できるようになっています。

脆弱性のイメージスキャニングは、普通大きくやっていくとすべてのファイルシステム総スキャンをするパターンが多いと思っていますが、BOMであれば非常に低負荷かつ高速でスキャニングできます。

インフラとしてうれしいのは何かというと、今後アセットがどんどん増えていく中で、管理台帳を作らなくていいわけです。私はあるお客さまで、コンテナ開発をこれからしていくものの、インフラ担当者がExcelで管理台帳をつけ始めてしまっているのを見ています。

Cloud Native Buildpacksを加えることによって、やってきた開発物たちがどのようなものを持っているのかを自動的に取得できる仕組みが取れるのも、大きな特徴です。

InnerLoopとOuterLoopの考えかたへの照らし合わせ

これを先ほどのInnerLoopとOuterLoopの考えかたに照らし合わせましょう。(スライドを示して)この絵は若干複雑にはなっていますが、見てほしいところは何かというと、InnerLoop、OuterLoopの登場人物は開発者と言っていますが、おそらくお客さんの多くは、開発者にもいろんなタイプがあると思っています。

(タイプの1つは)まず、フル内製化しているチームです。内製化しているチームの方々は、一番左の開発クラスタを使いながら、InnerLoopの早いところ、でき上がったらコミットをする時に、ビルドクラスタが先ほどのCloud Native Buildpacksを中心に作っていた場合、それでコンテナのイメージとYAMLはでき上がってきます。

別のチーム、SIerの方々が作ったとしましょう。SIerの方々には開発クラスタではなく、自分の環境で作ったJavaのソースコードがあるとします。これも同じように共有させられます。同じ仕組みでビルドパックを使って、コンテナを使って、YAMLファイルができてきます。

保守チームは、なにか脆弱性に問題があった時にRebaseで対応できるパッチであれば、そのままRebaseで対応していきます。コンテナで作ったものもそうですが、ビルドクラスタはその時のYAMLファイルの更新もやってくれます。

また、いろいろビルドする時に出てくる脆弱性検査を、例えばSBOM(Software Bill Of Materials)を外部のデータベースに保管をしていきます。なので、これから環境にデプロイされるものの一覧表を取得できる仕組みが取れることになってきます。要は、彼らをいちいち戻さなくても、ある程度保守側で開発物の管理ができるようになってきます。

これはOuterLoop側からして何がうれしいかというと、この3チームがどういう手段でやろうと、このOuterLoopから常に同じに見えてくるわけですね。

保守でやってきたイメージ、SIerが作ったイメージ、内製が作ったイメージ、全部ビルドパックです。今日紹介したビルドパックの部分だけにしますが、ビルドパックでまず標準化されたコンテナや、このビルドクラスタが、そのようなした仕組みで成果物がやってくるという方式になってきます。

ステージングクラスタ、プロダクションクラスタにも考えは適用できる

本日は、あくまでこの1ヶ所のみの紹介となってしまいましたが、これ以外にもこの考えかたはいろいろなところであると思います。ステージングクラスタ、プロダクションクラスタといったところです。

今日以外のところで、このInnerLoop、OuterLoop、それを取り巻く技術にどういったものがあるのかを、最近は我々VMwareのブログで少しずつ紹介しています。興味があればぜひ見てもらえればと思っています。

短い時間でしたが、本日はVMwareの新しい考えかたと、InnerLoop、OuterLoopを紹介しました。