Kubernetesスペシャリストが注目する関連ツール探求 23

「Karpenter」でKubernetesのノード運用を自動化してみよう

第23回の今回は、「Karpenter」によるKubernetesノード運用の自動化について、「Cluster Autoscaler」との違いやノードのライフサイクル管理の仕組み、運用上の注意点について解説します。

小池 玲斗

6:30

はじめに

3-shakeのSreake事業部に所属する小池(@r4ynode)です。

Kubernetesのノード管理には、ここ数年で選択肢が増えました。EKSのAuto ModeやGKEのAutopilotのように、ノードのプロビジョニングからスケーリング、更新までをマネージドに委ねられるサービスが登場しています。

少し前まで、ノードの調達は手間のかかる作業でした。ワークロードごとにインスタンスタイプを見積もり、ASG(Auto Scaling Group)を用途別にいくつも用意し、AMI更新やOSのEOLが来ればノードを入れ替える。過剰に構えればコストがかさみ、絞りすぎればPodがPending状態のまま滞留する。キャパシティプランニングの難しさと更新の負荷は、長くノード運用に付いて回ってきました。

もっとも、マネージドに委ねるか自前で管理し続けるかは取捨選択です。コストや制御性を理由に、ノードを自分たちで運用する組織も少なくありません。どちらを選ぶにせよ、ノードを増減する仕組みが何を基準に動いているのかを理解しておく価値はあります。

例えば、EKS Auto Modeは内部で「Karpenter」を動かしています。また、オートスケーリングにはPodをスケールする層とノードをスケールする層があり、以前に本連載で扱ったHPAやKEDAは前者、Karpenterは後者にあたります。

本記事では、このKarpenterを取り上げます。Cluster Autoscalerとの違いを起点に、ノードの生成から終了までを担うライフサイクル管理の仕組み、そして運用上の注意点を見ていきましょう。

「Karpenter」とは

Karpenterは、Kubernetes向けのオープンソースのノードオートスケーラーです。Podの要求に合わせてノードをプロビジョニングし、不要になれば削除します。もともとAWSが主導していましたが、現在はKubernetes SIG配下のkubernetes-sigs/karpenterとして管理され、2026年7月時点でv1.14.0が出ています。コアはクラウドに依存しない設計で、AWS向け実装(karpenter-provider-aws)が最も成熟しています。

基本動作は、次の4ステップです。

  1. スケジューラーがスケジュール不能(Unschedulable)と判定したPodを検知する
  2. そのPodの制約(リソース要求、nodeSelector、Affinity、Tolerationなど)を評価する
  3. 制約を満たすノードをプロビジョニングする
  4. ノードが不要になったら削除する

Pendingに応じてノードを増減するという目的は、従来から使われている「Cluster Autoscaler」と同じです。違うのは、その実現方法です。

「Cluster Autoscaler」との違い

Cluster Autoscalerは、クラウドのノードグループを単位にスケールします。ノードグループはKubernetesの標準機能ではなく、クラウドごとの仕組みです。AWSではASG、GCPではMIG(Managed Instance Group)、AzureではVMSS(Virtual Machine Scale Sets)がそれにあたります。そもそもKubernetesのコアにはノードを増減する機能がなく、標準にあるのはPodのレプリカ数を調整するHPAまでです。ノードの調達は、こうしたクラウド側の仕組みに委ねられています。

Cluster Autoscalerはこのノードグループの台数を動かします。AWSならPendingを検知してASGのdesired数を増やし、起動はASGに任せます。ここでCluster Autoscalerが置かれている前提が1つあります。同じノードグループのノードは、ほぼ同じスペックだ、というものです。増えるノードを1種類のテンプレートで代表させて判断するためで、だから用途や大きさの異なるノードが要ると、汎用・大サイズ・GPUというようにノードグループを分けることになります。ASG自体はMixed Instances Policyで複数のインスタンスタイプを候補にできますが、Cluster Autoscalerはグループを同質なノードのまとまりとして扱うので、形の違うノードはやはりグループが別になります。

