TT Lab
はじめる
学ぶ 学習パス コース

GPU Operatorとタイムスライシング

ロールアウトが一つのノードで止まるとき

TT Labで続きを見る

一言でいうと

DaemonSetのmaxUnavailable: 1は、1台のノードが新しいPodを起動できないと、残りのノードには手を付けずに、そのまま止まります。これはバグではなく約束であり、その結果、クラスターは、古い設定のノードと新しい設定のノードに分かれたまま残ります。

なぜ1台のノードに全体を止めさせるのか

逆を考えれば、答えが出ます。新しいスペックが間違っているのに、DaemonSetが止まらずに押し進め続けたら、どうなるでしょうか。GPUノード7台が、すべて同時に落ちます。ローリングアップデートの目的は、早く終わらせることではなく、誤った変更の爆発半径を1台に抑えることです。そのため、DaemonSetは1台を変更して、そのPodの準備ができるまで待ち、準備ができなければ永遠に待ちます。

問題は、この停止が静かであることです。kubectl get dsは、このように見えます。

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE
nvidia-container-toolkit   3         3         2       1            2

エラーではありません。赤い文字もありません。READY 2/3は、ぱっと見ると「ほとんど終わった」と読めます。しかし、この表が言っているのは、クラスターが2つの世代に分かれたという事実です。1台は新しいスペックで起動できずにいて、2台はまだ古いスペックのままです。GPU設定のように、ノードのファイルを書き換えるDaemonSetなら、この状態は「ノードごとにランタイム設定が違うクラスター」を意味します。

どう動くのか

DaemonSetのローリングアップデートは、ノード単位で次のように進みます。

  1. まだ古いスペックのノードを1つ選びます。
  2. そのノードのPodを先に削除します。
  3. 新しいスペックのPodを作成します。
  4. そのPodが利用可能になるまで待ちます。
  5. maxUnavailableの分だけ余裕がまた生まれたら、ステップ1に戻ります。

ステップ3で新しいPodがスケジュールされないと、ステップ4が終わらず、ステップ5は永遠に来ません。ステップ2がステップ3より先であることが重要です。止まったノードは、古いPodもなく、新しいPodも起動できない、空の状態になります。

新しいPodが起動しない理由は、たいてい次の3つのうちのどれかです。イメージを取得できないか、ノードが要求されたリソースを用意できないか、トレラレーションやノードセレクターがずれているかです。前の2つは、PodがPendingまたはImagePullBackOffのまま残り、最後は性質が異なります。トレラレーションがずれていると、DaemonSetコントローラーが、そもそも「このノードにPodがあってはならない」と判断して対象から外してしまうので、DESIREDの数字自体が減ります。同じ症状に見えても、見るべき欄が違います。

kubectl rollout status ds/...は、この状態ではただ待ち続けます。CIパイプラインにこのコマンドがタイムアウトなしで入っていると、ジョブが1時間ずつ止まったままになるので、--timeoutを必ず付けます。

現場での姿

第一に、アラートはnumberUnavailableで設定します。DaemonSetが数分以上、desiredNumberScheduled != numberReadyの状態を維持していたら、人が確認する必要があります。Podの再起動回数やエラーログでは、この停止は捕まえられません。何も再起動せず、何のエラーも出ないからです。

第二に、分かれた世代を確認する習慣をつけます。ノードごとに実際にどのスペックが動いているかを見る最も速い方法は、Podのイメージとノードを一緒に取り出してみることです。UP-TO-DATEの数字だけを見ても、どのノードが遅れているかはわからず、GPU設定では、その「どのノード」が、そのまま障害の範囲です。

第三に、元に戻すほうが、前に進むより早いことが多くあります。新しいスペックが起動しない原因を直している間も、1台のノードは空のままです。kubectl rollout undoで古いスペックを復旧して、ノード3台を同じ状態に戻してから、原因はそのあとで探すほうがよいです。障害対応で最初にすべきことは、診断ではなく、状態を1つにそろえることです。

次のラボですること

すぐ後のラボで、共有の3つの方式を、設定とオブジェクトで扱います。デバイスプラグインの設定に名前を付けて複数セット入れておき、ノードのラベルで選んで使えるようにし、MIGノードはリソース名そのものが異なるため、古いマニフェストがそのノードを使えないことをスケジューリングで確認し、チームに割り当てた分をクォータで固定します。ロールアウトが止まった様子は、このコースの最後のラボで、あらためて出会います。