Skip to content
Published on

Karpenter 実践ガイド — Kubernetesノードオートスケーリングの新パラダイム

Authors
  • Name
    Twitter
Karpenter Autoscaling

はじめに

Kubernetesクラスターにおけるノードオートスケーリングは、コスト最適化とサービス安定性の要です。長年にわたりCluster Autoscaler(CA)が標準でしたが、AWSが開発したKarpenterは根本的に異なるアプローチでノードプロビジョニングを革新しました。

2025年にSalesforceは1,000以上のEKSクラスターをCluster AutoscalerからKarpenterへ移行しました。これはKarpenterの成熟度を示す代表的な事例です。

Cluster Autoscaler vs Karpenter

Cluster Autoscalerの限界

# Cluster AutoscalerはNode Group単位でのみスケーリング
# 事前定義されたインスタンスタイプに依存
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
managedNodeGroups:
  - name: general
    instanceType: m5.xlarge # 固定インスタンスタイプ
    minSize: 2
    maxSize: 10
    desiredCapacity: 3

CAの主な限界:

  • Node Group依存:事前定義されたASG/Node Groupでのみスケーリング可能
  • 遅い応答:スケジューリング失敗 → CA検知 → ASG更新 → EC2プロビジョニング(数分かかる)
  • 非効率なBin-packing:ノードグループ単位のスケーリングによるリソースの無駄
  • 手動インスタンス選択:ワークロードに最適なインスタンスを手動で設定する必要がある

Karpenterのアプローチ

# KarpenterはPodの要件に最適なインスタンスを自動選択
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ['amd64', 'arm64']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand', 'spot']
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ['c', 'm', 'r']
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ['4']
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: '1000'
    memory: 1000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

Karpenterの主な利点:

特性Cluster AutoscalerKarpenter
スケーリング単位Node Group (ASG)個別Pod基準
インスタンス選択手動設定自動最適化
プロビジョニング速度数分数十秒
Bin-packing限定的最適化
Spot処理別途設定が必要ネイティブサポート
統合(Consolidation)非対応ネイティブサポート

Karpenterアーキテクチャの理解

コアリソース

Karpenter v1(GA)は2つのコアCRDを使用します:

1. NodePool — ノードプロビジョニングポリシーの定義

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu-workloads
spec:
  template:
    metadata:
      labels:
        workload-type: gpu
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ['p4d', 'p5', 'g5']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand']
      taints:
        - key: nvidia.com/gpu
          effect: NoSchedule
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: gpu-nodes
  limits:
    cpu: '100'
    nvidia.com/gpu: '16'
  weight: 10 # 優先度(値が大きいほど先に試行)

2. EC2NodeClass — AWSインフラ設定

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  role: 'KarpenterNodeRole-my-cluster'
  amiSelectorTerms:
    - alias: al2023@latest
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: 'my-cluster'
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: 'my-cluster'
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 100Gi
        volumeType: gp3
        iops: 3000
        throughput: 125
        encrypted: true
  userData: |
    #!/bin/bash
    echo "Custom bootstrap script"

プロビジョニングフロー

Pod作成(Pending)
Karpenter Controllerが検知
Podの要件を分析
CPU、Memory、GPU、Topology、Affinity)
最適なインスタンスタイプを計算
(価格、可用性、Bin-packing最適化)
EC2インスタンスを直接作成
ASGなしでEC2 Fleet APIを使用)
NodeClaim作成Node登録
Podスケジューリング

インストールと構成

Helmでインストール

# Karpenter v1.1.x インストール(2026年最新)
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version "1.1.2" \
  --namespace kube-system \
  --set "settings.clusterName=my-cluster" \
  --set "settings.interruptionQueue=my-cluster" \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --wait

IAM構成

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "KarpenterController",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateFleet",
        "ec2:CreateLaunchTemplate",
        "ec2:CreateTags",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeImages",
        "ec2:DescribeInstances",
        "ec2:DescribeInstanceTypeOfferings",
        "ec2:DescribeInstanceTypes",
        "ec2:DescribeLaunchTemplates",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DeleteLaunchTemplate",
        "ec2:RunInstances",
        "ec2:TerminateInstances",
        "iam:PassRole",
        "ssm:GetParameter",
        "pricing:GetProducts"
      ],
      "Resource": "*"
    },
    {
      "Sid": "KarpenterInterruption",
      "Effect": "Allow",
      "Action": ["sqs:DeleteMessage", "sqs:GetQueueUrl", "sqs:ReceiveMessage"],
      "Resource": "arn:aws:sqs:*:*:my-cluster"
    }
  ]
}

