このページでは、スケーラブルな GKE クラスタを設計するための一般的なベスト プラクティスについて説明します。これらの推奨事項をすべてのクラスタとワークロードに適用することで、最適なパフォーマンスを実現できます。これらの推奨事項は、大規模なスケーリングを行う予定のクラスタで特に重要となります。このベスト プラクティスは、インフラストラクチャのプロビジョニングを担当する管理者と、Kubernetes コンポーネントとワークロードを準備するデベロッパーを対象としています。
スケーラビリティとは
Kubernetes クラスタでは、スケーラビリティは、サービスレベル目標(SLO)を超えない範囲でのクラスタの拡張能力を意味します。Kubernetes には固有の SLO セットもあります。
Kubernetes は複合的なシステムであり、スケーリングできるかどうかは複数の要素によって決まります。このような要素としては、ノードプール内のノードのタイプと数、ノードプールのタイプと数、使用可能な Pod の数、リソースの Pod への割り当て方法、Service の数、Service の背後で実行されるバックエンド数などがあります。
可用性のベスト プラクティス
リージョンまたはゾーンのコントロール プレーンの選択
アーキテクチャの違いにより、リージョン クラスタは高可用性のほうが適しています。リージョン クラスタには、リージョン内の複数のコンピューティング ゾーンに複数のコントロール プレーンがあり、ゾーンクラスタには、単一のコンピューティング ゾーンに 1 個のコントロール プレーンがあります。
ゾーンクラスタがアップグレードされると、コントロール プレーン VM にはダウンタイムが発生し、Kubernetes API はアップグレードが完了するまで使用できません。
リージョン クラスタでは、IP のローテーション、コントロール プレーン VM のアップグレード、クラスタまたはノードプールのサイズ変更など、クラスタのメンテナンス中もコントロール プレーンを使用できます。リージョン クラスタをアップグレードする場合、ローリング アップグレード中は 3 つのうち 2 つのコントロール プレーン VM が常に実行中となるため、Kubernetes API を引き続き使用できます。同様に、シングルゾーンの停止によってリージョンのコントロール プレーンにダウンタイムが発生することもありません。
ただし、高可用性のリージョン クラスタには次のようなトレードオフがあります。
クラスタの構成の変更は、ゾーンクラスタ内の単一のコントロール プレーンではなく、リージョン クラスタ内のすべてのコントロール プレーンに反映する必要があるため、より長い時間を要します。
リージョン クラスタは、ゾーンクラスタほど頻繁に作成またはアップグレードできない可能性があります。いずれかのゾーンで、容量不足やその他の一時的な問題のために VM を作成できない場合、クラスタの作成またはアップグレードはできません。
こうしたトレードオフのため、ゾーンクラスタとリージョン クラスタのユースケースは異なります。
- 可用性の重要度が比較的低い場合は、クラスタを迅速に作成またはアップグレードするためにゾーンクラスタを使用します。
- 柔軟性よりも可用性が重要な場合は、リージョン クラスタを使用します。
クラスタを作成した後は変更できないため、クラスタを作成する際にはクラスタタイプを慎重に選択してください。変更できないことにより、新しいクラスタを作成してからトラフィックを移行する必要があります。クラスタ間で本番環境トラフィックを移行することは可能ですが、大規模な移行は困難です。
マルチゾーンまたはシングルゾーンのノードプールの選択
高可用性を実現するため、Kubernetes コントロール プレーンとそのノードを異なるゾーンに分散する必要があります。GKE には、シングルゾーンとマルチゾーンの 2 種類のノードプールがあります。
高可用性アプリケーションをデプロイするには、ゾーン全体のノードを均等に分散するマルチゾーン ノードプールを使用して、リージョン内の複数のコンピューティング ゾーンにワークロードを分散します。
すべてのノードが同じゾーンにある場合、ゾーンがアクセス不能になると Pod のスケジューリングができなくなります。マルチゾーンのノードプールの使用には、次のようなトレードオフがあります。
GPU は特定のゾーンでのみ使用できます。リージョン内のすべてのゾーンで取得できるとは限りません。
単一リージョン内のゾーン間のラウンドトリップ レイテンシは、単一のゾーン内のリソース間のレイテンシよりも大きくなる場合があります。ほとんどのワークロードで、この違いによる大きな影響はないと考えられます。
同じリージョン内でのゾーン間の下り(外向き)トラフィックの料金については、Compute Engine の料金ページをご覧ください。
スケーリングのベスト プラクティス
ベース インフラストラクチャ
Kubernetes ワークロードにはネットワーキング、コンピューティング、ストレージが必要です。また、ポッドを実行するのに十分な CPU とメモリを用意する必要があります。しかし、GKE クラスタのパフォーマンスとスケーラビリティに影響しうる、基盤インフラストラクチャのパラメータが他にもあります。
クラスタのネットワーキング
新しい GKE クラスタを設定する際は、VPC ネイティブ クラスタを使用するのがネットワーキングのデフォルトであり、推奨オプションです。VPC ネイティブ クラスタを使用すると、より大きい規模のワークロードを扱えることや、より多くのノードを利用できることなどのメリットがあります。
このモードの VPC ネットワークでは、すべての Pod IP アドレスにセカンダリ範囲があります。各ノードには、そのノードが所有する Pod IP アドレスのセカンダリ範囲のスライスが割り当てられます。これにより、VPC ネットワークは、カスタムルートに依存せずに Pod にトラフィックをルーティングする方法をネイティブに理解できます。1 つの VPC ネットワークに最大 15,000 個の VM を配置できます。
別の方法として、ルートベース クラスタを使用する方法もあります。これは非推奨で、サポートされるノード数は 1,500 ノードまでです。大規模なワークロードにルートベース クラスタは適していません。VPC ルートの割り当てを消費し、VPC ネイティブ ネットワークの他のメリットもありません。ルートベース クラスタは、新しいノードごとに、VPC ネットワークのルーティング テーブルに新しいカスタムルートを追加することにより機能します。
限定公開クラスタ
通常の GKE クラスタでは、すべてのノードにパブリック IP アドレスが割り振られています。限定公開クラスタでは、インターネットとのインバウンド接続とアウトバウンド接続からノードを分離するために、ノードには内部 IP アドレスのみが割り振られます。GKE は、VPC ネットワーク ピアリングを使用して、Kubernetes API サーバーを実行している VM をクラスタの残りの部分と接続します。これにより、トラフィックはプライベート IP アドレスを使用してルーティングされるため、GKE コントロール プレーンとノード間のスループットが向上します。
限定公開クラスタを使用すると、ノードがインターネットに公開されないというセキュリティ上のメリットがあります。
クラスタのロード バランシング
Compute Engine の GKE Ingress と Cloud Load Balancing は、ロードバランサを構成してデプロイし、Kubernetes ワークロードをクラスタの外部と公共のインターネットに公開します。GKE Ingress と Service コントローラは、GKE ワークロードに代わり、転送ルール、URL マップ、バックエンド サービス、ネットワーク エンドポイント グループなどのオブジェクトをデプロイします。それぞれのリソースには割り当てと上限があり、これらの上限は GKE にも適用されます。特定の Cloud Load Balancing リソースが割り当て量に達すると、特定の Ingress または Service が正しくデプロイされず、リソースのイベントにエラーが表示されます。
次の表は、GKE Ingress と Service を使用する際のスケーリングの上限を示しています。
ロードバランサ | クラスタあたりのノード数の上限 |
---|---|
内部パススルー ネットワーク ロードバランサ |
|
外部パススルー ネットワーク ロードバランサ | ゾーンあたり 1,000 個のノード |
外部アプリケーション ロードバランサ |
|
内部アプリケーション ロードバランサ | ノード数の上限なし |
この上限を増やす場合は、Google Cloud のセールスチームまでお問い合わせください。
DNS
GKE のサービス ディスカバリは kube-dns によって提供されます。これは、クラスタ内で実行される Pod に DNS 解決を提供する一元化されたリソースです。非常に大規模なクラスタや、リクエストの負荷が高いワークロードの場合、これはボトルネックになる可能性があります。GKE は、クラスタのサイズに基づいて kube-dn を自動的にスケーリングし、容量を増やします。まだ十分な容量でない場合、GKE は、NodeLocal DNSCache が設定された各ノードに DNS クエリの解決処理を分散します。この場合、各 GKE ノードに用意されたローカルの DNS キャッシュでクエリが処理されます。これにより、負荷が分散され、応答時間が短縮されます。
VPC ネイティブ クラスタでの IP アドレスの管理
VPC ネイティブ クラスタは、次の 3 つの IP アドレス範囲を使用します。
- ノード サブネットのプライマリ範囲: デフォルトは /20(4,092 個の IP アドレス)です。
- Pod サブネットのセカンダリ範囲: デフォルトは /14(262,144 個の IP アドレス)です。ただし、Pod サブネットは構成できます。
- Service サブネットのセカンダリ範囲: デフォルトは /20(4,096 アドレス)です。この Service サブネットを作成した後にこの範囲を変更することはできません。
詳細については、VPC ネイティブ クラスタの IP 範囲をご覧ください。
IP アドレスの制限と推奨事項:
- ノードの上限: ノードの上限は、ノードあたりのプライマリ IP アドレスと Pod IP アドレスの両方によって決まります。新しいノードをプロビジョニングするには、ノードと Pod の両方の IP アドレス範囲に十分なアドレスが必要です。デフォルトでは、Pod の IP アドレスの制限のため、作成できるノードは 1,024 個までです。
- ノードあたりの Pod 数の上限: デフォルトでは、ノードあたりの Pod 数の上限は 110 です。ただし、ノードあたりの Pod 数を減らして効率的に使用できるように、より小さい Pod CIDR を構成できます。
- RFC 1918 を超えるスケーリング: RFC 1918 で定義されたプライベート空間で使用可能な IP アドレスよりも多くの IP アドレスが必要な場合は、RFC 1918 以外のプライベート アドレスまたは PUPI を使用して、柔軟性を高めることをおすすめします。
- Service と Pod のセカンダリ IP アドレス範囲: デフォルトでは、4,096 個の Service を構成できます。ただし、Service のサブネット範囲を選択することで、より多くの Service を構成できます。作成後にセカンダリ範囲を変更することはできません。クラスタを作成する際、予想されるクラスタの増加に対応できる十分な大きさの範囲を選択するようにしてください。 不連続マルチ Pod CIDR を使用して、後から Pod の IP アドレスを追加することもできます。詳細については、Pod 用の空き IP アドレス空間が不足しているをご覧ください。
詳細については、ノード制限範囲と GKE への移行時に IP アドレスを計画するをご覧ください。
パフォーマンス向上のためのノードの構成
GKE ノードは標準の Google Cloud の仮想マシンです。たとえば、コアの数やディスクのサイズなどのパラメータには、GKE クラスタのパフォーマンスに影響するものもあります。
Pod の初期化時間の短縮
イメージ ストリーミングを使用すると、ワークロードがリクエストしたときに、対象となるコンテナ イメージからデータをストリーミングできます。これにより、初期化時間を短縮できます。
下り(外向き)トラフィック
Google Cloud では、インスタンスに割り当てられるマシンタイプとコアの数によってネットワーク容量が決まります。下り(外向き)の最大帯域幅は 1~32 Gbps とさまざまですが、デフォルトの e2-medium-2 マシンの最大下り(外向き)帯域幅は 2 Gbps です。帯域幅の上限の詳細については、共有コア マシンタイプをご覧ください。
IOPS とディスク スループット
Google Cloud では、永続ディスクのサイズによって、IOPS とディスクのスループットが決まります。通常、GKE は Persistent Disk をブートディスクとして使用し、Kubernetes の永続ボリュームをバックアップします。ディスクサイズを増やすと、IOPS とスループットの両方が特定の上限まで向上します。
永続ディスクへの書き込み操作を行うたびに、仮想マシン インスタンスの累積下り(外向き)ネットワークの上限に向かって累積されます。したがって、ディスク(特に SSD)の IOPS パフォーマンスは、ディスクサイズだけでなく、インスタンスの vCPU の数によっても異なります。書き込みスループットに対する下り(外向き)ネットワークの上限のため、コア数の少ない VM のほうが書き込み IOPS が低くなります。
仮想マシン インスタンスの CPU が不足している場合、アプリケーションは IOPS の上限に近づくことはできなくなります。原則として、予想されるトラフィックの 2,000~2,500 IOPS ごとに 1 個の CPU が必要です。
大容量または多数のディスクを必要とするワークロードでは、1 つの VM にアタッチできる PD の数の上限を考慮する必要があります。通常の VM では、上限は合計サイズが 64 TB の 128 ディスクであり、共有コア VM の上限は、合計サイズが 3 TB の PD 16 個です。この上限を適用するのは Kubernetes ではなく、Google Cloud です。
コントロール プレーンの指標をモニタリングする
利用可能なコントロール プレーンの指標を使用して、モニタリング ダッシュボードを構成します。コントロール プレーンの指標を使用すると、クラスタの正常性や、クラスタ構成変更(追加のワークロードやサードパーティ コンポーネントのデプロイなど)の結果を監視できます。また、問題のトラブルシューティングを行うこともできます。
モニタリングすべき最も重要な指標の一つは、Kubernetes API のレイテンシです。レイテンシの増加は、システムが過負荷状態になったことを示します。大量のデータを転送する LIST 呼び出しは、小さいリクエストよりもレイテンシが大幅に高くなることが予想されます。
Kubernetes API のレイテンシの増加は、サードパーティのアドミッション Webhook の応答遅延が原因で発生することもあります。指標を使用して Webhook のレイテンシを測定することで、このような一般的な問題を検出できます。
Kubernetes デベロッパーのベスト プラクティス
定期的なリストではなく list と watch のパターンを使用する
Kubernetes デベロッパーは、次の要件を満たすコンポーネントの作成が必要になることがあります。
- 特定の Kubernetes オブジェクトのリストを定期的に取得する。
- 複数のインスタンスで実行される(DaemonSet の場合は各ノードに分散)。
このようなコンポーネントは、定期的に取得されるオブジェクトの状態が変化していない場合でも、kube-apiserver の負荷の急増を招くことがあります。
最も簡単な方法は、定期的に LIST 呼び出しを使用することです。ただし、すべてのオブジェクトをメモリに読み込んでシリアル化し、毎回転送する必要があるため、これは呼び出し元とサーバーの両方にとって非効率的で、コストの高いアプローチになります。LIST リクエストを過度に使用すると、コントロール プレーンが過負荷状態になったり、リクエストのスロットリングが頻繁に発生したりする可能性があります。
このような場合、LIST 呼び出しに resourceVersion=0 パラメータを設定することでコンポーネントを改善できます。これにより、kube-apiserver はインメモリ オブジェクトのキャッシュを使用できるようになり、kube-apiserver と etcd データベースとの間の内部インタラクションや、関連する処理の数を減らすことができます。
繰り返し可能な LIST 呼び出しを使うのは避け、list と watch のパターンに置き換えることを強くおすすめします。オブジェクトの一覧を 1 回取得し、Watch API を使用して状態の増分変化を取得します。この方法では、定期的な LIST 呼び出しと比較して処理時間が短くなり、トラフィックを最小限に抑えることができます。オブジェクトが変更されていなければ、追加の負荷は発生しません。
Go 言語を使用している場合は、このパターンを実装している Go パッケージがあります。SharedInformer と SharedInformerFactory をご確認ください。
watch と list が生成する不要なトラフィックを制限する
Kubernetes は、内部処理で watch を使用してオブジェクトの更新に関する通知を送信します。定期的な LIST 呼び出しよりも必要なリソースがはるかに少ない watch でも、大規模なクラスタでの処理ではクラスタ リソースを大量に消費し、クラスタのパフォーマンスに影響する可能性があります。最も大きな悪影響を及ぼすのは、頻繁に変化するオブジェクトを複数の場所から観察する watch の作成です。たとえば、すべてのノードで実行中のコンポーネントから、Pod に関するデータをモニタリングする場合です。クラスタにサードパーティのコードや拡張機能をインストールすると、このような watch が作成されることがあります。
次のベスト プラクティスをおすすめします。
- watch と LIST の呼び出しによって生成される不要な処理とトラフィックを削減します。
- 頻繁に変更されるオブジェクトを複数の場所(DaemonSet など)からモニタリングする watch の作成を避けます。
- (強く推奨)単一ノードで必要なデータを監視して処理する中央コントローラを作成します。
- オブジェクトのサブセットのみを監視します。たとえば、各ノード上の kubelet で、同じノードにスケジューリングされた Pod のみを監視します。
- 大量の watch または LIST 呼び出しを行ってクラスタのパフォーマンスに影響を与える可能性がある、サードパーティのコンポーネントや拡張機能のデプロイを避けます。
Kubernetes オブジェクトのマニフェスト サイズを制限する
大規模なワークロードのサイズ変更や更新など、高スループットの Pod で高速なオペレーションを実行する必要がある場合は、Pod のマニフェスト サイズを最小限に抑えてください(理想的には 10 KiB 未満)。
Kubernetes はリソース マニフェストを etcd に保存します。list と watch を使用する場合を含め、リソースが取得されるたびにマニフェスト全体が送信されます。
マニフェストのサイズには次の制限があります。
- etcd 内の各オブジェクトの最大サイズ: 約 1.5 MiB。
- クラスタ内の etcd オブジェクトの割り当ての合計: 事前に構成された割り当てサイズは 6 GiB です。これには、直近の 150 秒間のクラスタ履歴にあるオブジェクトに対する更新がすべて記録されている変更ログも含まれます。
- トラフィックが多い期間のコントロール プレーンのパフォーマンス: マニフェストのサイズが大きくなると、API サーバーの負荷が増加します。
ほとんど処理されない単一オブジェクトの場合、マニフェストのサイズが 1.5 MiB 未満であれば、通常は問題になりません。ただし、非常に大きなワークロードの Pod など、頻繁に処理されるオブジェクトが多くなってマニフェストのサイズが 10 KiB を超えると、API 呼び出しのレイテンシが増加して全体的なパフォーマンスが低下する可能性があります。特に、list と watch はマニフェストのサイズから大きな影響を受ける可能性があります。また、API サーバー トラフィックが多い期間は、直近 150 秒間のリビジョン数が急激に蓄積されるため、etcd の割り当てが問題になる可能性があります。
Pod のマニフェスト サイズを削減するために、Kubernetes ConfigMap を利用して構成の一部(特に、クラスタ内の複数の Pod によって共有される部分)を保存することができます。たとえば、多くの場合、環境変数はワークロード内のすべての Pod で共有されます。
ConfigMap オブジェクトの数、サイズ、処理頻度が Pod と同じ程度になると、ConfigMap オブジェクトでも同様の問題が発生する可能性があります。部分的な構成の抽出は、全体的なトラフィックの減少につながる場合に最も役立ちます。
デフォルトのサービス アカウントの自動マウントを無効にする
Pod で実行されるロジックで Kubernetes API にアクセスする必要がない場合は、関連する Secret と watch が作成されないように、サービス アカウントのデフォルトの自動マウントを無効にする必要があります。
サービス アカウントを指定せずに Pod を作成すると、Kubernetes は自動的に次の処理を実行します。
- デフォルトのサービス アカウントを Pod に割り当てます。
- サービス アカウントの認証情報を Pod の Secret としてマウントします。
- マウントされた Secret ごとに、kubelet が watch を作成し、各ノードでその Secret に対する変更を監視します。
大規模なクラスタでは、これらの処理によって不要な watch が大量に生成され、kube-apiserver に多大な負荷がかかる可能性があります。
API リクエストに JSON ではなくプロトコル バッファを使用する
Kubernetes API のコンセプトで説明されているように、スケーラビリティの高いコンポーネントを実装する場合は、プロトコル バッファを使用します。
Kubernetes REST API は、オブジェクトのシリアル化形式として JSON とプロトコル バッファをサポートしています。デフォルトでは JSON が使用されますが、プロトコル バッファは CPU 負荷の高い処理が少なく、ネットワーク上で送信されるデータが少ないため、大規模な環境ではこちらのほうが効率的です。JSON の処理に関連するオーバーヘッドのため、大きなサイズのデータを一覧表示する際にタイムアウトが発生することがあります。