KarpenterによるKubernetesオートスケーリング:EKSのプロビジョニング時間を5分から45秒へ短縮

従来のEKSオートスケーリングが抱える課題

AWS Elastic Kubernetes Service(EKS)でトラフィックが急増した際、厄介な問題が浮き彫りになります。それはノード起動の遅さです。突発的なトラフィックスパイクが発生すると、Podはコンピュートリソースを待つために3〜7分間もPending状態のまま放置されます。レイテンシーに敏感なAPIを運用している場合、この数分間の遅延は接続の切断、504エラー、そしてユーザー体験の悪化に直結します。

ボトルネック:Auto Scaling Groupのオーバーヘッド

Kubernetes Cluster Autoscaler(CAS)は長年にわたり標準的なスケーリングエンジンとして利用されてきました(Pod単位のスケーリングを行うKEDAによるイベント駆動型オートスケーリングと組み合わせられることも一般的です)。しかし、CASは単体でコンピュートインスタンスを起動できません。その代わりに仲介役としてAWS EC2 Auto Scaling Groups(ASG)に依存しています。

この追加レイヤーが顕著な摩擦を生み出します。Podを配置できる空きノードがない場合、CASは静的なノードグループをスキャンし、条件に一致するASGの希望容量(desired capacity)を更新して、EC2の応答を待ちます。機械学習用のGPUノード、Webワーカー用のARM64 Gravitonインスタンス、Actions Runner Controller (ARC)によるCI/CDランナー、バッチ処理用のSpotインスタンスなど、クラスターで多様なワークロードを混在させている場合、管理対象のASGはあっという間に15〜25個にも膨れ上がります。この構成は、主に以下の2つの課題を引き起こします。

  • プロビジョニングの遅延:ASGのライフサイクルシグナルの待機、EC2インスタンスの初期化、kubeletのブートストラップ処理に3〜6分を要します。
  • クラウド費用の浪費:Podが固定サイズのインスタンスに無駄なく収まることは滅多にありません。例えば固定の4 vCPUインスタンス上で3.2 vCPUを要求するサービスを動かすと、サーバーの20%がアイドル状態のまま放置されますが、AWSからはサーバー全体の料金が請求されます。

Karpenterがこれをどのように解決するか

Karpenterは、アーキテクチャからAuto Scaling Groupを排除します。Kubernetes APIサーバー経由で直接保留中のPodを監視し、ec2:CreateFleetを呼び出してPodの要求に正確に合致するインスタンスを起動します。ノードは約45秒で登録され、Ready状態になります。最適なインスタンスタイプへPodを高密度にパッキングすることで、Karpenterはコンピュートコストを日常的に30%〜50%削減します(IaC環境における事前コスト予測にはInfracostによるTerraformのクラウドコスト試算の導入も推奨されます)。

クイックスタート(5分)

既存のEKSクラスターにHelmを使用してKarpenterをデプロイし、動的なノードプロビジョニングを設定する手順を以下に示します。

1. HelmによるKarpenterのインストール

EKSクラスターでOpenID Connect(OIDC)またはEKS Pod Identityが有効になっており、必要なIAMロールが設定されていることを確認します。その後、KarpenterのHelmチャートをインストールします。

export KARPENTER_VERSION="1.0.0"
export CLUSTER_NAME="production-eks-cluster"

helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version "${KARPENTER_VERSION}" \
  --namespace karpenter \
  --create-namespace \
  --set "settings.clusterName=${CLUSTER_NAME}" \
  --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::123456789012:role/KarpenterControllerRole" \
  --wait

2. EC2NodeClassとNodePoolの定義

Karpenter v1は、主に2つのカスタムリソース定義(CRD)を使用します。

  • EC2NodeClass:サブネット、セキュリティグループ、AMIファミリー、ストレージボリュームなどのAWSレベルの設定を行います。
  • NodePool:Podのスケジューリング境界、インスタンスアーキテクチャ、購入オプション、中断ポリシー(disruption policy)を設定します。

以下のマニフェストを適用して、動的ノードプロビジョニングを有効にします。

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  role: KarpenterNodeInstanceRole
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: production-eks-cluster
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: production-eks-cluster
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-compute
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
  limits:
    cpu: 1000
    memory: 2000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

3. 動的スケーリングのテスト

十分なCPU割り当てを要求する10レプリカのテスト用Deploymentを作成します。

kubectl create deployment inflate --image=public.ecr.aws/eks-distro/kubernetes/pause:3.7 --replicas=10
kubectl set resources deployment inflate --requests=cpu=1,memory=1Gi

Karpenterコントローラーのログを確認し、保留中のリクエストの評価、AWS Fleet APIへの問い合わせ、および最適なノードの即時起動の様子を観察します。ノードやPodの起動トラブルが発生した際は、Inspektor GadgetでKubernetesをデバッグしてリアルタイムにリソースの挙動を追跡するのも効果的です。

kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller

詳細解説:KarpenterとCluster Autoscalerの比較

なぜKarpenterはCluster Autoscalerよりも大幅に高速なのでしょうか?その決定的な違いはスケジューリングモデルにあります。

Karpenterがノードをスケジュールする仕組み

