- Authors
- Name
- はじめに
- Cluster Autoscaler vs Karpenter
- Karpenterアーキテクチャの理解
- インストールと構成
- 実践NodePoolパターン
- 統合(Consolidation)戦略
- Spotインタラプション処理
- モニタリング
- CAからKarpenterへの移行
- コスト最適化のヒント
- トラブルシューティング
- クイズ
- まとめ
- 参考資料

はじめに
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 Autoscaler | Karpenter |
|---|---|---|
| スケーリング単位 | 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 C │
│ CPU: 80% │ │ CPU: 20% │ │ CPU: 15% │
│ m5.2xl │ │ m5.2xl │ │ m5.2xl │
└──────────┘ └──────────┘ └──────────┘
統合後:
┌──────────┐ ┌──────────┐
│ Node A │ │ Node D │ Node 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以上のクラスター移行事例が証明するとおり、大規模本番環境でも実績があります。