実践NodePoolパターン

パターン1:汎用ワークロード

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ['amd64']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand', 'spot']
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ['c', 'm', 'r']
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ['5']
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ['metal', '24xlarge', '16xlarge']
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: '500'
    memory: 2000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 30s

パターン2:Spot優先 + On-Demandフォールバック

# Spot専用NodePool(高優先度)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: spot-first
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['spot']
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ['c', 'm', 'r']
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  weight: 80 # 高優先度
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 15s
---
# On-DemandフォールバックNodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: on-demand-fallback
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand']
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ['c', 'm']
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  weight: 20 # 低優先度(Spot失敗時に使用)

パターン3:ARM64 Gravitonの活用

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: graviton
spec:
  template:
    metadata:
      labels:
        arch: arm64
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ['arm64']
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ['c7g', 'm7g', 'r7g', 'c8g', 'm8g']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['on-demand', 'spot']
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: graviton
  weight: 90 # Graviton優先

統合(Consolidation)戦略

Karpenterの最も強力な機能の一つが自動統合です。

統合ポリシー

disruption:
  # WhenEmpty: 空のノードのみ削除
  # WhenEmptyOrUnderutilized: 空のノード + 低稼働ノードを統合
  consolidationPolicy: WhenEmptyOrUnderutilized
  consolidateAfter: 30s

  # 特定の時間帯のみ統合を許可
  budgets:
    - nodes: '10%' # 一度に最大10%のノードのみ統合
    - nodes: '0'
      schedule: '0 9 * * 1-5' # 平日9時に統合を停止
      duration: 8h # 8時間

統合の動作

現在の状態:
┌──────────┐ ┌──────────┐ ┌──────────┐
Node A   │ │ Node B   │ │ Node CCPU: 80% │ │ CPU: 20% │ │ CPU: 15%│ m5.2xl   │ │ m5.2xl   │ │ m5.2xl   │
└──────────┘ └──────────┘ └──────────┘

統合後:
┌──────────┐ ┌──────────┐
Node A   │ │ Node DNode B, C を削除
CPU: 80% │ │ CPU: 35% │   → より小さいインスタンスに置換
│ m5.2xl   │ │ m5.xl└──────────┘ └──────────┘

Do-Not-Disruptアノテーション

# 重要なワークロードがあるPodに適用
apiVersion: v1
kind: Pod
metadata:
  annotations:
    karpenter.sh/do-not-disrupt: 'true'
spec:
  containers:
    - name: critical-job
      image: batch-processor:latest

Spotインタラプション処理

# SQSキューによるSpotインタラプション通知
# Karpenterが自動処理:
# 1. Spotインタラプション通知を受信(2分前)
# 2. 対象ノードのPodをCordon + Drain
# 3. 新しいノードをプロビジョニング
# 4. Podを再スケジューリング

# EventBridge Rule (CloudFormation)
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  SpotInterruptionRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source: ['aws.ec2']
        detail-type: ['EC2 Spot Instance Interruption Warning']
      Targets:
        - Arn: !GetAtt InterruptionQueue.Arn
          Id: SpotInterruptionTarget

  RebalanceRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source: ['aws.ec2']
        detail-type: ['EC2 Instance Rebalance Recommendation']
      Targets:
        - Arn: !GetAtt InterruptionQueue.Arn
          Id: RebalanceTarget

  InterruptionQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: my-cluster
      MessageRetentionPeriod: 300

モニタリング

Prometheusメトリクス

# ServiceMonitor for Karpenter
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: karpenter
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: karpenter
  endpoints:
    - port: http-metrics
      interval: 30s

主要メトリクス:

# プロビジョニングされたノード数
karpenter_nodeclaims_created_total

# ノードプロビジョニングレイテンシ
histogram_quantile(0.99, karpenter_nodeclaims_registered_duration_seconds_bucket)

# 統合により削除されたノード数
karpenter_nodeclaims_disrupted_total{reason="consolidation"}

# Pending Pod数(Karpenter待ち)
karpenter_pods_state{state="pending"}

# インスタンスタイプ別ノード分布
count by (instance_type) (karpenter_nodeclaims_instance_type)

CAからKarpenterへの移行

