EKS マネージドノードグループの Warm Pool を検証 — poolState 別スケールアウト時間と reuseOnScaleIn の実態
目次
はじめに
2026年4月8日、AWS は EKS マネージドノードグループが EC2 Auto Scaling ウォームプールをサポート したことを発表した。ウォームプールは、OS 初期化・ユーザーデータ実行・ソフトウェア設定が完了済みの EC2 インスタンスをプールとして保持し、スケールアウト時にコールドスタートなしでクラスターに参加させる機能である。
ウォームプールの効果は poolState の選択で大きく変わる。Running は最速だがインスタンス課金が継続し、Stopped はコスト効率が良いが起動に時間がかかる。この「速度 vs コスト」のトレードオフを実測データで定量化し、ワークロード特性に応じた poolState 選択の判断基準を提示する。
本記事では、3パターンのスケールアウト時間を実測し、poolState ごとの速度とコストのデータを提供する。読者はこのデータをもとに「自分のワークロードにウォームプールを導入すべきか」「どの poolState を選ぶべきか」を判断できる。公式ドキュメントは EKS マネージドノードグループのウォームプール を参照。
ウォームプールの仕組み
ウォームプールを有効にすると、EKS はノードグループの Auto Scaling グループにウォームプールを作成する。インスタンスはウォームプールで以下の3つの状態のいずれかで待機する。
| poolState | 状態 | 維持コスト(t3.medium/月) | スケールアウト時の動作 |
|---|---|---|---|
| Running | 起動中 | 〜$32(インスタンス + EBS) | そのままクラスターに参加 |
| Stopped | 停止中 | 〜$2(EBS のみ) | 起動 → クラスターに参加 |
| Hibernated | 休止中 | 〜$2(EBS + RAM) | RAM 復元 → クラスターに参加 |
※ コスト概算は t3.medium(gp3 20GB)の ap-northeast-1 価格に基づく。インスタンスタイプやディスクサイズにより異なる。
前提条件:
- AWS CLI v2.34+ 設定済み(
eks:*,ec2:*,iam:*,autoscaling:*の権限) - kubectl v1.35+
- 検証リージョン: ap-northeast-1(東京)
セットアップだけ見たい場合は検証環境のセットアップ、結果だけ見たい場合は検証 1に進んでほしい。
検証環境のセットアップ
EKS クラスター・ノードロール・ノードグループの作成手順
既存の EKS クラスター(v1.35)を使用した。Auto Mode で作成されたクラスターの場合、マネージドノードグループには vpc-cni、kube-proxy、coredns アドオンが必要である。
REGION=ap-northeast-1
CLUSTER=eks-sandbox
aws eks create-addon --cluster-name $CLUSTER --addon-name vpc-cni --region $REGION
aws eks create-addon --cluster-name $CLUSTER --addon-name kube-proxy --region $REGION
aws eks create-addon --cluster-name $CLUSTER --addon-name coredns --region $REGIONノードロールを作成し、必要なポリシーをアタッチする。
cat > node-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role \
--role-name eks-warmpool-verify-node-role \
--assume-role-policy-document file://node-trust-policy.json
aws iam attach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
aws iam attach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
aws iam attach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly3つのノードグループを作成する。コールドスタート用(ウォームプールなし)、Stopped ウォームプール用、Running ウォームプール用。
# アカウント ID を取得
ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
NODE_ROLE="arn:aws:iam::${ACCOUNT_ID}:role/eks-warmpool-verify-node-role"
# クラスターのプライベートサブネットを取得
SUBNETS=$(aws eks describe-cluster --name $CLUSTER --region $REGION \
--query 'cluster.resourcesVpcConfig.subnetIds' --output text)
# 1. コールドスタート(ウォームプールなし)
aws eks create-nodegroup \
--cluster-name $CLUSTER --nodegroup-name ng-cold-start \
--node-role $NODE_ROLE --subnets $SUBNETS \
--instance-types t3.medium \
--scaling-config minSize=2,maxSize=5,desiredSize=2 \
--region $REGION
# 2. Stopped ウォームプール
aws eks create-nodegroup \
--cluster-name $CLUSTER --nodegroup-name ng-warm-stopped \
--node-role $NODE_ROLE --subnets $SUBNETS \
--instance-types t3.medium \
--scaling-config minSize=2,maxSize=5,desiredSize=2 \
--warm-pool-config enabled=true,maxGroupPreparedCapacity=4,minSize=1,poolState=Stopped,reuseOnScaleIn=true \
--region $REGION
# 3. Running ウォームプール
aws eks create-nodegroup \
--cluster-name $CLUSTER --nodegroup-name ng-warm-running \
--node-role $NODE_ROLE --subnets $SUBNETS \
--instance-types t3.medium \
--scaling-config minSize=2,maxSize=5,desiredSize=2 \
--warm-pool-config enabled=true,maxGroupPreparedCapacity=4,minSize=1,poolState=Running,reuseOnScaleIn=false \
--region $REGIONウォームプールの設定パラメータ:
enabled=true— ウォームプールを有効化maxGroupPreparedCapacity=4— ウォームプール + ASG の合計インスタンス上限。desiredSize=2 の場合、ウォームプールには 4-2=2 台が配置されるminSize=1— ウォームプールの最小インスタンス数poolState=Stopped|Running— ウォームプール内のインスタンス状態reuseOnScaleIn=true— スケールイン時にインスタンスをウォームプールに返却
ノードグループが ACTIVE になったら、ウォームプールのインスタンスが目的の状態(Stopped / Running)に遷移するまで待つ。
# ASG 名を取得
ASG=$(aws eks describe-nodegroup --cluster-name $CLUSTER --nodegroup-name ng-warm-stopped \
--region $REGION --query 'nodegroup.resources.autoScalingGroups[0].name' --output text)
# ウォームプールのインスタンス状態を確認
aws autoscaling describe-warm-pool --auto-scaling-group-name $ASG --region $REGION \
--query 'Instances[].{InstanceId:InstanceId,State:LifecycleState}' --output table-------------------------------------------
| DescribeWarmPool |
+----------------------+------------------+
| InstanceId | State |
+----------------------+------------------+
| i-0a546e64a73986140 | Warmed:Stopped |
| i-0c0c0f4ad0652338a | Warmed:Stopped |
+----------------------+------------------+検証 1: poolState 別のスケールアウト時間計測
3パターンのスケールアウト時間を計測する。各パターンで desiredSize を 2→3 に変更し、新ノードが kubectl get nodes で Ready になるまでの経過時間を記録した。
以下のポーリングスクリプトで計測を行った。NODEGROUP を各ノードグループ名に置き換えて使用する。
計測用ポーリングスクリプト
NODEGROUP="ng-cold-start" # ng-warm-stopped / ng-warm-running に置き換え
START_TIME=$(date +%s)
while true; do
READY=$(kubectl get nodes --selector="eks.amazonaws.com/nodegroup=$NODEGROUP" \
--no-headers 2>/dev/null | grep " Ready " | wc -l)
TOTAL=$(kubectl get nodes --selector="eks.amazonaws.com/nodegroup=$NODEGROUP" \
--no-headers 2>/dev/null | wc -l)
ELAPSED=$(( $(date +%s) - START_TIME ))
echo "$(date +%T) [${ELAPSED}s] Nodes: $TOTAL total, $READY ready"
[ "$READY" -ge 3 ] && echo "=== New node Ready! ===" && break
sleep 5
done(a) コールドスタート(ウォームプールなし)
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-cold-start \
--scaling-config minSize=2,maxSize=5,desiredSize=3 \
--region ap-northeast-117:30:27 [3s] Nodes: 2 total, 2 ready
17:30:40 [16s] Nodes: 2 total, 2 ready
17:30:54 [30s] Nodes: 2 total, 2 ready
17:31:06 [42s] Nodes: 3 total, 2 ready ← ノード出現
17:31:19 [55s] Nodes: 3 total, 2 ready
17:31:26 [62s] Nodes: 3 total, 3 ready ← Ready結果: 62秒。ログの内訳を見ると、42秒でノードが出現(EC2 インスタンスの起動完了 + kubelet 起動開始)し、そこから20秒で Ready になった(CNI 設定、ノード登録完了)。
(b) Stopped ウォームプール
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--scaling-config minSize=2,maxSize=5,desiredSize=3 \
--region ap-northeast-117:31:39 [2s] Nodes: 2 total, 2 ready
17:31:59 [22s] Nodes: 2 total, 2 ready
17:32:05 [28s] Nodes: 3 total, 2 ready ← ノード出現
17:32:18 [41s] Nodes: 3 total, 2 ready
17:32:25 [48s] Nodes: 3 total, 3 ready ← Ready結果: 48秒。コールドスタートより14秒(23%)短縮。ウォームプールのインスタンスは OS 初期化済みの状態で停止しているため、コールドスタートのようなフル起動シーケンスが不要になり、その分が短縮に寄与していると考えられる。
(c) Running ウォームプール
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-running \
--scaling-config minSize=2,maxSize=5,desiredSize=3 \
--region ap-northeast-117:32:38 [2s] Nodes: 2 total, 2 ready
17:32:51 [15s] Nodes: 3 total, 2 ready ← ノード出現
17:33:04 [28s] Nodes: 3 total, 3 ready ← Ready結果: 28秒。コールドスタートより34秒(55%)短縮。Running インスタンスは既に起動済みのため、EC2 起動と OS 初期化のステップをスキップできる。残りの28秒は主に EKS ブートストラップ(クラスター参加 + CNI 設定)に費やされていると考えられる。
検証 2: reuseOnScaleIn の動作と再スケールアウト速度
検証 1 で Stopped ウォームプールのベースライン時間(48秒)を計測した。検証 2 では reuseOnScaleIn=true の環境でスケールイン→再スケールアウトのサイクルを確認し、再利用インスタンスの速度を比較する。
検証 1 で ng-warm-stopped の desiredSize は 3 になっている。ここから 3→2 にスケールインする。
スケールイン: インスタンスのウォームプール返却
desiredSize を 3→2 に変更してスケールインを実行した。
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--scaling-config minSize=2,maxSize=5,desiredSize=2 \
--region ap-northeast-1スケールインの過程を観察すると、以下のステップで進行した。ウォームプールの状態遷移は以下のコマンドで確認できる。
# ASG 名を取得(セットアップ時に取得済みの場合は省略)
ASG=$(aws eks describe-nodegroup --cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--region ap-northeast-1 --query 'nodegroup.resources.autoScalingGroups[0].name' --output text)
# ノード状態とウォームプール状態を定期的に確認
while true; do
echo "--- $(date +%T) ---"
kubectl get nodes --selector='eks.amazonaws.com/nodegroup=ng-warm-stopped' --no-headers
aws autoscaling describe-warm-pool --auto-scaling-group-name $ASG --region ap-northeast-1 \
--query 'Instances[].{Id:InstanceId,State:LifecycleState}' --output table
sleep 15
done17:33:43 ノード: Ready,SchedulingDisabled ← ドレイン開始
17:34:35 ノード: NotReady,SchedulingDisabled ← kubelet 停止
17:49:09 ウォームプール: Warmed:Pending:Proceed ← ライフサイクルフック完了
17:49:40 ウォームプール: Warmed:Stopped ← 返却完了スケールイン開始から返却完了まで約16分。EKS は ASG に Terminate-LC-Hook(ハートビートタイムアウト 1800秒 = 30分)を自動設定しており、ノードのドレイン自体は約1分で完了したにもかかわらず、ライフサイクルフックの処理完了まで待機が発生した。なお、ドキュメントでは「EKS API 経由でウォームプールを設定し、EC2 Auto Scaling API で直接変更しないこと」と記載されているため、このタイムアウト値をユーザーが調整することは推奨されていない。
aws autoscaling describe-lifecycle-hooks \
--auto-scaling-group-name $ASG --region ap-northeast-1{
"LifecycleHooks": [
{
"LifecycleHookName": "Launch-LC-Hook",
"LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING",
"HeartbeatTimeout": 1800
},
{
"LifecycleHookName": "Terminate-LC-Hook",
"LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING",
"HeartbeatTimeout": 1800
}
]
}ASG のアクティビティログからも、インスタンスがウォームプールに返却されたことを確認できる。
Status: MidTerminatingLifecycleAction
Description: Scaling in EC2 instance into warm pool: i-0f44a906c76cdfe62再スケールアウト: 再利用インスタンスの速度
ウォームプールに返却されたインスタンスを使って再スケールアウトした。
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--scaling-config minSize=2,maxSize=5,desiredSize=3 \
--region ap-northeast-117:49:55 [2s] Nodes: 2 total, 2 ready
17:50:22 [29s] Nodes: 2 total, 2 ready
17:50:28 [35s] Nodes: 3 total, 3 ready ← Ready結果: 35秒。初回の Stopped ウォームプール(48秒)より13秒速い。初回はインスタンス起動後に kubelet がクラスターに初めて参加するため、TLS ブートストラップ(証明書発行)やノードオブジェクトの新規登録が必要になる。再利用インスタンスではこれらがキャッシュされており、クラスター参加が高速化されたと考えられる。
検証 3: Pending Pod 起因のスケールアウト — Pod Running までの時間
検証 1-2 は手動で desiredSize を変更した。検証 3 では、実運用に近いシナリオとして Pending Pod が存在する状態でスケールアウトし、Pod が Running になるまでの時間を計測する。
検証 2 の再スケールアウトで ng-warm-stopped の desiredSize は 3 になっている。まず 2 に戻す。
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--scaling-config minSize=2,maxSize=5,desiredSize=2 \
--region ap-northeast-1
# ウォームプールのインスタンスが Warmed:Stopped に戻るまで待つ(約16分)desiredSize が 2 に戻り、ウォームプールに Stopped インスタンスが2つある状態で検証を開始する。
まず、既存の2ノードをリソース要求の大きい Pod で埋め、3つ目の Pod を Pending にした。
Pod マニフェスト(filler-pod × 2 + pending-pod × 1)
# 既存ノードを埋める Pod × 2 + Pending になる Pod × 1
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: filler-pod-1
spec:
nodeSelector:
eks.amazonaws.com/nodegroup: ng-warm-stopped
containers:
- name: nginx
image: nginx:alpine
resources:
requests:
cpu: "1500m"
memory: "2Gi"
---
apiVersion: v1
kind: Pod
metadata:
name: filler-pod-2
spec:
nodeSelector:
eks.amazonaws.com/nodegroup: ng-warm-stopped
containers:
- name: nginx
image: nginx:alpine
resources:
requests:
cpu: "1500m"
memory: "2Gi"
---
apiVersion: v1
kind: Pod
metadata:
name: pending-pod
spec:
nodeSelector:
eks.amazonaws.com/nodegroup: ng-warm-stopped
containers:
- name: nginx
image: nginx:alpine
resources:
requests:
cpu: "1500m"
memory: "2Gi"
EOFt3.medium は 2 vCPU / 4 GiB で allocatable は約 1930m CPU だが、各ノードにはシステム Pod が動作しているため、1500m CPU を要求する Pod は1ノードに1つしか配置できない。結果として filler-pod-1 のみが Running になり、filler-pod-2 と pending-pod は Pending になった。
NAME READY STATUS AGE
filler-pod-1 1/1 Running 6s
filler-pod-2 0/1 Pending 6s
pending-pod 0/1 Pending 6sPending Pod が2つあるため、2ノードの追加が必要。Stopped ウォームプールから desiredSize を 2→4 に変更してスケールアウトを実行した。
aws eks update-nodegroup-config \
--cluster-name eks-sandbox --nodegroup-name ng-warm-stopped \
--scaling-config minSize=2,maxSize=5,desiredSize=4 \
--region ap-northeast-117:53:29 [2s] Running: 1/3
17:53:49 [22s] Running: 1/3
17:53:55 [28s] filler-pod-2: ContainerCreating ← ノード Ready、Pod スケジュール
17:54:21 [54s] Running: 2/3
17:54:28 [61s] pending-pod: Running
17:54:34 [67s] Running: 3/3 ← 全 Pod Running結果: 67秒(全 Pod Running まで)。内訳はノード Ready まで約48秒 + Pod スケジューリング + イメージプルに約19秒。Cluster Autoscaler を使う場合は、Pending Pod 検知→ASG desiredSize 変更のオーバーヘッドがさらに加わる(本記事では未計測)。
総合比較: poolState × スケーリング方式の組み合わせ
| パターン | ノード Ready までの時間 | 維持コスト概算(1インスタンス/月) | 備考 |
|---|---|---|---|
| コールドスタート(ウォームプールなし) | 62秒 | $0 | ベースライン |
| Stopped ウォームプール(初回) | 48秒(-23%) | 〜$2(EBS のみ) | OS 再起動のみ |
| Running ウォームプール(初回) | 28秒(-55%) | 〜$32(インスタンス + EBS) | ブートストラップのみ |
| reuseOnScaleIn 再利用(Stopped) | 35秒(-44%) | 〜$2(EBS のみ) | 再利用による高速化(推測: ブートストラップのキャッシュ) |
| Stopped + Pod Running まで | 67秒 | 〜$2(EBS のみ) | イメージプル含む |
poolState 選択の判断基準
計測結果から、以下の判断フレームワークを提示する。
- Running を選ぶべきケース — スケールアウトの速度が最優先で、30秒以内にノードを追加したい場合。インスタンス課金が継続するため、ウォームプールのサイズは最小限に抑える。バースト頻度が高く、1回あたりの遅延コストが大きいワークロード向け
- Stopped を選ぶべきケース — コスト効率を重視し、50秒程度のスケールアウト時間を許容できる場合。EBS 課金のみなので Running の約 1/16 のコスト。バースト頻度が低〜中程度のワークロード向け
- reuseOnScaleIn を有効にすべきケース — スケールイン/アウトが頻繁に発生する場合。再利用インスタンスは初回 Stopped より13秒速い。ただし、スケールイン→ウォームプール返却に約16分かかるため、短時間のバーストが連続する場合は効果が薄い
ウォームプールを導入しないほうが良いケース
- t3.medium のようなブート時間が短いインスタンスタイプでは、コールドスタートとの差が14-34秒にとどまる。ユーザーデータで初期化が重いワークロード(大量のパッケージインストールやデータダウンロード)ほど効果が大きい
- ウォームプールはカスタム AMI をサポートしない(EKS 最適化 AMI のみ)。カスタム AMI を使用している場合は利用できない。初期化処理はユーザーデータ(launch template)で行う必要がある
- Bottlerocket AMI では Hibernated 状態と reuseOnScaleIn が使えない
まとめ
- Running は速いが高い — 28秒でノード追加できるが、インスタンス課金が継続する。速度要件が厳しいワークロードに限定して使うべき
- Stopped はバランスが良い — 48秒で EBS 課金のみ。多くのワークロードではこれで十分
- reuseOnScaleIn は16分のドレイン待ちがボトルネック — 再スケールアウトは35秒と速いが、ウォームプールへの返却に16分かかった。EKS が自動設定するライフサイクルフック(1800秒タイムアウト)が関与しており、ドキュメントではこの値の手動変更は推奨されていない
- Pod Running までの時間はノード Ready + イメージプル — ノードが Ready になっても Pod が Running になるまで追加で約19秒かかる。イメージのプリプルやイメージサイズの最適化も合わせて検討すべき
クリーンアップ手順
REGION=ap-northeast-1
CLUSTER=eks-sandbox
# Pod 削除
kubectl delete pod filler-pod-1 filler-pod-2 pending-pod
# ノードグループ削除
for NG in ng-cold-start ng-warm-stopped ng-warm-running; do
aws eks delete-nodegroup --cluster-name $CLUSTER --nodegroup-name $NG --region $REGION
done
# ノードグループ削除完了を待つ
for NG in ng-cold-start ng-warm-stopped ng-warm-running; do
aws eks wait nodegroup-deleted --cluster-name $CLUSTER --nodegroup-name $NG --region $REGION
done
# アドオン削除(Auto Mode クラスターで追加した場合)
aws eks delete-addon --cluster-name $CLUSTER --addon-name vpc-cni --region $REGION
aws eks delete-addon --cluster-name $CLUSTER --addon-name kube-proxy --region $REGION
aws eks delete-addon --cluster-name $CLUSTER --addon-name coredns --region $REGION
# IAM ロール削除
aws iam detach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
aws iam detach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
aws iam detach-role-policy --role-name eks-warmpool-verify-node-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
aws iam delete-role-policy --role-name eks-warmpool-verify-node-role \
--policy-name ClusterAutoscalerPolicy 2>/dev/null # 追加した場合のみ
aws iam delete-role --role-name eks-warmpool-verify-node-role