Karpenterは、このノードグループを介しません。AWSならEC2 Fleet APIを直接呼び出してインスタンスを起動します。この違いが2つの特徴につながります。

1つはPod中心であること。PendingのPodをまとめ、CPU・メモリ・GPUの要求量でビンパッキングして効率のよいインスタンスタイプを割り出します。そのうえで、条件を満たす複数のインスタンスタイプを候補としてEC2 Fleet APIに渡し、価格の安さ(スポットの場合は、あわせて中断されにくさ)を基準に、実際に起動するタイプが選ばれます。

もう1つはGrouplessであること。インスタンスタイプを固めたノードグループを事前に作る必要がありません。許可するファミリーやアーキテクチャ、キャパシティタイプを広く定義しておけば、その範囲からPodの要件に合うものを選びます。ノードグループという同質な単位がないので、大小さまざまなインスタンスを1つのNodePoolの制約で扱えます。

なぜKarpenterが生まれたのか

Cluster Autoscalerの制約は、どれもノードグループを単位にすることから生じていました。ならば介さなければよい、という発想です。

Karpenterは2021年にAWSが発表しました。実運用での採用が広がるなか、2023年10月にベータへ昇格し、同時期にコアがAWSからCNCF(Kubernetes SIG Autoscaling傘下のkubernetes-sigs)へ移管されていきます。そして2024年8月にv1.0としてGAを迎えます。「ノードグループを介さず、Podの要求からインスタンスを起動する」という考え方を、AWS固有の最適化ではなく、クラウドをまたいだノードオートスケーリングの一般的なモデルとして育てる方向づけです。そしてこの考え方は、いまやEKS Auto Modeのように、マネージドサービスの内側にも取り込まれています。

Karpenterの主要なCRD

では、こうした仕組みを実際にどう設定するのか。Karpenterの挙動は、すべてKubernetesのCRDを通して定義します。ノードを1台ずつ手で用意するのではなく、「どんなノードを、どの範囲まで許すか」を宣言する形です。中心になるのは次の3つです。

NodePool

NodePoolは許可するノードの制約を定義します。インスタンスタイプやアーキテクチャ、キャパシティタイプ(spot / on-demand)、有効期限(expireAfter)、総量の上限(limits)、後述するDisruptionの方針などです。GPU用と一般用のように分け、taintやlabelで使い分けることもできます。ここで書くのは個別の指示ではなく、許可する範囲だということがポイントです。

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: "1000"

3つのファミリーとspot/on-demandを指定するだけで、条件を満たす何十種類ものインスタンスをここから選べます。

EC2NodeClass

「EC2NodeClass」(クラウド非依存の文脈ではNodeClass)は、AMIやサブネット、セキュリティグループ、UserDataといったAWS固有の設定を担います。クラウドに依存しない「NodePool」と、クラウド固有の「NodeClass」に分かれています。

NodeClaim

「NodeClaim」は起動した個々のノードを表す内部リソースです。通常は直接書きませんが、どのインスタンスがどのNodePool由来かをたどるときに役立ちます。

ノードのライフサイクルを支える機能

Karpenterの役割は、ノードをプロビジョニングすることだけではありません。むしろ本質は稼働中のノードを継続的に評価し、最適化・更新・終了までを含めたライフサイクル全体を管理する点にあります。

ノードの生成はこれまで見たとおりで、運用上難しいのは、稼働しているノードをいつ、どのように停止・入れ替えするかの判断です。

Provisioning(生成)

プロビジョニングの流れは前述の通りです。許可する候補を広く取るほどFleetが選べる選択肢が増えるので、インスタンスタイプを絞りすぎないことが可用性とコストに直結します。スポットの容量不足が返ったら、その組み合わせを一時的に候補から外し、別の候補でリトライします。

Disruption(最適化と入れ替え)

