コンテナとKubernetesによって、ITサービス配信の大改革が起きると予測しています。しかし、一方で、従来のシステム設計の考え方や概念に捕らわれると、日経コンピュータ誌の「動かないコンピュータ」題材を提供する様な事になるかもしれません。
その様な不幸な状況を未然に防ぎ、コンテナがもたらす明るい未来へ進める様に、論文、ホワイトペーパー、ウェブ記事などから学んだ原則や考え方を、「知っておきたいコンテナの基礎知識」として要約します。
物流のコンテナ革命とIT業界のコンテナ
物流業界のコンテナとソフトウェア技術のコンテナを対比して、その価値を再確認してみたいと思います。
物流業界のコンテナによる革命
物流の世界のコンテナは、陸上輸送-海上輸送-陸上輸送という流れで革命的な改革を成し遂げ、輸送コストの削減、重労働からの解放、スピードアップを成し遂げました。 現代人の豊かな生活には、コンテナ革命の寄与があると言って間違いないでしょう。
トレーラーをシャーシとコンテナに分離し、コンテナ部分だけを効率よく船倉内に固定するための画期的なセルガイド方式を開発した。この新方式のコンテナ船は、国際貨物輸送の分野に海陸一貫輸送という大変革をもたらした。この「コンテナ革命」は、1970年代には世界の主要航路のコンテナ化がほぼ完了した。わずか10年程度でこれほど急激な輸送形態の変化が起こったのは海運史上でも他に例がない。
引用: コンテナ船:国際海上物流を一変させたユニークなアイデア
IT業界のコンテナの革命
IT業界のコンテナは、Dockerコンテナで代表されるソフトウェアのコンテナ技術です。 コンテナが物流業界で起こした大革命と同様に、ソフトウェアのコンテナ技術も、IT業界のアプリケーション開発、ソフトウェアの流通、システム運用といった分野で、大革命を起こして行くと信じています。
2013年初めに公開されたDockerコンテナは、約5年後の2017年末には、事実上標準化され同じ基準に基づいて、メガクラウド各社が対応するという状況になりました。 この技術の革命的な破壊力が認められている証だと思います。 例えるなら、ビックイベントの会場の準備が整い、これから利用客が流れ込む前の前夜といった状態だと思います。
クラウドベンダーだったdotCloudが2013年3月、オープンソースのソフトウェアとして公開しました。 そして、2017年末のコンテナの主な状況をまとめると、コンテナランタイムが標準化され、コンテナオーケストレーションツールの事実上の標準の座にKubernetesがつき、主要クラウドベンダはKubernetesのマネージドサービスを開始した。
引用 PublicKey Dockerコンテナ時代の第一章の終わり、そして第二章の展望など
以降、この記事で扱うコンテナは、Dockerやrktのコンテナを指すものとし、Kubernetesは、略してk8sと表記します。
コンテナ以前のシステムでは、物理サーバー、論理パーティション、または、仮想サーバー上に、本番、テスト、開発環境を構築して、ミドルウェア、テープバックアップ、監視システム、ジョブスケジューリングなどを個別に構築し、これらの運用を間違えなく実施するために、ドキュメントや手順を作成して運用するといったものであった、そして、環境を維持するために、OSやミドルウェアのパッチ適用のためにメンテナンス時間を確保して、停止調整、徹夜作業実施、問題発生時の切り戻し体制などを整えて運用する。 パブリック・クラウドの仮想サーバーやベアメタルを利用する場合でも、原則は、従来のオンプレミスの運用モデルを継承している。
一方で、ITのコンテナ革命以降では、アプリケーションが依存するOSとミドルウェアは、コンテナに置き換わり、Kubernetes以下のレイヤーへの個別依存が少なくなるために、サーバーのシステム管理に関する作業は大幅に少なり、劇的な効率化が期待される。
知っておきたいコンテナの基礎知識
アプリケーションをコンテナ化して、本格的なシステムの構築を進めるにあたって、プロジェクトに関わるすべての人に知っておいて欲しい共通の基礎知識について、簡潔に要約して、根拠となるリンクを掲載してきます。
1.コンテナの単一責任の原則
コンテナを一つの目的に絞って作成することは、後に列挙する参考資料「Dockerfileのベストプラクティス」や「ソフトウェア設計の原則」が主張する通り、適切な原則と考えます。
単一の関心事に対処するコンテナを作成する主な動機:
- コンテナイメージの再利用を促進と交換性を高める
- 変更の影響範囲を限定的にする
コンテナの範囲は、APサーバー、DBサーバーと言った仮想サーバーや物理サーバーのよりも具体性が高く、APサーバーを例にすると、APサーバーを構成するための ウェブサーバーのNginxモジュール、PHPアプリを動作させるPHP-FPMモジュール、ログ転送のFluentdモジュール、監視システムへ状態を伝える監視モジュールなど、他のサーバーでも再利用できる単位となります。
しかし、実用システムでは、単一目的のコンテナを密接に連携させて、複数のコンテナを組み合わせが出来なければなりません。 k8sなどのコンテナ・オーケストレータは、Qiita投稿記事 コンテナ・デザイン・パターンの論文要約に挙げた様に、コンテナの単一責任を維持しながら、複合的な機能を果たすサーバーとして機能を果たせる様になっています。
参考資料 ソフトウェア設計の原則
KISS — Keep it simple, stupid.
KISS の原則 (KISS principle) とは、"Keep it simple, stupid" (シンプルにしておけ!この間抜け)、もしくは、"Keep it short and simple" (簡潔に単純にしておけ)という経験的な原則[1]の略語。その意味するところは、設計の単純性(簡潔性)は成功への鍵だということと、不必要な複雑性は避けるべきだということである。
引用:ウィキペディア KISSの法則
DRY — Don’t repeat yourself.
Don't repeat yourself (DRY) あるいはSingle Source of truth(英)[要出典]は、特にコンピューティングの領域で、重複を防ぐ考え方である。この哲学は、情報の重複は変更の困難さを増大し透明性を減少させ、不一致を生じる可能性につながるため、重複するべきでないことを強調する。
引用: ウィキペディア Don't repeat yourself
YAGNI — You aren’t gonna need it.
YAGNIとは「You Aren't Going to Need it.」の略であり、日本語では、「それはきっと必要にならない」という意味になります。コードを書く上で「これが後で必要になりそう」で書いてはいけないという法則です。
引用:Qiita 知っておきたい!YAGNIの法則
SoC — Separation of concerns.
関心の分離(かんしんのぶんり、英語: separation of concerns、SoC)とは、ソフトウェア工学においては、プログラムを関心(何をしたいのか)毎に分離された構成要素で構築することである。 プログラミングパラダイムは開発者が関心の分離を実践することを手助けするものもある。その為には、モジュール性とカプセル化の実装のしやすさが重要となる。
引用: ウィキペディア 関心の分離
SOLID - Single responsibility, Open/closed, Liskov substitution, Interface segregation, Dependency inversion
- SRP (単一責任の原則:Single Responsibility Principle)
A class should have one and only one reason to change, meaning that a class should have only one job.
上記の言葉を直訳すると「クラスはただ一つの理由で変更すべきだ。つまり、クラスは一つの機能だけを持っているようにすること。」になります。
SRP原則を適用すれば責任領域が明確になりますので、一つの責任を変更することが次々と他の責任の変更になり続ける連鎖作用を防止できます。
更に、責任を適切に分配することにより、コードの可読性の向上,メインテナンスに容易という利点があります。
引用: オブジェクト指向プログラミングの五つ原則 - S.O.L.I.D (S編) http://developer.wonderpla.net/entry/blog/engineer/oop_solid_s/
- アジャイル設計と5つの原則 http://tdak.hateblo.jp/entry/20130703/1372842149
- 何かのときにすっと出したい、プログラミングに関する法則・原則一覧 https://qiita.com/hirokidaichi/items/d6c473d8011bd9330e63
Dockerfileのベストプラクティス
コンテナごとに1つのプロセスだけ実行
- Dockerfile を書くベスト・プラクティス http://docs.docker.jp/engine/userguide/eng-image/dockerfile_best-practice.html#id2
- Best practices for writing Dockerfiles https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
2.コンテナをブラックボックスとして扱う
オブジェクト指向プログラミングに関する研究が訴えてきた様に、コンテナをブラック・ボックスとすることは、再利用性を高め、生産性の改善に寄与します。 しかし、実用システムでは、コンテナ化されたアプリケーションの状態を観察するため手段を提供しなければなりません。つまり、コンテナをブラックボックスとして扱うために必要なAPIを備える必要があります。
最低限必要なAPI:
- 必要なAPIを実装して、可能な限り最良の方法でプラットフォームがアプリケーションを監視および管理できるようにする
- アプリケーションは、重要なイベントを標準エラー(STDERR)と標準出力(STDOUT)に出力
- Fluentd や Logstash などのツールを使用してログ集計
- Elasticsearch全文検索エンジン、Prometheusなどの時系列データベースにメトリクスを一元的に収集
コンテナのアプリケーションから外部に通知する方法は、を標準エラー(STDERR)と標準出力(STDOUT)ですが、入力のAPIは、後述の
「コンテナAPIとして活用する」に記載します。
オブジェクト指向プログラミングの背景からの引用
構成単位をブラックボックスとすることで再利用性を向上し、部品化を推進する仕組みが提唱され構造化プログラミング (structured programming) として1967年にエドガー・ダイクストラ (Edsger Wybe Dijkstra) らによってまとめあげられた
そこでデータを構造化し、ブラックボックス化するために考え出されたのが、データ形式の定義とそれを処理する手続きや関数をまとめて一個の構成単位とするという考え方でモジュール (module) と呼ばれる概念である
引用: ウィキペディア オブジェクト指向プログラミング
3.コンテナはLinuxのプロセスとして振舞う
コンテナの本質は、Linuxのプロセスであることから、プロセスとしての振る舞いに従わないといけません。以下4点がそれにあたります。
- シグナルを受け取り処理する
- 終了コードを返す
- 標準出力、標準エラー出力を利用する
- 引数を受け取り処理する
根拠となる参考リンク
コンテナはプロセスである
-
Containers and virtual machines
コンテナはLinuxでネイティブに動作し、ホスト・マシンのカーネルを他のコンテナと共有します。 コンテナは他のプロセスから隔離されたプロセスとして動作し、メモリを消費せず軽量です。 -
The underlying technology
DockerはGoで書かれており、Linuxカーネルのいくつかの機能を利用しています。 Namespacesによって、コンテナと呼ばれる独立した作業領域を提供します。これらのNamespacesは、分離のレイヤーを提供します。コンテナの各側面は別々のNamespacesで実行され、そのアクセスはその名前空間に限定されます。Control groupsは、特定のリソースセットに制限します。利用可能なハードウェアリソースをコンテナに共有し、オプションで制限と制約を強制することができます。
シグナルを受け取り処理
-
Termination of Pods
SIGTERMは各コンテナーのメインプロセスに送信されます。猶予期間が終了すると、SIGKILLがそれらのプロセスに送信され削除されます。
終了コードを返す
-
Container probes
コンテナはゼロ以外のステータスで終了すると失敗とみなされます。
標準ストリーム(出力)を利用する
-
Logging Architecture
コンテナ化アプリケーションの最も簡単で最も包括的なロギング方法は、標準出力ストリームと標準エラーストリームに書き込むことです。
引数を受け取り処理する
-
Define a Command and Arguments for a Container
ポッドを作成するときは、ポッド内で実行されるコンテナのコマンドと引数を定義できます。
4.コンテナにAPIを実装する
コンテナに格納するアプリケーションやサービスなどの再利用性、交換性を高めるために、コンテナは実行環境に柔軟に対応できるべきです。 例えば、データを格納するコンテナであれば、環境によって異なる永続ボリュームに接続できる必要があります。また、ユーザーIDやパスワードなど要求の依頼元を限定したり、接続先の認証を通過する必要もあります。 そして、起動時に初期化、または、前回の終了時の保存した情報をメモリ上の変数に戻す必要があるかもしれません。 急な終了要求に対して、実行中の処理を終了して永続ボリュームなどへ途中の状態を保存する必要もあります。
- 環境変数や引数を利用する
- 使用するポートを宣言
- ボリュームのマウント
- フック(起動や終了などのイベントを処理する)
- 健全性チェック
環境変数
-
Define Environment Variables for a Container
Kubernetes Podでコンテナを実行するときに環境変数を定義する方法が記載されています。 -
Container Environment Variables
コンテナ環境でコンテナが使用できるリソースが記載されていて、その中に環境変数もあります。 - Dockerfile ENV
- docker runコマンド 環境変数の設定(-e、–env、–env-file)
ポートの宣言
-
Connecting Applications with Services
デフォルトでは、Dockerはホストプライベートネットワークを使用しているため、コンテナは同じマシン上にある場合にのみ他のコンテナと通信できます。Dockerコンテナがノード間で通信するためには、マシンのIPアドレスに割り当てられたポートが割り当てられている必要があります。ポートは、コンテナに転送またはプロキシされます。これは、明らかに、コンテナがどのポートを使用するかを非常に注意深く調整するか、ポートを動的に割り当てる必要があることを意味します。 しかし、コンテナの複数の開発者の間でポートを調整することは、大規模に行うことは非常に難しく、ユーザーをコントロール外のクラスターレベルの問題に晒すことになります。Kubernetesは、どのホストに着陸しても、ポッドは他のポッドと通信できる様にしています。 - Dockerfile EXPOSE
- docker runコマンド EXPOSE (露出用のポート)
ボリュームのマウント
-
Secrets
「シークレット」は、パスワード、トークン、またはキーなどの少量の機密データを含むオブジェクトです。コンテナ・イメージに格納するよりも詳細に制御でき、偶発的流出のリスクを軽減できます。「シークレット」は、1つまたは複数のコンテナにマウントされたボリューム内のファイルとして、または、環境変数としてポッドで使用できます。 -
Configure a Pod to Use a ConfigMap
ConfigMapsを利用すると、環境変数、または、ボリュームのファイルとして、ConfigMapsが作成された実行環境から、ポッドがデータを読み取れる様になり、ポータブルに保つ事が出来る様になります。
フックに関する参照先
-
Container Lifecycle Hooks
Kubernetesはライフサイクルフックをコンテナへ提供します。フックにより、コンテナはライフサイクルのイベントを認識し、ハンドラに実装されたコードを実行できます。 -
Attach Handlers to Container Lifecycle Events
Kubernetesは、postStartイベントとpreStopイベントをサポートしています。コンテナの起動直後にpostStartイベントを送信し、終了する直前にpreStopイベントを送信します。
健全性チェック
-
Container probes
プローブによって定期的にコンテナの診断する。ノードに割り当てられたポッドを監視するkubeletは、コンテナに実装されたハンドラを呼び出します 。ハンドラには次の3種類があります。(1)ExecAction:コンテナ内で指定されたコマンドを実行します。コマンドがステータスコード0で終了すると、診断は成功したとみなされます。(2)TCPSocketAction:指定されたポートに対してTCPチェックを実行します。ポートが開いている場合、診断は成功したと見なされます。(3)HTTPGetAction:指定されたポートとパスにHTTP Get要求を実行します。ステータスコードが200以上400未満の場合、診断は成功とされます。 -
Configure Liveness and Readiness Probes
活性プローブと準備完了プローブがあります。コンテナが正常に動作することを確認する活性プローブは、コンテナ内で定期にコマンドを実行、HTTPリクエスト実行、または、ポートへのアクセスによって診断します。一方、準備完了プローブは、初期化処理中など一時的に要求を受信したくない場合に設定することで、Kubernetesのサービスから要求が分配されない様にします。
4.コンテナにラベルを付与する
コンテナのラベルと、コンテナを内包するKubernetesのポッドのラベルには、用途に違いがあります。
-
Dockerコンテナのラベル
イメージ、コンテナ、デーモンに対してラベルを通してメタデータを追加できます。ラベルには様々な使い方があります。例えば、メモの追加、イメージに対するライセンス情報の追加、ホストを識別するためです。 -
Kubernetesのラベル
Kubernetesでは、ラベルを使用して、独自の組織構造をオブジェクトにマップできます。サービスとデプロイメントの対応付け、デプロイメントからのサービスへの対応付など、ポッドが他のポッドのサービスを利用する際の対応付けとしてラベルを利用します。
参考資料
-
カスタム・メタデータ追加
Dockerコンテナでは、イメージ、コンテナ、デーモンに対してラベルを通してメタデータを追加できます。ラベルには様々な使い方があります。例えば、メモの追加、イメージに対するライセンス情報の追加、ホストを識別するためです。 -
Labels and Selectors
Kubernetesでは、ラベルを使用して、オブジェクトのサブセットを整理して選択することができます。ラベルは、作成時にオブジェクトに貼り付けることができ、その後、いつでも追加および変更することができます。 ラベルを使用すると、ユーザーはこれらのマッピングを保存する必要なく、疎結合の方法で独自の組織構造をシステムオブジェクトにマップできます。 -
Using Labels
Kubernetesのアプリケーションやデプロイメントなどのセマンティック属性を識別するラベルを定義して使用する。これらのラベルを使用して、他のリソースに適切なポッドを選択することができます。
5.コンテナの停止に対応する
コンテナは、いつでも新しいコンテナと置き換え可能にする必要がある。 サーバーの場合はプロセスの再起動で対応するのですが、コンテナの本質はプロセスなので、突然の停止要求に対応する必要がある。そして、コンテナのサイズを小さく保ち起動時間が最小にすることが必要です。
コンテナが停止するケースには以下があります。
- コンテナ内のアプリがメモリ・リークして、メモリのリミットを超えると強制停止される。
- 少なくなったリクエスト数に応じて、占有するリソースを減らすために、コンテナのレプリカ数を減らす。
- コンテナ内のアプリが続行不能な致命的エラーが発生した場合、コンテナを再起動する。
- コンテナをホストするサーバーに障害がある場合、コンテナを終了させて、健全なコンテナホストで起動する。
理由の参考リンク
-
12FA IX. 廃棄容易性
即座に起動・終了することができる。起動時間を最小化するよう努力するべきである。理想的には、1つのプロセスは、起動コマンドが実行されてから数秒間でリクエストやジョブを受け取れるようになるべきである。SIGTERMシグナルを受け取ったときに、グレースフルにシャットダウンする。 下層のハードウェアの障害に関して言えば、プロセスは 突然の死に対して堅牢 であるべきである。 -
How Pods with resource limits are run
コンテナがメモリ制限を超えると、コンテナは終了させられます。 もし再起動可能であれば、他のタイプのランタイムエラーと同様に再起動します。 -
Durability of pods (or lack thereof) 日本語訳 ポッドの耐久性(またはその欠如)
コンテナを内包するポッドは、耐久力のある実体として扱われる様にはなっていません。 スケジューリングの失敗、ノードの障害または退避、リソース不足の問題、または、ノードの保守作業などで、コンテナを内包するポッドは停止します。 -
Updating a Deployment
デプロイメントを詳しく見ると、最初に新しいポッドを作成した後、古いポッドをいくつか削除して新しいポッドを作成したことがわかります。 -
Determine the Reason for Pod Failure
このリンクは、コンテナの終了メッセージを書き込んで読み込む方法について記述されています。
7.ビルドしたコンテナは変更不能とする
コンテナ・イメージには、OS、ライブラリ、構成、ファイル、およびアプリケーションのコードが含まれていて、開発者のローカル・ラップトップ、データセンター内の物理マシンまたは仮想マシン、クラウドプロバイダ、または複数の環境で実行できます。
例えば、あるプログラミング言語のフレームワークを利用して、アプリケーションを開発したとします。 フレームワークは、開発者が高い生産性をあげることができる様に、少ないコード量で、最大の機能を実装できる様になっています。このため、フレームワークのコードには、たくさんのOSSプロジェクトの成果物が含まれており、構成部品として利用するソフトウェア・パッケージの数やコード量は、大きな量となります。これら下位スタックのOSSプロジェクトで、脆弱性、バグ修正、そして、新機能追加などの理由から、随時コードが修正されます。 もちろん、セキュリティやバグを避けるという観点から最新のコードを利用したいと考えるには自然な事と思います。
少し極端な言い方をですが、アプリケーションのコンテナをビルドした時点で、唯一無二のコード・スタックが生成される事になります。従って、コンテナを開発、テスト、本番などの環境を移動する都度に、作り直してはいけないと考えられます。 コンテナを作り直したら、もう一度、テストから実施するべきでしょう。しかし、心配することはありません。開発者のローカル・ラップトップ、データセンター内の物理マシンまたは仮想マシン、クラウドプロバイダ、または複数の環境で実行できるため、流れ作業として自動的に推進することができます。
- コンテナ・イメージは、開発、テスト、本番で同じにして、コンテナ・イメージをリポジトリからプルして利用する。
- データベースやキャッシュなど、各環境により異なるサービスの接続先は、環境のボリュームに保存しておき、環境から提供を受ける動作を切り替える。(ConfigMap, secretsなど)
- ビルド時にコンテナが自立的に実行できるファイル・レイアウトを作成し、その後の環境変数などのコンテナAPIで動作を切り替える。
- コンテナのビルドを再現性のある様に保つため、コンテナに入って修正をかけ、コミットして利用しない。 必ずDockerfileから再ビルドできる様にする。
根拠となる参考リンク
-
12FA X. 開発/本番一致
継続的デプロイしやすいよう開発環境と本番環境の差異を小さくする。 つまり、開発者が書いたコードは数時間以内にデプロイされる、開発者はデプロイに関わり本番環境での挙動をモニタリングする、開発環境と本番環境で利用するミドルウェアなど一致させた状態を維持する。 -
Docker Overview
コンテナは、継続的な統合と継続的な配信(CI / CD)のワークフローに最適です。次シナリオが考えられます。(1)開発者はコードをローカルで作成し、Dockerコンテナを使用して同僚と作業を共有します。(2)Dockerを使用してアプリケーションをテスト環境にプッシュし、自動テストと手動テストを実行します。(3)開発者がバグを見つけたら、開発環境でバグを修正し、テスト環境に再デプロイしてテストと検証を行うことができます。(4)テストが完了したら、更新されたイメージを本番環境にプッシュします。または、コンテナ・イメージで納品することもできます。
8.自己完結させる
前述の「ビルドしたコンテナは変更不能とする」と深く関わるのですが、コンテナのビルド時に、すべての依存を追加する必要があります。
コンテナ・イメージには、OS、ライブラリ、構成、ファイル、およびアプリケーションのコードが含まれていて、開発者のローカル・ラップトップ、データセンター内の物理マシンまたは仮想マシン、クラウドプロバイダ、または複数の環境で実行できる様にします。
- カーネルのみに依存するべき (コンテナはホストOSのカーネル上で動作する)
- 他の環境上でも同様に動くために、アプリが依存するすべてのコードを同梱する
環境に対して柔軟に順応できる様にして再利用性を高めるため「コンテナとしてのAPIを実装」も合わせて実施します。
参考リンク
- Container Patterns
-
6. Self-Contained
コンテナはLinuxカーネルのみに依存すべきです。ビルド時にすべての依存関係を追加する必要があります。
9.小さなイメージが良い
コンテナは、単一の目的を達成するためにできるだけ少ないコード/ライブラリである必要があります。
より小さなイメージが好まれる理由には、以下の様なものがあります。
- 起動時間が短くなることで、導入、再スケジュール、更新などが早くなる。
- コンテナ・イメージをネットワークで転送する時間が短くできる
- コードを少なくすることで、より小さな攻撃面となり、監査が簡単になる
開発の初期段階で、試行錯誤に実験を繰り返す様な段階では、UbuntuやCentOSのイメージを使うと豊富なツールをインストールして試す事ができるので好ましいと思います。 しかし、意図するコンテナの責任範囲が決まり、本番利用に向けて準備する段階となれば、BusyboxやAlpineなどをベースイメージとして、コンパクトなコンテナのビルドを目指す事が望ましいと考えます。
-
7. Small
コンテナは、単一の目的を達成するためにできるだけ少ない量のコード/ライブラリを持つ必要があります。
まとめ
Kubernetesを導入するにあたり、その基礎となるコンテナの本質を理解して設計指針とする事で、陥りがちな落とし穴を避け、効果を発揮するアプリケーションのモダナイゼーション が達成できると考えます。
最後にまとめとして、コンテナ技術を採用する事で目指すべき、システムの特性を挙げたいと思います。
- 部分の単独リリースが可能
- 部分領域や基盤部分の再利用が可能
- 処理タイプで分離できる
- 異なる負荷の混在が可能
- 実証試験の影響隔離
- 自在な組み合わせ構成
- 異なる開発言語、バージョン、ライブラリの混在
- アーキテクチャ上の境界を強制
- 頑丈さ、および、クラッシュ時の影響範囲隔離またはチーム分離