::: tip このセクションの内容 surge がローテーション中の Pod 可用性にどう影響するか、および 1 ノード surge バジェットの適用方法。 :::
| 懸念事項 | 対処 |
|---|---|
| Pod pending 時間 | surge によりほぼゼロ |
readyReplicas のディップ |
アプリケーション層での緩和 |
| 同時 surge ノード | NodePool 内シリアル(v1) |
- Pod pending 時間: surge により Karpenter Graceful セマンティクス(make-before-break)と同等。
readyReplicasのディップ: Kubernetes の構造的制限 — surge があっても eviction 後に新しい Pod が即座にReadyにならない。緩和: レプリカのオーバープロビジョニング + PDB。スコープ外。- 同時 surge: v1 は NodePool あたり
surge.maxUnavailable = 1(内部シリアル; 異なるプールは同時に surge 可能)。代替ノードは NodePool 所有(placeholder 経由で誘導、§3.3)。
spec.limits は リソースバジェット({cpu, memory, …})であり、ノード数ではない。前提条件は placeholder のリクエストが NodePool の残りバジェット(limits − 現在プロビジョニング済み)に収まること。
コントローラーは 開始前にヘッドルームを事前チェック(§5.2 ステップ 3、候補選定後 — placeholder のリクエストは選定された候補に依存するため)。バジェットが 1 ノード分を収容できない場合は警告付きでスキップ。
/metrics で公開:
| メトリクス | タイプ | ラベル |
|---|---|---|
noderotation_candidates |
Gauge | nodepool |
noderotation_in_progress |
Gauge | nodepool |
noderotation_completed_total |
Counter | nodepool, outcome |
noderotation_forceful_fallback_total |
Counter | nodepool |
noderotation_duration_seconds |
Histogram | nodepool, phase |
noderotation_window_active |
Gauge | nodepool |
noderotation_policy_conflict |
Gauge | nodepool |
noderotation_freeze_until_timestamp |
Gauge | nodepool |
noderotation_age_threshold_seconds |
Gauge | nodepool |
noderotation_rotation_chances |
Gauge | nodepool |
noderotation_throughput_capacity |
Gauge | nodepool |
noderotation_t_rot_estimate_seconds |
Gauge | nodepool |
noderotation_t_rot_bound_seconds |
Gauge | nodepool |
noderotation_window_period_seconds |
Gauge | nodepool |
noderotation_short_lead_nodes |
Gauge | nodepool |
noderotation_drain_stuck |
Gauge | nodepool |
noderotation_retry_count |
Gauge | nodepool |
::: details メトリクス詳細 — クリックで展開
noderotation_candidates: プールあたりの適格 NodeClaim 数noderotation_in_progress: プールあたりのアクティブローテーション数noderotation_completed_total: 累積完了数;outcome∈ {success,failure,expired}。expired= graceful ローテーション完了前に force-expire(1回のみ発行、success としてカウントしない)noderotation_forceful_fallback_total: surge なし forceful fallback ローテーション開始数(§3.6); 開始時にインクリメントnoderotation_duration_seconds: フェーズごと;phase∈ {surge_wait,drain}。surge_wait=started-at → surge_ready;drain=draining-at → 旧 NodeClaim ファイナライズ。成功遷移ごとに最大 1 回観測(リトライ書き込みでの二重カウントなし)noderotation_window_active: 0/1 ウィンドウメンバーシップ表示noderotation_policy_conflict: 0/1 セレクタータイまたは無効ポリシーによりブロック(§5.4)noderotation_freeze_until_timestamp: アクティブ freeze の Unix タイムスタンプ(0 = なし)noderotation_age_threshold_seconds: 導出されたageThreshold(§3.2)noderotation_rotation_chances: 保証回数Gnoderotation_throughput_capacity: layer-2 予測C— 発生あたりの開始数(§3.2)noderotation_t_rot_estimate_seconds: 予測サービス時間t_rot_est = provisioningEstimate + drainEstimatenoderotation_t_rot_bound_seconds: 期限側上限t_rot = readyTimeout + tGP + buffernoderotation_window_period_seconds: 最悪ケース周期Pnoderotation_short_lead_nodes: 自身のspec.expireAfterでK回を保証できない NodeClaim 数(§3.2 layer 3)noderotation_drain_stuck: 0/1 ドレインがtGP + bufferを超過(§5.2)noderotation_retry_count: プール内 NodeClaim の最大retry-count(0 = なし)
:::
NodePool ごとのウィンドウ(各プールが自身の RotationPolicy を解決、§5.4)により、noderotation_window_period_seconds と noderotation_window_active は ロードベアリング な nodepool ラベルを持つ — P とメンバーシップはプール間で異なりうる。
expireAfter: Never: すべての導出ゲージが0(導出スキップ)- ウィンドウ発生なし(
P ≤ 0): bound/estimate は非ゼロ;throughput_capacityのみ0
- シリーズは NodePool 削除時にクリア — ゲージは各 reconcile で再計算
- governing policy を失ったプールも同様にシリーズがドロップ(§5.4)
Warning レベルの状態が kubectl describe で確認可能:
| オブジェクト | Reason | タイミング |
|---|---|---|
| NodePool | KBelowTwo, AVeryAggressive, TGPUnset, HardCapExceeded, RetryBackoffShort, DrainEstimateAboveTGP, ProvisioningEstimateAboveReadyTimeout, ThroughputBelowArrival, ThroughputBurstShortfall, RotationSpansNextWindow, OverrideGBelowK |
スケジュール finding がアクティブ |
| NodeClaim | ShortLead |
claim が K 回を保証できない |
| NodePool | ForcefulFallback |
surge なしローテーション開始 |
| NodePool | StaticNodePool |
spec.replicas が設定されており surge では絶対にローテーションできない(§3.3) |
| NodePool | PolicyConflict |
同一 specificity の RotationPolicy タイ — そのプールはローテーションしない(§5.4) |
| NodePool | GovernanceLost |
ガバナンス喪失後に in-flight ローテーションをロールバック(§5.4) |
| NodePool | RotationStarted |
候補選定(Normal) |
| NodePool | RotationCompleted |
旧 NodeClaim ファイナライズ(Normal) |
| NodeClaim | RotationFailed |
readyTimeout 超過; ロールバック |
| NodeClaim | SurgeUnschedulable |
placeholder PodScheduled=False |
| NodeClaim | SurgeClamped |
placeholder クランプ済み(Normal) |
| NodeClaim | SurgeClampBandExceeded |
クランプ shortfall > バンド(Warning) |
| NodeClaim | SurgeClampRefused |
DaemonSet が allocatable を消費(Warning) |
- 重複排除: 状態への遷移時に発行; クリアして再発時に再発行
- Fatal finding は Event ではない — ローテーション開始をブロックし §5.2 feasibility gate でログ
すべての状態遷移が 1 つの INFO ログ行を発行(永続アノテーション書き込み後):
| 行 | 主要フィールド |
|---|---|
rotation candidate selected |
nodeclaim, age, deadline, surgeless |
no rotation candidate |
reason、census カウント |
surge placeholder created |
placeholder, requests、除外カウント、クランプ情報 |
surge placeholder is not schedulable |
placeholder, reason, detail |
surge node ready |
surgeNode, surgeWait |
drain started |
node, mode ∈ {surge, forceful-fallback} |
rotation attempt failed |
reason, readyTimeout, retryCount, backoffUntil |
rotation complete |
mode, drain, surgeNode, surgeWait, total |
- レベルトリガー行(
no rotation candidate、surge placeholder is not schedulable)は遷移 dedup を使用 — reason/census/message が変化した場合のみ再発行 - デバッグ冗長性(
V(1))で dedup なしの各パス findings とハートビートを追加 - ライブネスシグナル: ログの沈黙ではなく
controller_runtime_reconcile_total/ workqueue メトリクスから読み取る
| アラート | 条件 |
|---|---|
| 失敗/expired | `increase(noderotation_completed_total{outcome=~"failure |
| 追いつけない | noderotation_candidates > 0 が 2 回連続ウィンドウ |
| ウィンドウ無駄 | window_active == 1 全ウィンドウ、完了ゼロ、候補非ゼロ |
| ドレイン停滞 | noderotation_drain_stuck == 1 |
| Short lead | noderotation_short_lead_nodes > 0 |
| 体系的失敗 | noderotation_retry_count >= 3 |
| Forceful fallback | increase(noderotation_forceful_fallback_total[1h]) > 0(severity: info) |
Helm chart がオプションの PrometheusRule として提供(prometheusRule.enabled でゲート、デフォルト false)。チューニングは プロダクション Runbook を参照。
- apiGroups: ["karpenter.sh"]
resources: ["nodeclaims"]
verbs: ["get", "list", "watch", "update", "patch", "delete"]
- apiGroups: ["karpenter.sh"]
resources: ["nodepools"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["events.k8s.io"]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]nodeclaims:createなし — v1 はNodeClaimを作成しない(§3.3)。update/patchは状態アノテーション;deleteはローテーションと failure reap を駆動nodepools:update/patchは active-rotation anchor、状態ミラー、完了アノテーション用nodes:update/patchはdo-not-disrupt/マーカー +spec.unschedulable(cordon)用pods: placeholder Pod を直接管理events: NodePool/NodeClaim の Warning Events + リーダー選出記録leases: リーダー選出
placeholder の PriorityClass は Helm chart により静的にインストール — priorityclasses 権限不要。
- v1: クラウド API への直接呼び出しなし。すべての操作は
NodeClaimCRD 経由。 - v2(pre-pull): Job が新ノード上の Pod として実行され、そのロールを継承。コントローラーレベルの追加クラウド権限なし。
::: tip 要点
各ローテーションは ~10–20 分の重複課金を発生させる。失敗駆動のコストは 2 つのメカニズムで制限: エスカレーティング retryBackoff とプールレベルの failurePause。
:::
短時間の重複: surge 中に新旧ノードが同時に課金。
- ローテーションあたり: 1 つの追加オンデマンドインスタンスの ~10–20 分
- 月間(週次ローテーション、N ノード):
≈ N × 4 × hourly_rate × 0.25 - ピーク重複: 同時にローテーションする NodePool 数に比例
失敗した試行は readyTimeout まで surge ノードを課金する可能性あり(未使用時にその後 reap; 再利用されたノードは通常キャパシティとして残存)。
| メカニズム | 制限対象 |
|---|---|
エスカレーティング retryBackoff |
同一 claim のリトライ |
プールレベル failurePause |
体系的失敗下の候補サイクリング |
failurePauseなし: 体系的原因が ~1 分以内に次の候補に移行し、候補ごとにreadyTimeout分の課金を消費failurePauseあり:readyTimeout + failurePauseあたり最大 1 回の試行(デフォルトで ~25m)failurePauseはcooldownAfterと分離 — スループット向上のための settle 短縮がコスト制限を弱めないnoderotation_retry_countがパターンをアラート(§4.2)