Cluster Autoscalerは、硬直したグループの境界内でリアクティブ(対症療法的)に動作します。PodがPending状態になると、CASは既存のASGがそれをホストできるかどうかを確認します。一致するグループがあれば、CASはそのASGのキャパシティをインクリメントし、AWS EC2 Auto Scalingの起動ライフサイクルが完了するのを待ちます。

一方、Karpenterはプロアクティブなスケジューリングアシスタントとして機能します。その制御ループは、以下の明確な4つのステージで実行されます。

  1. グループ化(Grouping):Karpenterは、アフィニティ、Taint、Toleration、要求リソースに基づいて、保留中のPodをバッチ処理します。
  2. インスタンス選定(Instance Selection):コントローラーはEC2の価格情報とインスタンス仕様をリアルタイムで確認し、バッチ全体を過不足なく収容できる最も費用対効果の高いインスタンス構成を算出します。
  3. 直接プロビジョニング(Direct Provisioning):Karpenterはec2:CreateFleetを直接トリガーします。ASGの起動テンプレート、ウォームプールの遅延、ポーリングのオーバーヘッドをバイパスします。
  4. 自動登録(Automatic Registration):EC2インスタンスがEKSコントロールプレーンに参加し、ノードがReadyになると直ちにPodがバインドされます。

自動ビンパッキングとノードの統合(コンソリデーション)

従来のノードグループでは、CPU使用率がわずか30%〜45%程度にとどまることがよくあります。余剰キャパシティを回収するには、通常、独自のドレインスクリプトやサードパーティ製コントローラーが必要でした。

Karpenterは、アクティブな統合処理(コンソリデーション)によってこの問題を解決します。consolidationPolicy: WhenEmptyOrUnderutilizedを設定すると、クラスターの集約度を常時監視します。例えば夜間にトラフィックが減少し、1時間あたり0.38ドルのm6i.2xlarge上で動作している3つのPodが0.096ドルのc6i.largeに収まる場合、Karpenterは以下のようなローリングマイグレーションを実行します。

  1. 代替となるc6i.largeノードを起動します。
  2. m6i.2xlargeを安全にドレイン(退避)およびCordon(スケジュール対象外に設定)します。
  3. すべてのPodが新しいインスタンスで稼働したことを確認し、大きなノードを終了します。

高度な活用法

1. SpotとGravitonによるコスト最適化の優先

AWS Graviton(ARM64)とEC2 Spotインスタンスを組み合わせることで、オンデマンドのx86ノードと比較してコンピュートコストを60%〜80%削減できます。例えば、c7g.xlargeのSpotインスタンスは1時間あたり約0.05ドルであり、オンデマンドのc6i.xlarge(1時間あたり0.17ドル)と比べて大幅に安価です。

この戦略は、NodePool内で直接設定して適用できます。

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cost-optimized
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: kubernetes.io/arch
          operator: In
          values: ["arm64"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["5"]
      expireAfter: 720h
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s
    budgets:
      - nodes: "20%"

2. ドリフト検出とノードの自動アップグレード

標準的なEKS環境でワーカーノードのAMIを更新する場合、起動テンプレートの更新、ASGの調整、およびインスタンスの手動ローテーションが必要です。Karpenterは設定の乖離(ドリフト)をネイティブに処理します。EC2NodeClass内のAMIタグやセキュリティグループを更新すると、Karpenterは古いノードをドリフト状態としてマークします。そして中断予算(disruption budget)を遵守しながら段階的にノードを置き換え、ロールアウト中もワークロードの稼働を維持します。本番導入前にはChaos Meshを用いたカオスエンジニアリングを実施し、ノード中断時の耐障害性を検証しておくと安心です。

本番運用のベストプラクティス

高スループットなEKSクラスターの運用経験から得られた、4つの運用上のベストプラクティスをご紹介します。

  • Spotインスタンスタイプを多様化する:Karpenterをc5.xlargeのような単一のインスタンスファミリーに固定しないようにしてください。c6i、c7g、m6i、m7gなど、複数のファミリーを許可します。選択肢を広く持たせることで、特定のAWSアベイラビリティゾーンでSpotキャパシティが逼迫した場合でも起動失敗を防ぐことができます。
  • Pod Disruption Budget(PDB)を適用する:Karpenterは積極的にコンソリデーションを行います。PDBが設定されていないと、急激なノードのドレインによって一時的なサービス停止が発生するリスクがあります。ミッションクリティカルなサービスには、必ずminAvailable: 1またはmaxUnavailable: 20%などを指定したPDBを設定してください。
  • コントローラーをオンデマンドノードに隔離する:KarpenterのコントローラーPodは、オンデマンドのマネージドノードグループまたはAWS Fargate上で実行してください。KarpenterがSpotノード上で動作していて中断・回収された場合、コントローラーが別の場所で再起動するまでクラスターは代替ノードをプロビジョニングできなくなります。
  • サブネットとセキュリティグループのタグを確認する:Karpenterはkarpenter.sh/discovery: <cluster-name>などのAWSタグを介してVPCサブネットやセキュリティグループを検出します。TerraformやOpenTofuのマニフェストで、すべてのプライベートサブネットおよび対象のセキュリティグループにこれらのタグが付与されていることを確認してください。
Share: