コンテンツに移動
Google Cloud

Cloud Container Builder によるリーン コンテナ開発

2017年5月18日
Google Cloud Japan Team

Java アプリケーションを作るには、ソース コード、アプリケーション ライブラリ、ビルド システム、ビルド システムの依存ファイル等々、そしてもちろん JDK というように、たくさんのファイルが必要です。ところが、アプリケーションをコンテナ化すると、これらのファイルが残ってしまい、コンテナ サイズが大きくなってしまうことがあります。そして、時間の経過とともに Docker レジストリとコンテナ ランタイムの間で不要なビットの格納や移動が行われ、膨大な時間とコストが無駄になります。

コンテナ サイズをできるかぎり小さく保つためには、ランタイム コンテナのアセンブルからアプリケーションのビルド(およびビルドに必要なツール)を分離すべきです。Google Cloud Container Builder を使えば、まさにそのようにして以前よりも大幅にリーンなコンテナを構築できます。リーン コンテナは、高速にロードでき、ストレージにかかるコストを節約します。

コンテナのレイヤ

Dockerfile の各行はコンテナに新しいレイヤを追加します。例を見てみましょう。

読み込んでいます...

この例では、“lots-of-data” というローカル ディレクトリをコンテナの “data” ディレクトリにコピーし、すぐにそれを削除しています。この操作は一見無害に見えますが、実はそうではありません。

それは、以前のレイヤを読み出し専用にする Docker の “copy-on-write” 戦略に理由があります。一連のコマンドでコンテナ ランタイムに不要なデータを生成し、同じコマンドでそれを削除しないときは、そのスペースは回収できなくなるのです。

Spinnaker コンテナ

Spinnaker は、Netflix が開発したオープンソースのクラウド用継続的デリバリ ツールで、Netflix や Google を含むパートナーのコミュニティによって活発にメンテナンスされています。このツールはマイクロサービス アーキテクチャになっており、個々のコンポーネントは Groovy と Java で書かれ、Gradle でビルドされます。

Spinnaker はマイクロサービス コンテナを Quay.io にパブリッシュします。どのサービスもほぼ同じ Dockerfile を使用しているので、ここでは例として Gate サービスを使います。以前は次のような Dockerfile を使っていました。

読み込んでいます...

Spinnaker では、ビルドには Gradle が使われており、この場合は Debian パッケージをビルドしています。Gradle はすばらしいツールですが、ビルドのために大量のライブラリをダウンロードします。これらのライブラリは、パッケージのビルドには必要不可欠ですが、実行時には不要です。実行時に必要な依存ファイルは、すべてパッケージ自体にバンドリングされています。

先ほども触れたように、Dockerfile の各行はコンテナに新しいレイヤを作ります。そのレイヤでデータが生成され、同じコマンドで削除されなければ、そのスペースは回収できません。この例の場合、Gradle はビルドのために数百個の MB 級のライブラリを “cache” ディレクトリにダウンロードしていますが、それらのライブラリは削除されていません。

この場合は、2 つの “RUN” コマンドを結合して、ビルド完了時にすべてのファイル(ソース コードを含む)を削除したほうが、効率的にビルドできます。

読み込んでいます...

これで、最終的なコンテナ サイズは 652 MB から 284 MB へと減少し、56 % も不要なファイルを削除できました。しかし、もっと良い方法はないのでしょうか。

Cloud Container Builder を使用する

Container Builder を使用すると、ランタイム コンテナのアセンブルからアプリケーションのビルドをさらにはっきりと分離できます。

Container Builder チームは、git や docker、gcloud など、コマンドライン インターフェースの開発ツールをまとめた一連の Docker コンテナをパブリッシュしています。これらのツールを使えば、ワンステップでアプリケーションをビルドし、別のワンステップで最終的なランタイム環境をアセンブルする “cloudbuild.yaml” ファイルを定義できます。

ここで使用する “cloudbuild.yaml” ファイルは次のとおりです。

読み込んでいます...

何が起きているのかを知るために、個々のステップをじっくり見ていきましょう。

ステップ 1 : アプリケーションのビルド

読み込んでいます...

私たちのリーン ランタイム コンテナには “dpkg” が含まれていないので、Gradleの “buildDeb” タスクは使用しません。その代わり、同じディレクトリ構造を作成してコピーを楽にする “installDist” という別のタスクを使います。

ステップ 2 : ランタイム コンテナのアセンブル

読み込んでいます...

次に、Docker build を実行してランタイム コンテナをアセンブルします。ランタイム コンテナの定義には、“Dockerfile.slim” という別のファイルを使います。その内容は次のとおりです。

読み込んでいます...

ステップ 1 で行った Gradle の “installDist” タスクは、すでに必要なディレクトリ構造(“gate/bin/”、“gate/lib/” など)を作っているので、単純にそれをターゲット コンテナにコピーできます。

Linux ベース レイヤとしてとてもリーンな Alpine、“openjdk:8u111-jre-alpine” を使っていることが、スペース節約の大きな要因の 1 つになっています。さらに、アプリケーションのビルドで必要だった大きな JDK ファイルを取り除き、JRE だけをコピーしています。

ステップ 3 : イメージのレジストリへのパブリッシュ

読み込んでいます...

最後に、コンテナにタグとしてコミット ハッシュと “latest” を付け、Google Cloud Container Registry(grc.io)に対して、これらのタグが付いたコンテナをプッシュします。

まとめ

Container Builder を使用すると、最終的なコンテナ サイズは 91.6 MB まで減りました。これは、初期の Dockerfile よりも 85 % 小さく、改良版と比べても 68 % 削減したことになります。

https://storage.googleapis.com/gweb-cloudblog-publish/images/-sFEBfRXPFx6_Z9UBflUbcjTZ4gPirElGi_iaxltZ7.max-1600x1600.PNG

*大幅なサイズ縮小は、ビルド環境とランタイム環境を分離し、最終的なコンテナのためにリーンなベース レイヤを選択したことによるものです。

https://storage.googleapis.com/gweb-cloudblog-publish/images/bTaPHAdmeZBY4LN2sdRQ8_XgLxdLX8qWyAK1wiSYj_.max-1000x1000.PNG

このアプローチを個々のマイクロサービスに適用したところ、同様の結果が得られました。最終的なコンテナのフットプリントの合計は、約 6 GB から 1 GB 未満に縮小しました。

* この投稿は米国時間 5 月 5 日、Spinnaker Team の Software Engineer である Travis Tomsu によって投稿されたもの(投稿はこちら)の抄訳です。

- By Travis Tomsu, Software Engineer, Spinnaker Team

投稿先