OpenAI が Kubernetes を 7,500 ノードにスケール
OpenAI は、機械学習研究チームがコードを変更せずにワークロードをスケールできるよう、単一の Kubernetes クラスタを 7,500 ノードにスケールしました。このインフラは、単一の Pod がしばしばハードウェア効率を最大化するために NVLink と GPUDirect を介してノード全体を占有するような大規模ジョブをサポートします。
ワークロードの特性
OpenAI の Kubernetes ワークロードは、一般的なエンタープライズアプリケーションとは大きく異なります。主な焦点は、ハードウェアリソースへのアクセスを重視し、ビンパッキングやフラグメンテーションよりも大規模機械学習ジョブにあります。
- リソース利用率: Pod は通常、GPU が NVLink 経由で直接通信し、NIC が GPUDirect を介して通信できるように、ノード全体を占有します。これにより、NUMA や CPU 競合に基づく複雑なスケジューリングの必要が減ります。
- ジョブの性質: ジョブはしばしば MPI(Message Passing Interface)で実行され、「半状態的」になります。MPI コミュニケータ内の 1 つの Pod が死んだ場合、ジョブ全体が停止し、最後のチェックポイントから再開されます。
- ネットワークパターン: Kubernetes のロードバランシング、HTTPS トラフィック、サービスディスカバリへの依存は最小限です。Pod は MPI over SSH を使用して Pod IP アドレスで直接通信します。
- ストレージ: システムは主にブロブストレージを使用してデータセットのストリーミングとチェックポイントを行い、POSIX セマンティクスが必要な場合にのみ PersistentVolume を使用します。
ネットワーク最適化
7,500 ノードと約 200,000 の同時 IP アドレスをサポートするため、OpenAI は Flannel から Azure VMSS(Virtual Machine Scale Sets)向けのネイティブ Pod ネットワークと関連 CNI プラグインへ移行しました。
高スループットネットワーキング
エイリアスベースの IP アドレッシングを使用し、ルートベースのネットワーキングを避けることで、実効ルート数の制限を回避し、ホストレベルのネットワークスループットを確保しました。このアプローチはカプセル化を排除し、設定を簡素化するとともに、MTU に起因するパケットフラグメンテーション問題も回避します。
ネットワークモニタリング
OpenAI は iptables の mangle ルールを使ってパケットを内部向けかインターネット向けかでタグ付けします。これらのカウンタはオープンソースの iptables-exporter で追跡され、Prometheus に統合され、研究者がネットワークボトルネックを可視化できるようにしています。
クラスタ分離
クラスタはハブ‑アンド‑スポークモデルで構成されます。研究者はハブ経由で個々のクラスタ(スポーク)にアクセスできますが、クラスタ同士は通信できないようにして障害の分離を確保しています。
API サーバーと etcd の安定性
スケール時のクラスタヘルスを維持するため、OpenAI は API サーバーと etcd をクラスタ外の専用ノードで実行しています。
- デプロイ: 最大規模のクラスタでは API サーバーを 5 台、etcd ノードを 5 台使用して負荷を分散しています。
- リソース使用量: API サーバーのメモリ使用量はクラスタサイズに比例して増加し、7,500 ノードで最大 70GB のヒープに達します。
- EndpointSlices: Kubernetes 1.17 で導入された EndpointSlices の採用により、Endpoints に対する WATCH の負荷が 1,000 倍削減され、ノードの増減時に発生する $N^2$ 帯域幅スパイクを防止しました。
- スケーリング戦略: API サーバーへの過負荷を防ぐため、OpenAI は API とのやり取りに DaemonSet の使用を避け、オートスケーリング時の新規ノード追加を平滑化しています。
Prometheus と Grafana によるモニタリング
OpenAI は時系列メトリクスに Prometheus、可視化に Grafana を使用していますが、スケーリング時に大きな課題に直面しました。
- メモリリーク:
/api/v1/seriesAPI のバグにより、Grafana がヒストグラムメトリクスをクエリした際にメモリ消費が無制限に増大し、Prometheus がクラッシュしました。OpenAI は Context を用いたタイムアウトを実装して Prometheus をパッチしました。 - WAL リプレイ性能: Write‑Ahead‑Log(WAL)リプレイにより起動時間が数時間遅延しました。
GOMAXPROCS=24を設定することで、高コアサーバ上の CPU 競合を減らし、改善しました。 - メトリクスフィルタリング: データ量を管理するため、Prometheus ルールで細かい、または未使用のメトリクスを「ドロップ」しています。
ノードヘルスと GPU 検証
受動的および能動的ヘルスチェックの組み合わせで、問題のあるノードを検出し除去する自動化が行われています。
受動的ヘルスチェック
システムはネットワーク到達性、ディスクヘルス、GPU エラー(例: 修正不可能な ECC エラー)を dcgm-exporter と NVML Device Query API で監視します。障害ノードは自動的にコーデンされ、重大な障害は Pod の退避と最終的な VM 終了を引き起こします。
能動的 GPU テスト
一部の GPU 問題はエラーコードを出さないため、OpenAI は「プレフライト」システムを使用します。新規ノードは taint とラベルを付けてクラスタに参加し、DaemonSet が徹底的な GPU テストを実行した後に taint を除去し、一般ワークロードを許可します。
リソース管理とスケジューリング
OpenAI は競合する研究チーム向けにリソース割り当てとギャングスケジューリングを行うカスタムメカニズムを実装しました。
- チーム Taint:
team-resource-managerサービスは taint(openai.com/team=teamname:NoSchedule)と admission webhook を使用して、特定のノード容量を研究チームに割り当てつつ、低優先度の Pod が未使用容量を借用できるようにします。 - リソースバルーン: クラスタオートスケーラーがアイドルノードを削除するのを防ぐため(起動遅延と API サーバー負荷が増大するため)、OpenAI は「バルーン」Deployment を使用します。これらの低優先度 Pod はスペースを占有しますが、実際の作業が来た瞬間に即座に退避されます。
- ギャングスケジューリング: 複数の実験がそれぞれ必要クラスタ容量の半分を占有しデッドロックするのを防ぐため、OpenAI は Kubernetes 1.18 で導入された Coscheduling プラグインを使用し、StatefulSet のすべての Pod がトレーニング開始前にスケジュールされるようにしています。
残された課題
OpenAI が特定した未解決の主要課題は 2 つです。
- メトリクスストレージ: Prometheus TSDB エンジンのコンパクトが遅く、"too many samples" エラーが頻発します。OpenAI は別の Prometheus 互換ストレージとクエリエンジンへ移行中です。
- トラフィックシェーピング: 研究者が使用する総インターネット帯域が外部データセットやソフトウェアパッケージリポジトリに負荷をかけるため、Pod ネットワークトラフィックのシェーピングが必要です。