段階的な移行

# ステップ1: Karpenterのインストール(CAと共存)
helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --namespace kube-system \
  --set "settings.clusterName=my-cluster"

# ステップ2: NodePoolの作成(CA Node Groupと重複しないように)
kubectl apply -f nodepool-general.yaml

# ステップ3: 新しいワークロードをKarpenterに誘導
kubectl label nodes -l eks.amazonaws.com/nodegroup=old-ng \
  migration-phase=legacy

# ステップ4: 既存ワークロードの段階的な移行

# ステップ5: CA Node Groupの縮小と削除
eksctl scale nodegroup --cluster my-cluster \
  --name old-ng --nodes 0 --nodes-min 0

注意事項

# PodDisruptionBudgetの確認
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: critical-app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: critical-app
# KarpenterはPDBを尊重するため、
# 統合時にPDB違反の可能性がある場合はスキップ

コスト最適化のヒント

1. 多様なインスタンスタイプを許可

# 悪い例: インスタンスタイプを制限
requirements:
  - key: node.kubernetes.io/instance-type
    operator: In
    values: ["m5.xlarge"]  # Spotの可用性が低い

# 良い例: 広い範囲を許可
requirements:
  - key: karpenter.k8s.aws/instance-category
    operator: In
    values: ["c", "m", "r"]
  - key: karpenter.k8s.aws/instance-generation
    operator: Gt
    values: ["5"]

2. 適切なリソースリクエスト

# Podのリソースリクエストが正確であるほどBin-packingの効率が向上
resources:
  requests:
    cpu: '500m'
    memory: '512Mi'
  limits:
    cpu: '1000m'
    memory: '1Gi'

3. Topology Spreadの活用

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web

トラブルシューティング

よくある問題

# NodeClaimが作成されない場合
kubectl describe nodepool default
kubectl get events --field-selector reason=FailedProvisioning

# インスタンス容量不足(ICE)
# → より多くのインスタンスタイプを許可
# → より多くのAZを許可

# プロビジョニングが遅い
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter \
  --tail=100 | grep -i "provision"

# AMIの問題
kubectl get ec2nodeclass default -o yaml | grep -A5 amiSelector

クイズ

Q1. KarpenterがCluster Autoscalerと根本的に異なる点は何ですか?

KarpenterはNode Group(ASG)なしにPodの要件を直接分析し、最適なEC2インスタンスをプロビジョニングします。CAは事前定義されたNode Groupでのみスケーリングが可能です。

Q2. Karpenter v1で使用する2つのコアCRDは?

NodePool(ノードプロビジョニングポリシー)とEC2NodeClass(AWSインフラ設定)です。

Q3. consolidationPolicyのWhenEmptyOrUnderutilizedはどのように動作しますか?

空のノードを削除し、低稼働(underutilized)ノードのPodを他のノードに移動させてから該当ノードを削除するか、より小さいインスタンスに置き換えます。

Q4. KarpenterがSpotインタラプションを処理するために必要なAWSサービスは?

SQSキューEventBridge Ruleが必要です。EC2 Spotインタラプション警告とリバランス推奨イベントをSQSに転送すると、Karpenterが自動的に処理します。

Q5. karpenter.sh/do-not-disruptアノテーションの用途は?

該当Podが実行中のノードをKarpenterの統合(Consolidation)や期限切れ(Expiration)による中断から保護します。バッチジョブや重要なワークロードに使用します。

Q6. NodePoolのweightフィールドはどのような役割を果たしますか?

複数のNodePoolがある場合に優先度を決定します。weightの値が大きいほど先に試行されます。例:Spot NodePool(weight: 80)がOn-Demand(weight: 20)より先に使用されます。

Q7. KarpenterでGraviton(ARM64)インスタンスを活用するにはどう設定しますか?

NodePoolのrequirementsでkubernetes.io/arch: arm64とGravitonインスタンスファミリー(c7g、m7gなど)を指定します。アプリケーションがARM64をサポートしている必要があります。

まとめ

Karpenterは、Kubernetesノードオートスケーリングのパラダイムを変革しています。Node Groupという中間層を排除し、Podの要件に直接応答して最適なインスタンスをプロビジョニングします。2026年現在、v1 GAが安定的に運用されており、Salesforceの1,000以上のクラスター移行事例が証明するとおり、大規模本番環境でも実績があります。

参考資料