動いているノードを止める操作を、Karpenterではまとめて「Disruption」と呼びます。無駄なノードを減らすのも、古くなったノードを新しくするのも、壊れたノードを取り替えるのも、すべてここに含まれます。種類は多いのですが、見分ける軸は1つだけです。Karpenterが自分の都合で止めるのか、外から止めさせられるのかです。

前者を「graceful」(自発的)、後者を「forceful」(強制的)と呼びます。違いは「待てるかどうか」に尽きます。gracefulは急がないので、一度に止める数を制限したり、特定のPodを守ったりできます。forcefulは締め切りが来ているので、そうした制限や保護はかけられません。この軸で分けると、それぞれ下表のように整理できます。

種類いつ起きるかモードPDB等で保護
Consolidation無駄なノードが見つかったときgraceful できる
Drift 設定と食い違うノードが見つかったときgracefulできる
Expiration寿命(expireAfter)が来たときforcefulできない
Interruptionスポット中断などの通知が来たときforcefulできない
Node Auto Repairノードが壊れたとき  forcefulできない

一番よく使われるのはConsolidation(統合)です。余ったノードを削除したり、より安い構成に置き換えたりしてコストを継続的に下げます。基準はPodの実使用量ではなくrequestsなので、その精度が最適化の質を左右します。入れ替えが多すぎるならconsolidateAfterで待ち時間を延ばせます。

もう1つのgracefulはDriftです。NodePoolやNodeClassの定義を変えたのに既存ノードが古い仕様のまま、という食い違いを直します。典型はAMIの更新で、EKSのアップグレードなどで解決されるAMIが変わると旧AMIのノードがDriftedと判定され、順に置き換わります。requirementsを広げただけなら、既存ノードが新しい制約の範囲内にある限りDriftは起きません。

残る3つはforcefulです。ExpirationはexpireAfter(既定720時間)を超えたノードを終了させ、鮮度を保ちます。Interruptionはスポットの2分前に通知などを受け取り、代替ノードの起動と退避を同時に進めます。Karpenterは新ノードの起動を並行して始めるため多くの場合は猶予内に退避できますが、実際の起動時間はAMIやインスタンスタイプに左右されます。

止める速度をそろえるのがDisruption Budgetです。同時に中断してよいノード数を制限でき、cronで「平日の営業時間は止めない」とも指定できます。ただし、対象になるのはgracefulだけです。ExpirationやInterruptionはBudgetの外で進むので、止められない中断があることは押さえておく必要があります。

運用で気をつけたいこと

Karpenter単体の挙動は、ここまで見たとおり素直です。それでも、運用に乗せると「ノードが余っているのに縮まない」「なぜかノードが増えすぎる」といった想定外に出会います。その多くは、Karpenter自身ではなく、すでにクラスタにあるPod側の設定との噛み合わせから生まれます。Karpenterはノードの増減を、Podの要求と、Podを守る設定を読んで決めるからです。気をつけるところも、Karpenterの設定より、周辺コンポーネントとの相性に集中します。

まず requests を正確に

土台はrequestsです。KarpenterはノードのサイズもConsolidationの可否もrequestsで決めます。小さく見積もれば詰め込みすぎて負荷時にあふれ、大きく見積もれば余白を抱えたまま縮みません。特にConsolidationは「requestsの合計が空くか」で判断するため、不正確なほど集約は的外れになります。

非CPUリソースでrequestsとlimitsが離れると、同時バーストでOOMを招くこともあります。requestsを詰めるか、LimitRangeで名前空間の既定を与えるか。この地味な整備が最適化の質を決めます。

PodDisruptionBudget との噛み合わせ

PodDisruptionBudget(PDB)はgracefulな中断からPodを守りますが、厳しすぎるとKarpenterがノードをドレインできず、ConsolidationやDriftが止まります。ありがちなのはレプリカ1のワークロードにminAvailable: 1を付ける例で、そのPodは退避できず、載ったノードが縮まないまま残ります。紛らわしいのは、PDBが効くのはgracefulな中断だけだという点です。スポット中断やNode Auto Repairのようなforcefulな中断は、締め切りが来ればPDBに関係なくPodを停止します。

