Kubernetesのノード数を7500に拡張(2/2)

インフラ

1.Kubernetesのノード数を7500に拡張(2/2)まとめ

・私達は誤動作しているノードを検出してクラスターから削除する処理は自動化
・GPUは既存ツールでは捕捉できないエラーがあるため内製の仕組みを利用
・Prometheusによって収集される膨大な量の計測データについては今後も課題

2.Kubernetesの大規模運用の課題

以下、openai.comより「Scaling Kubernetes to 7,500 Nodes」の意訳です。元記事の投稿は2021年1月25日、Benjamin ChessさんとEric Siglerさんによる投稿です。

アイキャッチ画像のクレジットはPhoto by Maximilian Weisbecker on Unsplash

PrometheusとGrafanaを使用した計測
Prometheusを使用して時系列で計測データを収集し、Grafanaを使用してグラフ、ダッシュボード、およびアラートを収集します。まず、様々な指標と視覚化のための優れたダッシュボードを収集するkube-prometheusの運用から始めました。
時間の経過とともに、独自のダッシュボード、指標、アラートの多くを追加してきました。

ノードを追加するにつれて、Prometheusによって収集される膨大な量の計測データに苦労する事になりました。kube-prometheusは多くの有用なデータを公開していますが、実際には私達も見たことがないものもあれば、きめ細かくて効果的に収集、保存、クエリを実行できないものもあります。Prometheusルールを使用して、これらの計測データの一部が取り込まれないように「削除」します。

しばらくの間、Prometheusがますます多くのメモリを消費し、最終的にメモリ不足エラー(OOM:Out-Of-Memory)でコンテナがクラッシュするという問題に苦労しました。これは、アプリケーションに大量のメモリ容量を投入した後でも発生するようでした。さらに悪いことに、クラッシュした場合、起動時にログ先行書き込みファイル(WAL)を再生してから再び使用できるようになるまでに何時間もかかりました。

最終的に、これらのOOMの発生源を追跡して、GrafanaとPrometheusの間の相互作用を特定しました。GrafanaはPrometheusで/api/v1/seriesのAPIを使用して{le!= “”}のクエリを実行していました。(これは基本的に、「全てのヒストグラムデータを取得」の意味です)

/api/v1/seriesは時間とデータサイズの両方の観点で無制限になっていました。取得結果が多きくなるクエリの場合、これはより多くのメモリと時間を消費し続けます。また、リクエスト元が接続をあきらめて接続を切った後も、処理をし続けます。私達の環境では十分なメモリがなかったため、Prometheusは最終的にクラッシュしていました。Prometheusにパッチを適用して、このAPIにもタイムアウトを強制出来るようにし、完全に修正しました。

Prometheusがクラッシュする頻度ははるかに少ないものの、再起動が必要になったときは、WALの再生に時間がかかる事は問題のままでした。Prometheusが新しい計測データを収集してクエリを処理するまでに、すべてのWALログを再生するのに何時間もかかることがよくありました。Robust Perceptionの助けを借りて、GOMAXPROCS = 24を適用すると大きな改善が見られました。Prometheusは、WALの再生中にすべてのコアを使用しようとします。コアの数が多いサーバーの場合、処理が競合してしまい全てのパフォーマンスが低下します。

現在は、後述の「未解決の問題」セクションで説明するように、監視能力を向上させるための新しいオプションを模索しています。

異常検知(ヘルスチェック)

クラスターがこれほど大きいので、私達は誤動作しているノードを検出してクラスターから削除する処理は自動化しています。時が経つにつれて、私達は多くの異常検知(ヘルスチェック)システムを構築してきました。

パッシブヘルスチェック
一部のヘルスチェックは受動的(パッシブ)であり、常にすべてのノードで実行されます。これらは、ネットワークの到達可能性、ディスクの不良またはディスク領域不足、GPUエラーなどの基本的なシステムリソースを監視します。GPUは様々な方法で問題を示しますが、簡単な一般的な問題は「修正不可能なECCエラー(Uncorrectable ECC error)」です。

NvidiaのデータセンターGPUマネージャー(DCGM:Data Center GPU Manager)ツールを使用すると、このエラーやその他の多くの「Xid」エラーを簡単に照会できます。 これらのエラーを追跡する1つの方法は、dcgm-exporterを使用して、監視システムであるPrometheusにメトリックを取り込むことです。 これはDCGM_FI_DEV_XID_ERRORSとして表示され、最後に発生したエラーコードに設定されます。 さらに、NVMLデバイスクエリAPIは、GPUの状態と動作に関するより詳細な情報を公開します。

