Understand K8S from the perspective of resource management
リソースタイプ
物理マシンや仮想マシンのクラスター時代において、管理対象のリソースは主にホストベースのクラスターである。単一のホストノードは物理マシンでも、ハイパーバイザ仮想化ミドルウェアに基づく VM でもよい。リソースタイプには主に CPU、メモリ、ディスク IO、ネットワーク帯域幅が含まれる。
K8S クラスターは一般に、少なくとも 1 つのマスターノードといくつかのワーカーノードで構成される。マスターノードには Scheduler、Controller、リソースオブジェクトインターフェイスである API Server、および永続ストレージ etcd が含まれる。Worker ノードは Kubelet を通じて Master ノードと通信し、そのリソースと負荷 Pod は Master によって制御・スケジューリングされる。K8S のアーキテクチャは C/S モデルであり、Worker ノードのリソース管理とスケジューリングは Master ノードが制御する。
K8S クラスターのリソースを認識する際は、従来のノード単位での分割方法は使用しない。ここではワークロード、ストレージ、ネットワークの 3 つの観点からリソースタイプを紹介する。これらは K8S が提供するコアリソースオブジェクトである Pod、PVC/PV、Service/Address に基づいており、最も基本的なクラスターリソース管理とコンテナオーケストレーションを実現する。K8S クラスターでは、CPU とメモリリソースの定義は Pod に含まれており、ユーザーは K8S を介して直接管理することはできない。PVC/PV モードはストレージリソースとノードリソースを切り離す。Service は自動サービス検出を提供し、K8S クラスターに自己修復機能を持たせる。Ingress はクラスターネットワークトラフィックの制御を提供し、K8S はワークロードとネットワーク負荷を統合的に考慮して弾力的スケーリングを実現する。
ワークロード
K8S は Pod を通じてコンテナグループのライフサイクルを管理する。Pod は K8S スケジューリングの最小単位である。アプリケーションサービスのデプロイメントとスケジューリングも Pod ベースで行われ、ワークロードとも呼ばれる。ステートレスなアプリケーションのデプロイメントは ReplicaSet によって定義・管理され、依存関係と状態を持つアプリケーションのデプロイメントは StatefulSet によって定義・管理される。ReplicaSet と StatefulSet は別の興味深いトピックであり、今後のシリーズで詳しく紹介する予定である。アプリケーションが必要とする CPU/メモリリソースは Pod 内で定義される。CPU/メモリリソースはコンテナレベルであり、Pod の spec -> containers -> resources で定義されるためである。
K8S は requests と limits の 2 つの設定パラメータを提供し、リソースの範囲とクォータを定義する。
Requests はワークロードのリソース要件を定義する。これはコンテナ起動時の K8S リソース割り当てのデフォルト値である。
Limits はワークロードのリソース上限を定義する。これはコンテナ実行時の K8S リソースの事前割り当てクォータである。
コンテナは同じ Pod のストレージボリューム、ネットワーク名前空間、PID 名前空間を共有する。コンテナには CPU/メモリリソースの割り当てとリソースクォータがある。リソース管理を行う際は、CPU/メモリリソースの割り当てクォータと、ワークロードの実際の CPU/メモリ使用率を区別する必要がある。K8S クラスターの情報コンソールでは、一般にワークロードで定義された CPU/メモリ割り当てクォータが表示される。
ストレージ
PVC の正式名称は PersistentVolumeClaim で、永続ボリューム要求である。PV の正式名称は PersistentVolume で、永続ボリュームである。その名の通り、永続ボリュームは K8S がさまざまなタイプのストレージリソース(ブロックストレージ、NAS、オブジェクトストレージ)を記述するためのリソースオブジェクト定義である。各クラウドベンダーが提供するストレージ管理プラグインに基づいて、IaaS レイヤーのストレージリソースを直接 K8S クラスターストレージとして使用できる。
PV の抽象化だけで、K8S ストレージリソースと各クラウドメーカーの IaaS ストレージリソースとのマッピング問題を解決できるように思えるが、なぜさらに PVC リソースオブジェクトを抽象化する必要があるのだろうか。PVC は OOP の抽象クラスに似ている。開発時に抽象クラスを使用するのは、通常オブジェクトの呼び出しとオブジェクトの実装を切り離すためである。Pod が PV ではなく PVC にバインドすることで、Pod のデプロイメントと PV リソースの割り当てが切り離される。
Pod のデプロイメントは一般に開発チームのアプリケーションデプロイメントのサブプロセスに属し、開発者が制御する。PV ストレージリソースの定義と割り当ては DevOps チームに属し、クラスター管理者が制御する。
Pod と PVC は名前空間のアクセス制御範囲内のリソースオブジェクトであり、PV はクラスターのアクセス制御範囲内のリソースオブジェクトである。
PVC/PV モードはストレージリソースとノードリソースを切り離す。つまり、PVC/PV で定義されたストレージリソースはノードリソースから独立しており、動的バインディングを通じてワークロードとともにノード間で移行できる。具体的な動作はクラウドベンダーが提供するストレージプラグインの実装に依存する。
ネットワーク
Service はサーバー側で自動検出メカニズムを実装する。つまり、アプリケーションサービスを提供する Pod グループが同じサービスドメイン名のアクセスアドレスを通じて外部サービスを提供できる。Pod の追加や削除は Service の全体的なサービスパフォーマンスに影響しない。ワークロードは Service を通じて仮想 IP とポートにバインドされ、Pod が割り当てられるノードリソースに対して透過的である。Service とノードネットワークリソースは切り離されており、クラスター全体が負荷移行と障害自己修復の機能を持つ。
Service オブジェクトとは異なり、Ingress は K8S がクラスター外部にサービスを提供するためのリソースオブジェクトである。K8S 以前は、Nginx のリバースプロキシと負荷分散を使用して負荷とトラフィックを制御し、サーバーの処理能力を水平方向に拡張していた。Ingress は本質的に NginxPod であり、Service オブジェクトを通じて公開され、LoadBalancer モードに基づいてリバースプロキシと負荷分散を提供する。Ingress はクラスターのルーター兼アクセスポータルのようである。Ingress にルーティングルールを設定してワークロードと対応するドメイン名解決パスをバインドすることで、1 つの外部 IP アドレスだけで複数クラスター内のサービスを公開でき、IP リソースを節約できる。
Pod のスケジューリングと管理
最も人気のあるコンテナオーケストレーションプラットフォームとして、K8S はプラットフォームレベルの弾力的スケーリングと障害自己修復のソリューションを提供する。K8S クラスターは C/S アーキテクチャに基づいており、Master がクラスターリソースと負荷分散を制御・管理する。Pod は K8S スケジューリングの最小単位である。クラスターリソース管理とスケジューリングを習得するなら、Pod のスケジューリングと管理から始めるべきである。
ヘルスチェック
K8S はプラットフォームレベルの弾力的スケーリングと障害自己修復を実行する前に、アプリケーションサービスの実行状況を把握する必要がある。最も簡単な方法は、コンテナプロセスが実行中かどうかを継続的に確認することである。コンテナプロセスの障害を検出すると、自動的にプロセスの再起動を試みる。多くの場合、プロセスの再起動で問題を解決できるため、このヘルスチェックはシンプルだが非常に効果的で必要不可欠である。
ただし、Java アプリケーションの実行中に OOM 例外やデッドロックが発生しても、JVM プロセス自体は実行中のままの場合がある。この場合、Pod はまだ実行中だが、アプリケーションプロセスはすでにサービスを提供できない状態である。このような状況では、前述のヘルスチェックでは対応できない。K8S は
livenessProbe を提供しており、アプリケーションサービスレベルの異常状態を捕捉し、アプリケーションサービスが正常に動作しているかを総合的に把握できる。
livenessProbe はプロセスヘルスチェックに似ており、コンテナプロセスの健全性を確認する。障害を検出すると、プロセスを再起動して自動修復を行う。
異なる点は、livenessProbe がアプリケーションサービスで定義された HTTP GET API を呼び出して Pod の公開 IP とポートに接続し、リクエストのリターンコードが 200〜399 の範囲内かどうかでコンテナプロセスの動作状態を判定することである。
HTTP に加えて、TCP ソケット接続の成否によってもアプリケーションサービスの動作状態を判断できる。
アプリケーションサービス内部からではなく K8S 側からこの動作状態判断ロジックを実行することで、K8S はアプリケーションサービスレベルの健全性状態を把握できる。
負荷が過大な場合、コンテナプロセスが正常な状態であっても、アプリケーションサービスが正常にサービスを提供できない可能性がある。このような状況は、K8S が readinessProbe を通じて検出する。
Readiness Probe のアクセス方法は LivenessProbe と同じである。HTTP GET API または TCP ソケット接続を通じて、Kubelet プローブが Pod のワークロード状況を K8S 管理ノードに報告できる。
readinessProbe が失敗し、アプリケーションサービスプロセスがリクエストを正常に処理できない場合、Pod は再起動されず、サービスエンドポイントから削除され、サービスのリクエスト負荷を受け取らなくなる。トラフィックのデグレードに似ており、Pod が受信したリクエスト負荷を正しく処理できることが保証される。
自動スケジューリングポリシー
Google が 2015 年に K8S をオープンソース化した際、クラウドネイティブの初期定義を提起した。すなわち、アプリケーションのコンテナ化、マイクロサービス指向アーキテクチャ、およびアプリケーションのコンテナスケジューリング対応である。コンテナスケジューリングは K8S のコア技術である。数百のマイクロサービスコンテナクラスターに直面すると、Pod のスケジューリングとリソース管理は複雑になる。Pod 内のコンテナーグループは実行時に相互に関連し、同じノード上で動作してノードリソースを共有する。アプリケーション負荷が変動すると、コンテナのノードリソース消費も変化する。ノードリソースのキャパシティと可用性もアプリケーションサービスのパフォーマンスと安定性に影響を与える。
K8S の Scheduler コンポーネントは、APIServer が定義する Pod リソースオブジェクトと Kubelet が報告する各ノードのリソース使用状況に基づいて、スケジューリングに適したノードを選択する。Scheduler は Pod の作成、弾力的スケーリング、障害時の負荷移行を制御する。Scheduler はコンテナランタイムの依存関係、リソース要件の設定、デフォルトスケジューリングポリシーに基づいて判断を行う。デフォルトスケジューリングポリシーは通常、スケジューリング先のノードが高可用性、高性能、低レイテンシを保証できるように考慮されている。そのため、ノード選択に特別な目的がない限り、ユーザーはデフォルトのスケジューリング戦略を直接使用することが推奨される。
カスタマイズされたスケジューラはクラスター設定ファイルを操作できる。下図の JSON ファイルに示す通りである。その中で Predicates は、Pod スケジューリング時にルールに適合するノードリソースのみを考慮することを意味する。Priorities は、Predicates ルールのフィルタリングに適合したノードリソース集合を優先順位でソートし、最も重みの高いノードリソースを選択してスケジューリングすることを意味する。たとえば、Scheduler は以下の例のようにリソース要求が最も少ないノードを優先的にスケジューリング対象とする。
Scheduler が APIServer が生成した Pod リソースオブジェクトの定義を検出すると、まず Predicates を通じてルールに適合するノードリソース集合をフィルタリングする。次に、これらのノードリソースに優先順位に基づいて重み付けを行い、最適なノードを選択する。そしてノードリソースを割り当てて Pod を作成する。
Pod は一般に、Pod リソースの需要よりもリソースキャパシティが大きいノードにスケジューリングされる。一般に、ノード OS と K8S 管理コンポーネントは事前に一部のノードリソースを予約するため、割り当て可能なリソースキャパシティは通常ノードの総リソースよりも小さくなる。Scheduler がスケジューリングに参照するリソース設定は、ノードが割り当て可能なリソース、すなわちノードキャパシティを指す。その計算方法は以下の通りである:
Allocatable Capacity = Node Capacity - Kube-Reserved - System-Reserved
Allocatable Capacity は Scheduler がアプリケーションサービス Pod に割り当て可能なノードリソースである
Node Capacity はノードリソースの総量である
Kube-Reserved は K8S バックグラウンドプロセス用の予約リソースで、Kubelet、CRI、CNI などのコンポーネント用である
System-Reserved はノードのオペレーティングシステムのバックグラウンドプロセス用の予約リソースで、sshd、udev などのプロセス用である
前述のワークロードの紹介で、requests と limits を通じてコンテナ実行時のリソース消費量とクォータを定義できることに触れた。ノードリソースのすべてをリソーススケジューリングに使用できるわけではないため、Pod テンプレートを定義する際は、コンテナの resources -> requests と resources -> limits を明確に定義することが推奨される。これにより、ワークロードと K8S コンポーネントとのリソース競合によるスケジューリング失敗を防止できる。
Limits はコンテナリソース使用量の上限を定義し、Requests はコンテナリソースの初期設定を定義する。通常、リソースはコンテナ起動時に requests に基づいて割り当てられる。コンテナ実行時のリソース消費量は一般に requests の割り当て量よりも少ない。下図の通りである。
このピラミッド型のリソース割り当て方式では、多くのリソース断片化が発生する。ワークロード間のリソース競合が発生した場合、K8S は 3 つの QoS レベルを提供する。
Best Effort:Pod が requests と limits を設定しない。この種の Pod は QoS 優先度が最も低い。ノードリソーススケジューリングが不足したり競合が発生したりした場合、この Pod が最初に削除または移行される。
Burstable:Pod が requests と limits を設定するが、requests が limits より小さい。この種の Pod はリソース保証が比較的低い。すなわち、ノードリソース競合により Best Effort Pod がもう存在しない場合、この種の Pod が優先的に削除または移行される。
Guaranteed:Pod が requests と limits を設定し、requests が limits と等しい。この種の Pod は最も高いリソース保証を持つ。QoS レベルは Burstable と Best Effort よりも高い。そのため、アプリケーションサービスの Pod テンプレートを定義する際は、コンテナリソース保証とサービス品質に高い要件を持つ Pod に対して、できる限り適切な requests と limits を設定することが推奨される。
サービス検出
K8S クラスターで実行されるアプリケーションサービスは、主にマイクロサービスアーキテクチャに基づく分散システムである。サービス間には相互呼び出し関係が存在することが多い。アプリケーションサービス Pod をスケジューリングする際、Scheduler は最適なノードを選択してリソースを割り当て、Pod を作成する。コンテナ起動前に、この Pod に ClusterIP アドレスをランダムに割り当てる。そのため、他のアプリケーションサービス Pod がこのアプリケーションサービス Pod と通信したい場合、ランダムに割り当てられた ClusterIP 情報を取得することが困難である。
クライアントサイド自動検出
従来の分散システムでは、ZooKeeper などのサービス間自動検出にクライアントサイド検出方式がよく使用される。クライアントサービスにはサービスレジストリを発見して通信対象のサービスインスタンスを選択できるプローブエージェントが組み込まれている。サーバーサービスインスタンスは自身の状態をサービスレジストリに報告する。クライアントサービスはサービスレジストリ情報を照会することで、応答するサービスインスタンスを選択して起動し、インタラクションを行う。
エージェントによるクライアントサイドサービス検出
サーバーサイド自動検出
K8S が実装するサービス検出はサーバーサイド方式に基づいている。つまり、サーバーサイドの Pod が自身のサービス能力をサービスレジストリに積極的に報告する必要がある。クライアント Pod はサービスレジストリにアクセスでき、サービスレジストリを通じてサービス情報を取得し、応答するサーバーサイド Pod にアクセスする。クライアント Pod は一定の仮想 IP を使用してプロキシサービスを通じて同じサービスにアクセスできるため、どの Pod がサービスを提供しているかを意識する必要がない。
プロキシによるサーバーサイド検出
K8S でサーバーサイドの検出ロジックを実装するリソースオブジェクトが Service である。Service は Pod セレクターとポート番号の定義を通じて、仮想 IP(ClusterIP とも呼ばれる)を Pod グループにバインドできる。
Pod の起動後に ClusterIP がランダムに割り当てられるため、他のサービスの Pod はどのようにしてこの ClusterIP を検出し、通信するのだろうか。主に 2 つの方法がある:
環境変数
Pod の作成時に、それにバインドされた Service オブジェクトも同時に作成され、バインドされたポート番号はすぐにリッスンされる。Service に関連する ClusterIP とポートの値は、自動的に環境変数として Pod に設定され、アプリケーションサービスはこの ClusterIP とポートを通じて外部サービスを提供できる。
Service に対応する環境変数は Pod 起動後に注入できないため、環境変数ベースの ClusterIP とポートのバインディングは Pod 起動プロセス中のみ有効である。
DNS クエリ
K8S はすべての Pod が使用できるプラットフォームレベルの DNS サービスを提供する。Service リソースオブジェクトが作成されると、DNS サービスは対応する Pod がアクセスできる DNS アクセスアドレスをバインドできる。この DNS サービスは、アプリケーションサービスの DNS アクセスアドレスと Pod 起動時に割り当てられる ClusterIP およびポートの対応関係を管理し、DNS アクセスアドレスから対応する Pod へのトラフィック負荷の解決を担当する。
クライアントサービスが ServiceName と対応する名前空間を知っていれば、内部ドメイン名アドレス service-name.namespace.svc.cluster.local を通じてアプリケーションサービス Pod に直接アクセスできる。
Service-name は Service オブジェクト定義の名前である
Namespace は Service と Pod の属する名前空間名である
Svc は Service リソースを表す
Cluster.local は K8S の CoreDNS サービスのデフォルトのクラスター内部アクセスドメイン名である
アドレスドメイン名のプレフィックスを要求する
あとがき
最近、K8S 導入の基本的な知識ポイントを整理している。この記事は「アプリケーション開発の視点から K8S を理解する」の姉妹編であり、リソース管理の視点から K8S に対する新たな理解を提供する。K8S プラットフォームのユーザーは一般にアプリケーション開発者とクラスター管理者に分かれる。K8S を習得する際、クラスター管理者はクラスターレベル、リソースレベル、パフォーマンスレベルから K8S を理解する必要がある。そのためには、Pod のスケジューリングとリソース管理、ストレージとネットワークリソース、Service とトラフィック管理といった最も基本的な知識ポイントを習得し理解する必要がある。
物理マシンや仮想マシンのクラスター時代において、管理対象のリソースは主にホストベースのクラスターである。単一のホストノードは物理マシンでも、ハイパーバイザ仮想化ミドルウェアに基づく VM でもよい。リソースタイプには主に CPU、メモリ、ディスク IO、ネットワーク帯域幅が含まれる。
K8S クラスターは一般に、少なくとも 1 つのマスターノードといくつかのワーカーノードで構成される。マスターノードには Scheduler、Controller、リソースオブジェクトインターフェイスである API Server、および永続ストレージ etcd が含まれる。Worker ノードは Kubelet を通じて Master ノードと通信し、そのリソースと負荷 Pod は Master によって制御・スケジューリングされる。K8S のアーキテクチャは C/S モデルであり、Worker ノードのリソース管理とスケジューリングは Master ノードが制御する。
K8S クラスターのリソースを認識する際は、従来のノード単位での分割方法は使用しない。ここではワークロード、ストレージ、ネットワークの 3 つの観点からリソースタイプを紹介する。これらは K8S が提供するコアリソースオブジェクトである Pod、PVC/PV、Service/Address に基づいており、最も基本的なクラスターリソース管理とコンテナオーケストレーションを実現する。K8S クラスターでは、CPU とメモリリソースの定義は Pod に含まれており、ユーザーは K8S を介して直接管理することはできない。PVC/PV モードはストレージリソースとノードリソースを切り離す。Service は自動サービス検出を提供し、K8S クラスターに自己修復機能を持たせる。Ingress はクラスターネットワークトラフィックの制御を提供し、K8S はワークロードとネットワーク負荷を統合的に考慮して弾力的スケーリングを実現する。
ワークロード
K8S は Pod を通じてコンテナグループのライフサイクルを管理する。Pod は K8S スケジューリングの最小単位である。アプリケーションサービスのデプロイメントとスケジューリングも Pod ベースで行われ、ワークロードとも呼ばれる。ステートレスなアプリケーションのデプロイメントは ReplicaSet によって定義・管理され、依存関係と状態を持つアプリケーションのデプロイメントは StatefulSet によって定義・管理される。ReplicaSet と StatefulSet は別の興味深いトピックであり、今後のシリーズで詳しく紹介する予定である。アプリケーションが必要とする CPU/メモリリソースは Pod 内で定義される。CPU/メモリリソースはコンテナレベルであり、Pod の spec -> containers -> resources で定義されるためである。
K8S は requests と limits の 2 つの設定パラメータを提供し、リソースの範囲とクォータを定義する。
Requests はワークロードのリソース要件を定義する。これはコンテナ起動時の K8S リソース割り当てのデフォルト値である。
Limits はワークロードのリソース上限を定義する。これはコンテナ実行時の K8S リソースの事前割り当てクォータである。
コンテナは同じ Pod のストレージボリューム、ネットワーク名前空間、PID 名前空間を共有する。コンテナには CPU/メモリリソースの割り当てとリソースクォータがある。リソース管理を行う際は、CPU/メモリリソースの割り当てクォータと、ワークロードの実際の CPU/メモリ使用率を区別する必要がある。K8S クラスターの情報コンソールでは、一般にワークロードで定義された CPU/メモリ割り当てクォータが表示される。
ストレージ
PVC の正式名称は PersistentVolumeClaim で、永続ボリューム要求である。PV の正式名称は PersistentVolume で、永続ボリュームである。その名の通り、永続ボリュームは K8S がさまざまなタイプのストレージリソース(ブロックストレージ、NAS、オブジェクトストレージ)を記述するためのリソースオブジェクト定義である。各クラウドベンダーが提供するストレージ管理プラグインに基づいて、IaaS レイヤーのストレージリソースを直接 K8S クラスターストレージとして使用できる。
PV の抽象化だけで、K8S ストレージリソースと各クラウドメーカーの IaaS ストレージリソースとのマッピング問題を解決できるように思えるが、なぜさらに PVC リソースオブジェクトを抽象化する必要があるのだろうか。PVC は OOP の抽象クラスに似ている。開発時に抽象クラスを使用するのは、通常オブジェクトの呼び出しとオブジェクトの実装を切り離すためである。Pod が PV ではなく PVC にバインドすることで、Pod のデプロイメントと PV リソースの割り当てが切り離される。
Pod のデプロイメントは一般に開発チームのアプリケーションデプロイメントのサブプロセスに属し、開発者が制御する。PV ストレージリソースの定義と割り当ては DevOps チームに属し、クラスター管理者が制御する。
Pod と PVC は名前空間のアクセス制御範囲内のリソースオブジェクトであり、PV はクラスターのアクセス制御範囲内のリソースオブジェクトである。
PVC/PV モードはストレージリソースとノードリソースを切り離す。つまり、PVC/PV で定義されたストレージリソースはノードリソースから独立しており、動的バインディングを通じてワークロードとともにノード間で移行できる。具体的な動作はクラウドベンダーが提供するストレージプラグインの実装に依存する。
ネットワーク
Service はサーバー側で自動検出メカニズムを実装する。つまり、アプリケーションサービスを提供する Pod グループが同じサービスドメイン名のアクセスアドレスを通じて外部サービスを提供できる。Pod の追加や削除は Service の全体的なサービスパフォーマンスに影響しない。ワークロードは Service を通じて仮想 IP とポートにバインドされ、Pod が割り当てられるノードリソースに対して透過的である。Service とノードネットワークリソースは切り離されており、クラスター全体が負荷移行と障害自己修復の機能を持つ。
Service オブジェクトとは異なり、Ingress は K8S がクラスター外部にサービスを提供するためのリソースオブジェクトである。K8S 以前は、Nginx のリバースプロキシと負荷分散を使用して負荷とトラフィックを制御し、サーバーの処理能力を水平方向に拡張していた。Ingress は本質的に NginxPod であり、Service オブジェクトを通じて公開され、LoadBalancer モードに基づいてリバースプロキシと負荷分散を提供する。Ingress はクラスターのルーター兼アクセスポータルのようである。Ingress にルーティングルールを設定してワークロードと対応するドメイン名解決パスをバインドすることで、1 つの外部 IP アドレスだけで複数クラスター内のサービスを公開でき、IP リソースを節約できる。
Pod のスケジューリングと管理
最も人気のあるコンテナオーケストレーションプラットフォームとして、K8S はプラットフォームレベルの弾力的スケーリングと障害自己修復のソリューションを提供する。K8S クラスターは C/S アーキテクチャに基づいており、Master がクラスターリソースと負荷分散を制御・管理する。Pod は K8S スケジューリングの最小単位である。クラスターリソース管理とスケジューリングを習得するなら、Pod のスケジューリングと管理から始めるべきである。
ヘルスチェック
K8S はプラットフォームレベルの弾力的スケーリングと障害自己修復を実行する前に、アプリケーションサービスの実行状況を把握する必要がある。最も簡単な方法は、コンテナプロセスが実行中かどうかを継続的に確認することである。コンテナプロセスの障害を検出すると、自動的にプロセスの再起動を試みる。多くの場合、プロセスの再起動で問題を解決できるため、このヘルスチェックはシンプルだが非常に効果的で必要不可欠である。
ただし、Java アプリケーションの実行中に OOM 例外やデッドロックが発生しても、JVM プロセス自体は実行中のままの場合がある。この場合、Pod はまだ実行中だが、アプリケーションプロセスはすでにサービスを提供できない状態である。このような状況では、前述のヘルスチェックでは対応できない。K8S は
livenessProbe を提供しており、アプリケーションサービスレベルの異常状態を捕捉し、アプリケーションサービスが正常に動作しているかを総合的に把握できる。
livenessProbe はプロセスヘルスチェックに似ており、コンテナプロセスの健全性を確認する。障害を検出すると、プロセスを再起動して自動修復を行う。
異なる点は、livenessProbe がアプリケーションサービスで定義された HTTP GET API を呼び出して Pod の公開 IP とポートに接続し、リクエストのリターンコードが 200〜399 の範囲内かどうかでコンテナプロセスの動作状態を判定することである。
HTTP に加えて、TCP ソケット接続の成否によってもアプリケーションサービスの動作状態を判断できる。
アプリケーションサービス内部からではなく K8S 側からこの動作状態判断ロジックを実行することで、K8S はアプリケーションサービスレベルの健全性状態を把握できる。
負荷が過大な場合、コンテナプロセスが正常な状態であっても、アプリケーションサービスが正常にサービスを提供できない可能性がある。このような状況は、K8S が readinessProbe を通じて検出する。
Readiness Probe のアクセス方法は LivenessProbe と同じである。HTTP GET API または TCP ソケット接続を通じて、Kubelet プローブが Pod のワークロード状況を K8S 管理ノードに報告できる。
readinessProbe が失敗し、アプリケーションサービスプロセスがリクエストを正常に処理できない場合、Pod は再起動されず、サービスエンドポイントから削除され、サービスのリクエスト負荷を受け取らなくなる。トラフィックのデグレードに似ており、Pod が受信したリクエスト負荷を正しく処理できることが保証される。
自動スケジューリングポリシー
Google が 2015 年に K8S をオープンソース化した際、クラウドネイティブの初期定義を提起した。すなわち、アプリケーションのコンテナ化、マイクロサービス指向アーキテクチャ、およびアプリケーションのコンテナスケジューリング対応である。コンテナスケジューリングは K8S のコア技術である。数百のマイクロサービスコンテナクラスターに直面すると、Pod のスケジューリングとリソース管理は複雑になる。Pod 内のコンテナーグループは実行時に相互に関連し、同じノード上で動作してノードリソースを共有する。アプリケーション負荷が変動すると、コンテナのノードリソース消費も変化する。ノードリソースのキャパシティと可用性もアプリケーションサービスのパフォーマンスと安定性に影響を与える。
K8S の Scheduler コンポーネントは、APIServer が定義する Pod リソースオブジェクトと Kubelet が報告する各ノードのリソース使用状況に基づいて、スケジューリングに適したノードを選択する。Scheduler は Pod の作成、弾力的スケーリング、障害時の負荷移行を制御する。Scheduler はコンテナランタイムの依存関係、リソース要件の設定、デフォルトスケジューリングポリシーに基づいて判断を行う。デフォルトスケジューリングポリシーは通常、スケジューリング先のノードが高可用性、高性能、低レイテンシを保証できるように考慮されている。そのため、ノード選択に特別な目的がない限り、ユーザーはデフォルトのスケジューリング戦略を直接使用することが推奨される。
カスタマイズされたスケジューラはクラスター設定ファイルを操作できる。下図の JSON ファイルに示す通りである。その中で Predicates は、Pod スケジューリング時にルールに適合するノードリソースのみを考慮することを意味する。Priorities は、Predicates ルールのフィルタリングに適合したノードリソース集合を優先順位でソートし、最も重みの高いノードリソースを選択してスケジューリングすることを意味する。たとえば、Scheduler は以下の例のようにリソース要求が最も少ないノードを優先的にスケジューリング対象とする。
Scheduler が APIServer が生成した Pod リソースオブジェクトの定義を検出すると、まず Predicates を通じてルールに適合するノードリソース集合をフィルタリングする。次に、これらのノードリソースに優先順位に基づいて重み付けを行い、最適なノードを選択する。そしてノードリソースを割り当てて Pod を作成する。
Pod は一般に、Pod リソースの需要よりもリソースキャパシティが大きいノードにスケジューリングされる。一般に、ノード OS と K8S 管理コンポーネントは事前に一部のノードリソースを予約するため、割り当て可能なリソースキャパシティは通常ノードの総リソースよりも小さくなる。Scheduler がスケジューリングに参照するリソース設定は、ノードが割り当て可能なリソース、すなわちノードキャパシティを指す。その計算方法は以下の通りである:
Allocatable Capacity = Node Capacity - Kube-Reserved - System-Reserved
Allocatable Capacity は Scheduler がアプリケーションサービス Pod に割り当て可能なノードリソースである
Node Capacity はノードリソースの総量である
Kube-Reserved は K8S バックグラウンドプロセス用の予約リソースで、Kubelet、CRI、CNI などのコンポーネント用である
System-Reserved はノードのオペレーティングシステムのバックグラウンドプロセス用の予約リソースで、sshd、udev などのプロセス用である
前述のワークロードの紹介で、requests と limits を通じてコンテナ実行時のリソース消費量とクォータを定義できることに触れた。ノードリソースのすべてをリソーススケジューリングに使用できるわけではないため、Pod テンプレートを定義する際は、コンテナの resources -> requests と resources -> limits を明確に定義することが推奨される。これにより、ワークロードと K8S コンポーネントとのリソース競合によるスケジューリング失敗を防止できる。
Limits はコンテナリソース使用量の上限を定義し、Requests はコンテナリソースの初期設定を定義する。通常、リソースはコンテナ起動時に requests に基づいて割り当てられる。コンテナ実行時のリソース消費量は一般に requests の割り当て量よりも少ない。下図の通りである。
このピラミッド型のリソース割り当て方式では、多くのリソース断片化が発生する。ワークロード間のリソース競合が発生した場合、K8S は 3 つの QoS レベルを提供する。
Best Effort:Pod が requests と limits を設定しない。この種の Pod は QoS 優先度が最も低い。ノードリソーススケジューリングが不足したり競合が発生したりした場合、この Pod が最初に削除または移行される。
Burstable:Pod が requests と limits を設定するが、requests が limits より小さい。この種の Pod はリソース保証が比較的低い。すなわち、ノードリソース競合により Best Effort Pod がもう存在しない場合、この種の Pod が優先的に削除または移行される。
Guaranteed:Pod が requests と limits を設定し、requests が limits と等しい。この種の Pod は最も高いリソース保証を持つ。QoS レベルは Burstable と Best Effort よりも高い。そのため、アプリケーションサービスの Pod テンプレートを定義する際は、コンテナリソース保証とサービス品質に高い要件を持つ Pod に対して、できる限り適切な requests と limits を設定することが推奨される。
サービス検出
K8S クラスターで実行されるアプリケーションサービスは、主にマイクロサービスアーキテクチャに基づく分散システムである。サービス間には相互呼び出し関係が存在することが多い。アプリケーションサービス Pod をスケジューリングする際、Scheduler は最適なノードを選択してリソースを割り当て、Pod を作成する。コンテナ起動前に、この Pod に ClusterIP アドレスをランダムに割り当てる。そのため、他のアプリケーションサービス Pod がこのアプリケーションサービス Pod と通信したい場合、ランダムに割り当てられた ClusterIP 情報を取得することが困難である。
クライアントサイド自動検出
従来の分散システムでは、ZooKeeper などのサービス間自動検出にクライアントサイド検出方式がよく使用される。クライアントサービスにはサービスレジストリを発見して通信対象のサービスインスタンスを選択できるプローブエージェントが組み込まれている。サーバーサービスインスタンスは自身の状態をサービスレジストリに報告する。クライアントサービスはサービスレジストリ情報を照会することで、応答するサービスインスタンスを選択して起動し、インタラクションを行う。
エージェントによるクライアントサイドサービス検出
サーバーサイド自動検出
K8S が実装するサービス検出はサーバーサイド方式に基づいている。つまり、サーバーサイドの Pod が自身のサービス能力をサービスレジストリに積極的に報告する必要がある。クライアント Pod はサービスレジストリにアクセスでき、サービスレジストリを通じてサービス情報を取得し、応答するサーバーサイド Pod にアクセスする。クライアント Pod は一定の仮想 IP を使用してプロキシサービスを通じて同じサービスにアクセスできるため、どの Pod がサービスを提供しているかを意識する必要がない。
プロキシによるサーバーサイド検出
K8S でサーバーサイドの検出ロジックを実装するリソースオブジェクトが Service である。Service は Pod セレクターとポート番号の定義を通じて、仮想 IP(ClusterIP とも呼ばれる)を Pod グループにバインドできる。
Pod の起動後に ClusterIP がランダムに割り当てられるため、他のサービスの Pod はどのようにしてこの ClusterIP を検出し、通信するのだろうか。主に 2 つの方法がある:
環境変数
Pod の作成時に、それにバインドされた Service オブジェクトも同時に作成され、バインドされたポート番号はすぐにリッスンされる。Service に関連する ClusterIP とポートの値は、自動的に環境変数として Pod に設定され、アプリケーションサービスはこの ClusterIP とポートを通じて外部サービスを提供できる。
Service に対応する環境変数は Pod 起動後に注入できないため、環境変数ベースの ClusterIP とポートのバインディングは Pod 起動プロセス中のみ有効である。
DNS クエリ
K8S はすべての Pod が使用できるプラットフォームレベルの DNS サービスを提供する。Service リソースオブジェクトが作成されると、DNS サービスは対応する Pod がアクセスできる DNS アクセスアドレスをバインドできる。この DNS サービスは、アプリケーションサービスの DNS アクセスアドレスと Pod 起動時に割り当てられる ClusterIP およびポートの対応関係を管理し、DNS アクセスアドレスから対応する Pod へのトラフィック負荷の解決を担当する。
クライアントサービスが ServiceName と対応する名前空間を知っていれば、内部ドメイン名アドレス service-name.namespace.svc.cluster.local を通じてアプリケーションサービス Pod に直接アクセスできる。
Service-name は Service オブジェクト定義の名前である
Namespace は Service と Pod の属する名前空間名である
Svc は Service リソースを表す
Cluster.local は K8S の CoreDNS サービスのデフォルトのクラスター内部アクセスドメイン名である
アドレスドメイン名のプレフィックスを要求する
あとがき
最近、K8S 導入の基本的な知識ポイントを整理している。この記事は「アプリケーション開発の視点から K8S を理解する」の姉妹編であり、リソース管理の視点から K8S に対する新たな理解を提供する。K8S プラットフォームのユーザーは一般にアプリケーション開発者とクラスター管理者に分かれる。K8S を習得する際、クラスター管理者はクラスターレベル、リソースレベル、パフォーマンスレベルから K8S を理解する必要がある。そのためには、Pod のスケジューリングとリソース管理、ストレージとネットワークリソース、Service とトラフィック管理といった最も基本的な知識ポイントを習得し理解する必要がある。
Related Articles
-
A detailed explanation of Hadoop core architecture HDFS
Knowledge Base Team
-
What Does IOT Mean
Knowledge Base Team
-
6 Optional Technologies for Data Storage
Knowledge Base Team
-
What Is Blockchain Technology
Knowledge Base Team
Explore More Special Offers
-
Short Message Service(SMS) & Mail Service
50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00