一方でExpirationは締め切りを持たないため、terminationGracePeriodを設定していないと、逆にPDBやdo-not-disruptアノテーション(後述)にドレインを阻まれ、ノードが延々と残ることがあります(公式も両者の併用を推奨しています)。いずれにせよ、forcefulな中断に対してPDBが期待どおりの保護を与えるとは限らない、と押さえておく必要があります。

TopologySpreadConstraints との噛み合わせ

TopologySpreadConstraints(TSC)でPodをゾーンやホストに分散させると、Karpenterはその分散を満たすためにノードを起動します。whenUnsatisfiable: DoNotScheduleでは分散を満たせないPodがPendingになり、それを解消しようとノードが増えます。公式はScheduleAnyway(緩い制約)の場合でもKarpenterが分散を優先して新規ノードを立てるため、想定より多くのノードが立つことがあると注意喚起しています。Consolidationの側でも、あるノードを削除すると分散が崩れる場合、Karpenterは削除しません。「余っているのに縮まない」の正体が、TSCによる集約のブロックだった、というのはよくあります。

そのほかの噛み合わせ

似た構図は、ほかのPod側設定でも起こります。

  • Pod anti-affinity: 「1ノードに1つ」のような強い分離を課すとPodの数だけノードが要り、集約もできません。
  • do-not-disruptアノテーション: 長時間ジョブなどを守れますが、付けすぎるとノードが延々と残ります。
  • DaemonSet: 各ノードで動く分使える容量が目減りし、重いと大きなノードが選ばれます。なお「空ノード」の判定はDaemonSetのPodなど中断コストのないPodだけが残った状態を指すため、DaemonSetが載っていても空とみなされます。
  • EBS を使うStatefulSet: ボリュームがゾーンに固定されるため、入れ替えや再スケジュールがそのゾーンに縛られます。

Google Cloudにおけるノードオートスケーリング

AWSではノード管理の主流がKarpenterになりつつあり、EKSにはマネージドで内蔵するAuto Modeまで用意されました。では、Google Cloudはどうでしょうか。

GKEは、この問いに自前の答えを持っています。Cluster Autoscalerがノードプールの台数を増減し、その拡張であるNode Auto-Provisioning(NAP)が保留中のPodに合わせてノードプールそのものを自動で作成し、Autopilotがノードプールの管理ごとGoogleに委ねます。

KarpenterとNAPは、同じ課題に別のアプローチで答えています。Karpenterはノードグループを介さずにインスタンスを起動するのに対して、NAPはノードプールを自動で作ります。目的は同じで手段が異なります。そのためGoogle CloudではネイティブのNAPやAutopilotが充実し、公式のKarpenter GCPプロバイダーはなく、あるのはコミュニティ主導のプレビュー段階の実装だけです。

AWSはKarpenterに寄り、AzureはAKSのNode Auto Provisioningがkarpenter-provider-azureを採用し、GoogleはNAPとAutopilotという自前路線を歩んでいます。

まとめ

冒頭で、ノード管理をマネージドに委ねる選択肢が増えたことに触れました。委ねるにせよ自前で持つにせよ、ノードを供給する仕組みの中身を知る価値はあります。AWSではその中核をKarpenterが担い、Podの要求から必要なノードを用意し、不要になれば削除します。

Karpenterが真価を発揮するのは、ワークロードの多様性やリソース変動が大きく、コスト最適化の余地が大きい環境です。逆に、要件がシンプルであればマネージドサービスやCluster Autoscalerで十分な場面も少なくありません。

本記事で解説した仕組みとトレードオフを、自組織のクラスタが抱えるノード管理の課題と照らし合わせ、導入を検討する材料としていただければ幸いです。

【参考資料】

人気記事トップ10

人気記事ランキングをもっと見る

企画広告も役立つ情報バッチリ! Sponsored