エラーを検出したら、GPUまたはシステムをリセットすることで修正できることがよくありますが、場合によっては、基盤となるGPUを物理的に交換する必要があります。

別の形式のヘルスチェックは、アップストリームクラウドプロバイダーからのメンテナンスイベントを追跡します。主要なクラウドプロバイダーはそれぞれ、現在の仮想マシン(VM)に、最終的に中断を引き起こすようなメンテナンスイベントが予定されているかどうかを知る方法を公開しています。基盤となるハイパーバイザーパッチを適用したり、物理ノードを他のハードウェアに交換したりできるように、VMを再起動する必要がある場合があります。

これらのパッシブヘルスチェックは、すべてのノードのバックグラウンドで常に実行されます。ヘルスチェックが失敗し始めると、ノードは自動的に封鎖されるため、ノードで新しいポッドがスケジュールされることはありません。より深刻なヘルスチェックの失敗については、ポッドの削除を試みて、現在実行中のすべてのポッドをすぐに終了するように要求します。このポッド削除要求の発生を許可するかどうかを決定するのは、ポッドディスラプションバジェット(Pod Disruption Budget)を介して構成可能なポッド自体です。
最終的に、すべてのポッドが終了した後、または7日が経過した後(SLAの一部)、VMを強制的に終了します。

アクティブGPUテスト
残念ながら、全てのGPUの問題がDCGMを通じて表示されるエラーコードとして表現されるわけではありません。GPUを実行して追加の問題を検出し、ハードウェアとドライバーが期待どおりに動作していることを確認する独自のテストライブラリを構築しました。これらのテストはバックグラウンドで実行することはできません。実行するには、GPUを数秒または数分間排他的に使用する必要があります。

最初に、「プリフライト(preflight)」と呼ばれるシステムで、起動時にノードでこれらのテストを実行します。すべてのノードは、「プリフライト」の痕跡(taint)とラベルが適用された状態でクラスターに参加します。このラベルがあると通常のポッドとしてノードでスケジュールされる事はなくなります。DaemonSetは、このラベルが付いた全てのノードでプリフライトテストポッドを実行するように構成されています。テストが正常に完了すると、テスト自体で痕跡とラベルを削除し、ノードを一般的に使用できるようになります。

また、ノードの存続期間中、これらのテストを定期的に実行します。これをCronJobとして実行し、クラスター内の使用可能な任意のノードに到達できるようにします。これは確かに少しランダムで、どのノードをテストするかについて制御されていませんが、時間の経過とともに、最小限の調整や中断で十分なカバレッジが提供されることがわかりました。

リソースの使用量制限(Quotas & resource usage)

クラスターをスケールアップすると、研究者は割り当てられた全ての容量を取得するのが難しいことに気付き始めました。従来のジョブスケジューリングシステムには、Kubernetesにはない、競合するチーム間で公平に作業を実行するために利用できるさまざまな機能があります。時間の経過とともに、これらのジョブスケジューリングシステムからインスピレーションを得て、Kubernetesネイティブの方法でいくつかの機能を構築しました。

チームの痕跡
各クラスターには、複数の機能を持つ「team-resource-manager」というサービスがあります。そのデータソースは、特定のクラスターに容量を持つすべての研究チームのタプル(ノードセレクター、適用するチームラベル、割り当て量)を指定するConfigMapです。
これをクラスター内の現在のノードと調整し、openai.com/team=teamname:NoScheduleで適切な数のノードにチームの痕跡(taint)を残します。

「team-resource-manager」には、各ジョブが送信されるときに、送信者のチームメンバーシップに基づいて対応する許容範囲が適用されるように、アドミッションWebhookサービスもあります。痕跡を使用すると、Kubernetesポッドスケジューラを柔軟に制約できます。例えば、優先度の低いポッドに「任意の」許容範囲を設定できるため、チームは大規模な調整を必要とせずに互いの容量を借りることができます。

CPUとGPUのバルーン(CPU & GPU balloons)
cluster-autoscalerを使用してVMでバックアップされたクラスターを動的にスケーリングすることに加えて、クラスター内の異常なメンバーを修正(削除および再追加)するために使用します。これを行うには、クラスターの「最小サイズ」をゼロに設定し、クラスターの「最大サイズ」を使用可能な容量に設定します。ただし、cluster-autoscalerは、アイドル状態のノードを検出すると、必要な容量のみにスケールダウンしようとします。複数の理由(VMのスピンアップ遅延、事前に割り当てられたコスト、前述のAPIサーバーへの影響)により、このアイドルスケーリングは理想的ではありません。

そこで、CPUのみのホストとGPUホストの両方にバルーンデプロイメントを導入しました。このデプロイメントには、「最大サイズ」の数の低優先度ポッドを含むReplicaSetが含まれています。これらのポッドはノード内のリソースを占有するため、オートスケーラーはそれらをアイドル状態とは見なしません。ただし、優先度が低いため、スケジューラはすぐにそれらを削除して、実際の作業のためのスペースを確保できます。
(DaemonSetがノード上のアイドルワークロードと見なされないようにするために、DaemonSetの代わりにDeploymentを使用することを選択しました)
注意すべき点の1つは、ポッドの非親和性を使用して、ポッドがノード全体に均等に分散されるようにすることです。以前のバージョンのKubernetesスケジューラには、ポッドの非アフィニティに関する\(O(N ^ 2)\)のパフォーマンスの問題がありました。これは、Kubernetes1.18以降で修正されています。

並列システム用スケジューリングアルゴリズム(Gang scheduling)

私たちの実験では、多くの場合、1つ以上のStatefulSetが含まれ、それぞれがトレーニング作業の異なる部分を操作します。オプティマイザーの場合、研究者はトレーニングを行う前に、StatefulSetのすべてのメンバーをスケジュールする必要があります。(オプティマイザーメンバー間の調整にMPIを使用することが多く、MPIはグループメンバーシップの変更に敏感であるため)

ただし、デフォルトでは、Kubernetesは、あるStatefulSetからの全てのリクエストの実行を別のStatefulSetよりも優先する必要はありません。たとえば、2つの実験がそれぞれクラスターの容量の100%を要求した場合、1つの実験のすべてをスケジュールするのではなく、Kubernetesは各実験のポッドの半分だけをスケジュールし、どちらの実験も進行できないデッドロックにつながる可能性があります。

カスタムスケジューラを必要とするいくつかのことを試しましたが、通常のポッドのスケジュール方法と競合する極端なケースに遭遇しました。Kubernetes 1.18では、コアKubernetesスケジューラーにプラグインアーキテクチャが導入され、このような機能をネイティブに追加するのがはるかに簡単になりました。 私たちは最近、この問題を解決するための良い方法としてCoschedulingプラグインを採用しました。

未解決の問題

Kubernetesクラスターをスケールアップする際には、まだ対処すべき多くの問題があります。 それらのいくつかは次のとおりです。

計測データ
私たちの規模では、Prometheusの組み込みTSDBストレージエンジンの圧縮が遅く、再起動するたびにWAL(ログ先行書き込み)を再生するのに長い時間が必要であるという多くの問題がありました。また、クエリによって「クエリ処理で読み込まれるサンプルが多すぎる」というエラーが発生する傾向があります。現在、別のPrometheus互換のストレージおよびクエリエンジンに移行中です。それがどうなるかについての将来のブログ投稿を楽しみにしています!

ポッドのネットワーク帯域の制限(Pod network traffic shaping)
クラスターをスケールアップすると、各ポッドは一定量のインターネット帯域幅を利用できるように計算されます。1人あたりのインターネット帯域幅の総要件はかなりのものになり、私たちの研究者は、ダウンロード用のデータセットやインストールするソフトウェアパッケージなど、インターネット上の他のサイトに意図せずに大きなリソース負荷をかけてしまう事態が発生する事になりました。

結論

Kubernetesは、私達の研究ニーズに対して非常に柔軟なプラットフォームであることがわかりました。Kubernetesには、私達が課した最も要求の厳しいワークロードに合わせてスケールアップする機能があります。まだ改善が必要な領域はたくさんありますが、OpenAIのスーパーコンピューティングチームは、Kubernetesがどのように拡張できるかを引き続き調査します。このような作業を面白そうと感じた場合は、OpenAIの採用チームへの応募を検討してください!

3.Kubernetesのノード数を7500に拡張(2/2)関連リンク

1)openai.com
Scaling Kubernetes to 7,500 Nodes

タイトルとURLをコピーしました