NO IMAGE

EKS Cluster + Karpenter 本番運用 Terraform Container Insights

NO IMAGE
目次

1. この記事について

fig01: EKS + Karpenter 全体アーキテクチャ図

AWS EKS は 2018 年に GA して以来、Kubernetes 本番運用の主流基盤として採用が広がってきた。一方で 2021 年に AWS が公式リリースした Karpenter は、Cluster Autoscaler の制約 (ASG 単位の遅延 / インスタンスタイプ柔軟性の欠如) を解消する Node プロビジョナとして 2024 年に v1 API が GA し、EKS 本番運用の事実上の標準となりつつある。しかし日本語記事には「EKS Cluster の Terraform 実装だけ」「Karpenter の概念解説だけ」と分散しており、ECS / ecspresso 経験者が EKS に移行する判断軸を示しつつ、Cluster 起動 → Karpenter ノードプロビジョニング → アプリデプロイ → 監視 (Container Insights + Fluent Bit) までを 1 本で完走できるガイドが見当たらない。

本記事はその空白を 1 本で解消する。ECS との 5 軸比較から始まり、aws_eks_cluster + VPC + IAM + OIDC + EKS Add-ons の Terraform 完全実装、Karpenter v1 NodePool / EC2NodeClass の本番設計、aws eks update-kubeconfig + kubectl によるアプリデプロイ、Container Insights + Fluent Bit DaemonSet による監視スタックまでを Terraform 完全 HCL + AWS CLI + コンソール の 3 形式で解説する。読者が「そのままコピー&ペーストで本番 EKS + Karpenter 基盤を構築できる」ことをゴールとしている。

【シリーズ】EKS 本番運用シリーズ (全3巻)

  • Vol1 (本記事): EKS Cluster + Karpenter による本番運用基盤完全ガイド — Cluster 起動から Karpenter ノードプロビジョニング・アプリデプロイ・監視まで 1 本完走
  • Vol2: IRSA (IAM Roles for Service Accounts) 完全活用編 — ServiceAccount 単位の最小権限設計と Pod 認証の本番運用 (準備中)
  • Vol3: ALB Ingress Controller + Argo CD GitOps 編 — ALB Ingress の本番設計と Argo CD による GitOps デプロイパイプライン (準備中)
本記事の対象読者

  • ECS / Fargate / ecspresso で本番運用しており EKS への移行 / 併用を検討している中〜上級者
  • EKS Cluster Autoscaler から Karpenter への移行を検討中のチーム
  • Terraform で EKS 基盤を一気通貫構築したいインフラエンジニア

1-1. なぜ今 EKS + Karpenter か

EKS と Karpenter が GA してから時間は経ったが、日本語記事には依然として 4 つの空白がある。

空白①: ECS / ecspresso 経験者向けの移行判断軸が希少

EKS 単独の入門記事は多いが、ECS Fargate CICD / ecspresso 既存運用チームが「EKS に移るべきか・併用すべきか」を判断する 5 軸比較が国内に少ない。本記事 §3 でこの空白を埋める。

空白②: Karpenter v1 API + Consolidation の本番設計が散在

Karpenter v1 (2024 GA) の NodePool / EC2NodeClass / Consolidation policy を本番運用視点で示した記事が国内に少ない。本記事 §5 で QG-3 として設計指針を提示する。

空白③: EKS Cluster + Karpenter + Add-ons + 監視を 1 本で完走するガイドがない

aws_eks_cluster + VPC + OIDC + Add-ons + Karpenter helm + Container Insights + Fluent Bit を Terraform 完全実装で示した記事が見当たらない。本記事 §4-§7 で完走実装する。

空白④: ECS / ecspresso 既公開記事との連携導線がない

国内既公開の ECS Fargate CICD / ecspresso 記事と EKS の関係性を示す記事が少ない。本記事 §3 で多重バックリンクを配置する。

1-2. 本記事のゴール

本記事を読み終えると以下を単独で実施できる。

ゴール対応章
ECS / Fargate / ecspresso と EKS の選定判断 (5 軸比較)§3
EKS Cluster の Terraform 完全実装 (VPC / IAM / OIDC / Add-ons)§4
Karpenter v1 NodePool / EC2NodeClass の本番設計§5
aws eks update-kubeconfig + kubectl によるアプリデプロイ§6
Container Insights + Fluent Bit による標準監視スタック§7
落とし穴 10 選を回避した本番投入§8

1-3. 差別化6軸

EKS + Karpenter の実装記事は国内でも増えているが、多くは「Cluster 起動だけ」「Karpenter 概念解説だけ」と分断されており、ECS/ecspresso 経験者が一気通貫で本番基盤を構築できるガイドは少ない。本記事はその空白を以下 6 軸で埋める。

本記事の差別化6軸

  1. EKS + Karpenter v1 API を 1 本で完走: Cluster 起動 → Karpenter NodePool / EC2NodeClass → Deployment + Service → Container Insights + Fluent Bit まで Terraform 完全 HCL で一気通貫実装。「手順が分散している」問題を 1 記事で解消する。
  2. ECS / ecspresso との 5 軸比較 + 移行判断フロー: ECS Fargate CICD Vol1/Vol2 / ecspresso 既公開記事との対比で「EKS に移るべきか・併用すべきか」を 5 軸 (運用負荷 / 学習コスト / エコシステム / カスタマイズ性 / コスト) で判断できる。
  3. Karpenter v1 NodePool / EC2NodeClass / Consolidation の本番設計: spot + On-Demand 混在 / interruption handling / consolidation policy (WhenEmptyOrUnderutilized) を本番設計として実装で示す。v0 → v1 API 変更点も明記。
  4. EKS Add-ons + aws-ebs-csi-driver + IRSA 連携の入口: vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver の managed add-ons 管理 + OIDC Provider 設定まで。Vol2 IRSA で ServiceAccount 単位の最小権限設計に深掘りする。
  5. Container Insights + Fluent Bit による標準監視スタック: DaemonSet / IAM Role / IRSA / CloudWatch Logs 転送の統合実装を Terraform で示す。ECS の CloudWatch Logs driver 経験者が EKS 監視設計を理解しやすい導線を配置。
  6. 3 点セット (Terraform + AWS CLI + コンソール): 全ステップで Terraform HCL 完全掲載 + aws eks コマンド + コンソール確認手順を併記。「コピー&ペーストで動く」を担保しつつ、コンソールで内部状態を視覚的に確認できるよう設計。
差別化軸国内の一般的な記事本記事
EKS + Karpenter + 監視を 1 本完走Cluster または Karpenter 単独✅ 一気通貫
ECS / ecspresso との比較・移行判断EKS 単独のみ✅ 5 軸比較 + フロー
Karpenter v1 本番設計概念解説のみ✅ 実装で示す
EKS Add-ons (vpc-cni / ebs-csi-driver)省略されがち✅ 完全実装
監視スタック (Container Insights + Fluent Bit)言及のみ✅ DaemonSet Terraform
Terraform + AWS CLI + コンソール 3 形式1 形式のみが多い✅ 3 点セット

1-4. 章立て

§Nタイトル概要
§2前提・環境・準備aws-cli v2 / kubectl 1.30 / helm 3.14 / terraform 1.9.x / IAM
§3EKS 全体像 + ECS / Fargate との比較移行判断フロー + ECS Vol1/Vol2 / ecspresso 比較
§4EKS Cluster Terraform 完全実装aws_eks_cluster / VPC / IAM / OIDC / EKS Add-ons
§5Karpenter による Node プロビジョニングNodePool / EC2NodeClass / spot / consolidation
§6アプリデプロイ + Cluster アクセス手順aws eks update-kubeconfig / Deployment / Service / kubectl
§7監視 (Container Insights + Fluent Bit)CloudWatch Container Insights / Fluent Bit DaemonSet / IRSA 予告
§8まとめ + Vol2 IRSA 予告 + 落とし穴 10 選チートシート + シリーズナビ + ECS / ecspresso クロスリンク

1-5. 想定環境

本記事の動作確認環境を以下に示す。Terraform / aws-cli / kubectl / helm / Karpenter のバージョンを揃えることで、記事のコマンドがそのまま実行できる。

ツール / サービスバージョン備考
Terraform1.9.xAWS Provider 5.x
AWS CLIv2 (最新)aws configure で認証設定済
kubectl1.30EKS Cluster のバージョンに合わせる
helm3.14Karpenter helm chart のインストールに使用
Karpenterv1.0 (2024 GA)v0 系 API は非対応
OSmacOS 14+ / Ubuntu 22.04+WSL2 可
AWS リージョンap-northeast-1 (東京)VPC CIDR は 10.0.0.0/16 を前提

注意: Karpenter v1 は NodePool / EC2NodeClass API が v0 系と非互換。v0 → v1 移行の場合は Karpenter v1 Migration Guide を先に確認すること。

本記事の Terraform を実行するには以下の IAM 権限が必要となる。本番環境では最小権限ポリシーに絞ることを推奨する。

IAM アクション用途
eks:*EKS Cluster / Add-ons / NodeGroup 管理
iam:*IAM Role / Policy / OIDC Provider 作成
ec2:*VPC / Subnet / SG / ENI 管理
ecr:*コンテナイメージ pull
logs:*Container Insights / Fluent Bit ログ転送
cloudwatch:*メトリクス取得・ダッシュボード操作

Tips: 複数 AWS アカウントを管理する場合、AWS_PROFILE=your_profile terraform plan で AWS CLI Named Profile を指定する。

§3 EKS 全体像 + ECS / Fargate との比較に進む


2. 前提・環境・準備

2-1. 前提環境

本記事を実際に動作させるには、以下の前提条件を満たしている必要がある。

AWS アカウントと IAM 権限

有効な AWS アカウントと、以下の権限を持つ IAM ユーザー / Role が必要。開発環境では AdministratorAccess を付与する場合もあるが、本番環境では最小権限ポリシーを設計することを推奨する。

  • eks:* — EKS Cluster / NodeGroup / Add-ons の作成・管理
  • iam:* — IAM Role / Policy / OIDC Provider の作成
  • ec2:* — VPC / Subnet / Security Group / ENI の管理
  • ecr:* — ECR リポジトリ / コンテナイメージ pull
  • logs:* — CloudWatch Logs ストリーム作成・書き込み
  • cloudwatch:* — Container Insights メトリクス送信

ローカル開発環境

以下のツールをローカルマシン (macOS / Linux / WSL2) にインストールしておく。

# Terraform (tfenv での管理を推奨)
tfenv install 1.9.5 && tfenv use 1.9.5
terraform version  # Terraform v1.9.x を確認

# AWS CLI v2
aws --version  # aws-cli/2.x.x を確認

# kubectl (EKS Cluster と同バージョンを推奨)
kubectl version --client  # v1.30 を確認

# helm
helm version  # v3.14.x を確認

aws configure または AWS_PROFILE 環境変数で EKS を作成するアカウントの認証情報を設定しておくこと。複数アカウントを管理する場合は AWS CLI Named Profiles を活用する。

2-2. 使用技術スタック

本記事で使用するサービス・ライブラリのバージョンを一覧化する。

カテゴリサービス / ツールバージョン / 備考
コンテナオーケストレーションAmazon EKS1.30
ノードプロビジョニングKarpenterv1.0 (2024 GA)
ネットワークAmazon VPCTerraform で管理
IaCTerraform1.9.x / AWS Provider 5.x
コンテナランタイムcontainerdEKS 標準
IAM 連携IRSAVol2 で詳解
EKS Add-onsvpc-cni / coredns / kube-proxy / aws-ebs-csi-driverEKS マネージド版
監視CloudWatch Container Insights
ログ収集Fluent BitDaemonSet として稼働
CLIkubectl / helm1.30 / 3.14

2-3. ゴール状態の定義

本記事を完走した時点で、以下の状態が再現可能になっている。

成果物確認状態
Amazon EKS Cluster (1.30)aws eks describe-cluster で ACTIVE
EKS Managed Add-ons 4 件vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver が ACTIVE
Karpenter helm releasekubectl get pods -n kube-system で Running
Karpenter NodePool / EC2NodeClasskubectl get nodepool,ec2nodeclass で Ready
サンプルアプリ Deployment + Servicekubectl get pods で Running / EXTERNAL-IP 取得済
Container InsightsCloudWatch > Container Insights でメトリクス確認可能
Fluent Bit DaemonSetkubectl get ds -n amazon-cloudwatch で Running

2-4. EKS / Karpenter 用語整理

EKS / Kubernetes の主要用語を ECS 経験者向けに整理する。§3 以降の内容を読む前に確認しておくと理解が深まる。

用語説明ECS での対応概念
Control PlaneKubernetes API サーバ / etcd / スケジューラ。EKS では AWS がフルマネージドで提供ECS ControlPlane (AWS マネージド)
Data Planeアプリが動作するワーカーノード (EC2)。EKS では EC2 が主体ECS Worker (EC2 / Fargate)
NodeGroup同一設定の EC2 ノード群 (ASG 管理)。Karpenter 導入後は管理ノードのみに縮小可ECS Auto Scaling Group
KarpenterNodePool に基づいて最適な EC2 を自動プロビジョニングするスケーラ。v1 で GAECS Capacity Provider
NodePoolKarpenter のスケーリングポリシー。インスタンスファミリ / 購入オプション / spot を定義ECS Capacity Provider 設定
EC2NodeClassKarpenter ノードの AMI / Subnet / SG などを定義するリソースEC2 Launch Template
Consolidation使用率の低いノードを自動統合・終了してコストを最適化する Karpenter の機能ECS task packing
Add-onsEKS が管理する Kubernetes システムコンポーネント (vpc-cni / coredns 等)ECS Agent
OIDC ProviderServiceAccount トークンを AWS IAM に連携する ID プロバイダなし (ECS は直接 Task Role)
IRSAOIDC を使って Pod 単位で IAM Role を付与する仕組み (Vol2 で詳解)ECS Task Role (概念は類似)
PodKubernetes の最小デプロイ単位。1 以上の Container を内包ECS Task
DeploymentPod のレプリカ数管理・ローリングアップデートを行うリソースECS Service
ServicePod へのネットワークアクセスを提供するリソース (ClusterIP / LoadBalancer 等)ECS Service / ALB TargetGroup

2-5. ECS / ecspresso との対比 (前置き)

§3 の詳細比較に先立ち、ECS / ecspresso の主要概念と EKS の対応を一覧化する。ECS 経験者が EKS の学習コストを見積もる際の参考にしてほしい。

ECS 概念EKS / Kubernetes 対応概念主な差異
Task DefinitionPod Spec (Deployment spec)EKS は YAML / HCL で宣言的に管理
ServiceDeployment + Service (ClusterIP/LB)EKS は Deployment と Service を分離
ClusterClusterEKS は Control Plane を AWS がマネージド
ECS AgentkubeletNode 上で動作するエージェント (役割は同様)
FargateKarpenter (EC2 自動プロビジョニング)EKS は EC2 Karpenter が主体 (Fargate も可)
ALB + TargetGroupService (LoadBalancer) / ALB IngressEKS は Ingress Controller が別途必要
CloudWatch Logs driverFluent Bit DaemonSetEKS は DaemonSet ベースのログ収集
Task Role (IAM)IRSA (Pod 単位 IAM Role)EKS は OIDC 経由で ServiceAccount に紐付け

ecspresso との詳細比較については §3 で扱う。ecspresso の Terraform 統合は ecspresso Terraform Integration を参照。


3. EKS 全体像 + ECS / Fargate との比較

本章は本記事の核心となる差別化セクションです。EKS のアーキテクチャ全体像を把握したうえで、すでに ECS / Fargate / ecspresso を運用している読者が「なぜ EKS に移行するのか」「いつ移行すべきか」「逆に移行してはいけないのはどんな場合か」を明確に判断できる材料を提供します。

本章を読めば、EKS の採否を自信を持って意思決定できるようになります。既存の ECS 資産を最大限活用しながら段階的に移行する方法も解説します。

3-1. EKS の全体アーキテクチャ

EKS は Control PlaneData Plane の 2 層で構成されます。fig01 を参照しながら各レイヤーの役割を確認しましょう。

Control Plane (AWS マネージド)

Control Plane は AWS が完全に管理する領域です。以下のコンポーネントが Multi-AZ のマネージドインフラ上で動作します。

  • API Server — kubectl や CI/CD ツールからの API リクエストを受け付ける入り口。認証 (OIDC / IAM) と認可 (RBAC) を担当します
  • etcd — クラスタ状態 (Pod / Service / ConfigMap など) を保存する分散 KV ストア。AWS がバックアップと HA を管理
  • Controller Manager — ReplicaSet / Deployment などのリコンシリエーションループを実行。期待状態と実状態の差を自動修正
  • Scheduler — 新規 Pod を適切な Node に割り当てるスケジューリング判断を行う

コントロールプレーンの費用は $0.10/hr (約 72 USD/月) 固定で発生します。EC2 ノードとは別請求です。

Data Plane (Karpenter が管理する EC2 Node)

Data Plane は実際のコンテナが動くレイヤーです。本シリーズでは Cluster Autoscaler の代わりに Karpenter を採用します。

  • Karpenter は Pending Pod の resource requests を検知すると、NodePool 定義に従い EC2 インスタンスを直接プロビジョニングします
  • Node が不要になると Consolidation ポリシーに基づき自動的に終了させ、コストを最適化します
  • Spot インスタンスの Interruption も検知・処理し、Pod を安全に退避させます

VPC / Subnet 設計

  • Public Subnet — NAT Gateway / ALB Ingress Controller (Vol3) を配置
  • Private Subnet — Worker Node (EC2) はここに配置。外部から直接到達不可にして攻撃面を最小化
  • Subnet には kubernetes.io/cluster/{cluster-name}=shared タグが必須 (Karpenter のノード検出に使用)

OIDC Provider と IRSA

EKS クラスタには OIDC Provider が自動作成されます。これを利用して IRSA (IAM Roles for Service Accounts) を実現します。Pod が AWS API を呼ぶ際にノードの IAM Role を共有するのではなく、Pod 単位で最小権限の IAM Role を割り当てられます。詳細は Vol2 で解説します。

Network Plugin: Amazon VPC CNI

EKS のデフォルト Network Plugin は Amazon VPC CNI です。Pod に VPC のプライマリ IP を直接割り当てるため、コンテナが VPC ネットワーク内でファーストクラスのルーティングを受けられます。

  • ECS の場合、awsvpc モードで Task に ENI を 1 つ割り当てる設計です
  • EKS + VPC CNI では各 Pod が独立した VPC IP を持つため、Security Group for Pods (Vol2 で解説) が利用可能です
  • IP 枯渇対策として Prefix Delegation (ENABLE_PREFIX_DELEGATION=true) を有効化することを推奨します (EC2 1 台あたりの割り当て Pod 数が大幅に増加)

Kubernetes バージョン管理

EKS は Kubernetes の新バージョンを GA から数ヶ月後にサポートします。各バージョンのサポート期間は 14 ヶ月で、期限を過ぎると自動アップグレードが実施されます。

  • 年に 2〜3 回のマイナーバージョンアップが必要 (ECS には不要な作業)
  • Add-on (vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver) も同時にアップデートが必要
  • aws_eks_cluster の version フィールドを Terraform で管理し、計画的なアップデートを実施します
QG-1: EKS 全体アーキテクチャ + ECS 比較ポイント

  • Control Plane / Data Plane の責務分担 — Control Plane (API Server / etcd / Scheduler / Controller Manager) は AWS フルマネージド。Data Plane (Worker Node) は Karpenter が NodePool 定義に従い EC2 を動的プロビジョニング。ECS の場合は Fargate ならノード管理ゼロ・EC2 起動型なら ECS Agent が常駐するという違いがある
  • OIDC Provider による IRSA 統合点 — EKS クラスタ作成時に OIDC Issuer URL が発行される。aws_iam_openid_connect_provider リソースで信頼ポリシーを構成し、ServiceAccount に IAM Role を紐づけることで Pod 単位の最小権限を実現 (Vol2 で深掘り)。ECS の場合は Task Execution Role / Task Role を Task Definition に直接指定する方式
  • ECS Task Definition と EKS Pod / Deployment の対応マトリクス
    • ECS Task Definition → EKS Pod spec (containers / resources / env / secrets)
    • ECS Service (desired count) → EKS Deployment (replicas)
    • ECS Task Role → EKS ServiceAccount + IRSA Role
    • ECS Service Discovery (Cloud Map) → EKS Service (ClusterIP / DNS)
    • ECS ALB Target Group → EKS Service (LoadBalancer / Ingress)

3-2. ECS / Fargate / ecspresso 比較 (5 軸)

fig02: ECS vs EKS 比較・移行判断フロー

既存の ECS / Fargate 運用者が EKS への移行を検討する際に最も重要な 5 軸で比較します。

比較軸ECS FargateECS EC2 (ecspresso)EKS + Karpenter判定
運用負荷低 (ノード管理ゼロ)中 (EC2 AMI 更新が必要)中〜高 (k8s バージョンアップ / Add-on 管理)ECS Fargate 有利
学習コスト低 (AWS ネイティブ概念のみ)低〜中 (ecspresso 学習)高 (k8s / Helm / Karpenter 必須)ECS 有利
エコシステムAWS 固有ツール (CodePipeline / ECR)AWS + ecspressoCNCF (Helm / Argo CD / Istio / Prometheus)EKS 有利
カスタマイズ性限定的 (Task Definition 範囲内)中 (EC2 ユーザーデータ)高 (Admission Webhook / CRD / Operator)EKS 有利
コスト効率中 (Fargate 単価高め)中 (On-Demand EC2 固定)中〜低 (Spot + Consolidation で最適化)EKS 有利 (規模次第)

詳細解説: 運用負荷

ECS Fargate はノード管理が完全に不要なため、少人数チームや運用工数を最小化したいケースで圧倒的な優位性があります。EKS は Control Plane こそ AWS 管理ですが、Add-on (vpc-cni / coredns / kube-proxy) のバージョン管理、NodePool の設計、Karpenter のアップデートなど、継続的な運用タスクが発生します。

詳細解説: エコシステム

EKS の最大の強みは CNCF エコシステムへのアクセスです。

  • Helm — アプリケーションのパッケージ管理。helm install/helm upgrade でアプリを宣言的に管理
  • Argo CD — GitOps による継続的デプロイ。Git リポジトリの状態をクラスタに自動同期
  • Istio / AWS App Mesh — サービスメッシュによる mTLS / トラフィック制御
  • Prometheus + Grafana — Kubernetes ネイティブなメトリクス収集と可視化
  • Keda — イベントドリブンなオートスケーリング (SQS / Kafka メッセージ数ベースなど)

これらは ECS では実現困難または AWS 固有の代替手段に限られます。

詳細解説: コスト効率

Fargate は CPU/Memory 単位で課金されるため、リソースを細かく指定できますが単価は EC2 より高めです。EKS + Karpenter の構成では Spot インスタンス (通常比 70〜90% 割引) と Consolidation による自動パッキングを組み合わせることで、大規模ワークロードほどコスト優位性が高まります。ただし コントロールプレーン費用 $0.10/hr が固定でかかるため、小規模では逆にコスト増になります。

コスト試算例 (月額 / 東京リージョン)

構成EC2/FargateControl Plane合計 (目安)
ECS Fargate (小規模: 4 vCPU / 8 GB)約 $120なし約 $120
ECS EC2 (m5.xlarge × 2)約 $280なし約 $280
EKS + On-Demand (m5.xlarge × 2)約 $280$72約 $352
EKS + Karpenter Spot (c5.2xlarge × 2 相当)約 $80$72約 $152
EKS + Karpenter Spot (大規模: 20 vCPU 相当)約 $200$72約 $272

Spot 活用でコントロールプレーン固定費 $72 を吸収できるのは、概ね 月額 EC2 コストが $200 以上のケースです。それ以下の規模では ECS Fargate のほうがシンプルかつ安価になります。

3-3. ECS Fargate CICD Vol1 / Vol2 / ecspresso との連携

本シリーズは既公開の ECS 記事群の続きとして位置づけています。EKS を理解するために ECS との差分ポイントを把握することが重要です。

既公開記事との関係性

  • ECS Fargate CICD Vol1 Rolling Deploy — CodePipeline + ECR + ECS ローリングデプロイの基礎構成。EKS では kubectl rollout が対応する概念だが、CI/CD ツール自体は CodePipeline or GitHub Actions から kubectl apply を呼ぶ形に変わる
  • ECS Fargate CICD Vol2 Blue/Green Deploy — CodeDeploy による Blue/Green デプロイ。EKS では Argo Rollouts や ALB Ingress Controller の重み付けルーティングで同等機能を実現。Vol3 で詳解予定
  • ecspresso Terraform 統合運用ガイド — ecspresso による ECS サービス管理。EKS では kubectl + Helm + Argo CD がこの役割を担う。Terraform で EKS クラスタ・IAM・VPC を管理し、アプリレイヤーは Helm values でパラメータ化するのが標準パターン

移行時の対応マッピング

既存の ECS + ecspresso 構成から EKS へ移行する際の作業対応表:

ECS / ecspresso の資産EKS での対応移行難易度
Task Definition JSONPod spec / Deployment YAML低 (構造は類似)
ecspresso ecs.jsonvalues.yaml (Helm)
CodePipeline デプロイアクションkubectl apply / helm upgrade
ECS Service Auto ScalingHPA / Karpenter NodePool
ALB Target GroupService (LoadBalancer) / Ingress
ECS Execkubectl exec低 (コマンド変更のみ)
CloudWatch Container InsightsEKS Add-on で同機能利用可

3-4. 移行判断フロー

ECS から EKS への移行は「できるかどうか」ではなく「すべきかどうか」の判断が重要です。以下の条件を確認してください。

EKS への移行を推奨するケース

次の条件のうち 2 つ以上に該当する場合、EKS 移行の費用対効果が高い:

  1. Kubernetes エコシステムが必要 — Helm / Argo CD / Istio / Prometheus を導入したい、またはマルチクラウド対応が将来要件にある
  2. 大規模スケーリングとコスト最適化が必要 — Spot インスタンス活用と Karpenter Consolidation で月間コストを 30〜60% 削減したい (目安: 月額 $1,000 以上の EC2 費用があるケース)
  3. 高度なカスタマイズが必要 — カスタム Admission Webhook、CRD、Operator パターンによる自動化が要件にある
  4. マルチテナント / 大規模チーム — Namespace による分離、RBAC の細粒度制御、複数チームが同一クラスタを共有する要件がある
  5. オンプレ / 他クラウドとの統一基盤 — EKS + EKS Anywhere または他クラウドの Kubernetes サービスと共通の運用手順を持ちたい

ECS 継続が正解なケース

以下に 1 つでも該当する場合、EKS への移行は慎重に検討:

  1. チーム規模が小さく k8s 学習リソースが不足 — Kubernetes の学習曲線は急峻。専任 SRE なしで EKS を運用すると、インシデント対応が困難になる可能性がある
  2. ワークロードが Fargate で十分 — コンテナ数が少なく、スケーリング要件がシンプルな場合は ECS Fargate のサーバーレス運用が最適
  3. AWS ネイティブツールが確立済み — CodePipeline / CodeBuild / ecspresso の運用フローが安定しており、切り替えコストが移行メリットを上回る
  4. コントロールプレーン費用が割高 — 月額 $72 固定費が事業規模に対して大きい場合は ECS Fargate の従量課金のほうが経済的

移行判断チェックリスト

  • [ ] Kubernetes 経験者がチームに 1 名以上いる
  • [ ] 月間 EC2 / Fargate コストが $1,000 を超えている
  • [ ] Helm / Argo CD / サービスメッシュの導入計画がある
  • [ ] マルチクラウドまたはオンプレ統合の要件がある
  • [ ] チームが k8s 学習に 1〜2 ヶ月を投資できる

3 つ以上チェックが付いた場合、EKS 移行の投資回収が見込めます。2 つ以下の場合は ECS の継続運用を優先し、チームの Kubernetes 習熟度が上がってから改めて評価することを推奨します。

推奨移行アプローチ: ストラングラー・フィグ戦略

一気に全サービスを EKS に切り替えるのはリスクが高く、以下の段階的アプローチを推奨します。

  1. Phase 1 (1〜2 ヶ月): 本記事の構成でステージング環境に EKS を構築。チームが Karpenter / kubectl を習熟
  2. Phase 2 (2〜3 ヶ月): 影響が小さいバッチ処理や内部 API サービスを EKS に移行。ECS と並行運用
  3. Phase 3 (3〜6 ヶ月): トラフィックが多いフロントサービスを EKS に段階切り替え。ALB Ingress (Vol3) + Argo CD (Vol4 予定) を整備
  4. Phase 4: ECS サービスを順次廃止。EKS 一本化

この進め方であれば、ECS の安定した運用基盤を維持しながらリスクを最小化して移行できます。

3-5. 採用してはいけないケース

EKS は強力なプラットフォームですが、以下のケースでは採用が逆効果になります。

危険パターン 1: 運用人員が少なく k8s 学習コストが払えない

EKS の本番運用には Kubernetes の深い理解が必要です。etcd の障害対応、API Server の認証設定ミス、Karpenter の誤設定によるノード枯渇など、ECS では発生しなかった障害パターンが多数あります。専任 SRE または Kubernetes 経験者なしでの本番導入はリスクが高く、障害時に原因特定だけで数時間を要するケースがあります。

危険パターン 2: 全ワークロードが Fargate で十分な小規模

コンテナが 10〜20 個程度のサービスで ECS Fargate が安定稼働しているなら、EKS を導入しても管理コストが増えるだけです。Kubernetes の真価はスケール (数十〜数百の Pod / 複数チーム / 複数マイクロサービス) で発揮されます。

危険パターン 3: コントロールプレーン費用が事業規模に対して割高

EKS は月額 $72 (約 1 万円) のコントロールプレーン費用が固定でかかります。月売上が数十万円規模のスタートアップ期や PoC 環境では、この固定費が運用上の制約になります。開発 / ステージング環境は ECS / LocalStack で代替し、本番のみ EKS を検討するアーキテクチャも有効です。

危険パターン 4: 「流行っているから」という理由だけの採用

EKS / Kubernetes は 2024〜2025 年にかけてデファクトスタンダードになりましたが、それは大規模・複雑なワークロードにおける話です。シンプルな Web API や小規模マイクロサービスは ECS Fargate + ecspresso のほうが運用負荷が低く、信頼性も高いケースが多くあります。技術選定は「業務要件を満たす最もシンプルな手段」が原則です。

本章のまとめ

観点EKS を選ぶ理由ECS を選ぶ理由
チームk8s 経験者がいる少人数・学習コスト払えない
スケール大規模 (数十〜数百 Pod)小〜中規模
エコシステムCNCF ツール必須AWS ネイティブで完結
コストSpot 活用で最適化したいFargate 従量課金で十分
将来性マルチクラウド / オンプレ統合AWS 専用で問題なし

本記事の Vol1 〜 Vol4 シリーズを通じて EKS + Karpenter の本番運用基盤を段階的に構築していきます。§4 以降では Terraform による具体的な実装に入ります。


4. EKS Cluster Terraform 完全実装

fig03: EKS Cluster Terraform リソース構成図

EKS Clusterを本番環境でTerraformにより構築するには、VPC・IAM Role・OIDC Provider・Add-onsといった複数のAWSリソースを正しい依存順序で定義する必要がある。本章では各リソースの実装コードと依存関係を順を追って解説し、terraform apply 一発で本番稼働可能なEKS Clusterが完成することを目標とする。KarpenterがノードProvisioningに利用するSubnetタグやIRSA用OIDC Providerも含めた完全構成を示す。

4-1. 必須リソース概観

QG-2: EKS Cluster Terraform 必須リソースチェックリスト

  • aws_eks_cluster: version="1.30" / vpc_config (private + public subnet 全指定) / encryption_config (KMS で secrets 暗号化)
  • VPC / Subnets (public ×2 + private ×2): kubernetes.io/cluster/{name}=shared タグ (Karpenter 必須) / public は kubernetes.io/role/elb=1 / private は kubernetes.io/role/internal-elb=1
  • IAM Role: cluster role (AmazonEKSClusterPolicy) + node role (AmazonEKSWorkerNodePolicy / AmazonEKS_CNI_Policy / AmazonEC2ContainerRegistryReadOnly)
  • aws_iam_openid_connect_provider: OIDC 発行者 URL + thumbprint_list を設定 — IRSA の基盤 (Vol2 で深掘り予告)
  • aws_eks_addon 4本: vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver をクラスタ作成後に一括管理

fig03 に示す通り、EKS Clusterの構築は依存関係に沿って以下の順序で実行する必要がある。

依存順序 (terraform apply が内部で解決):

  1. ネットワーク層: aws_vpcaws_subnet (public ×2 / private ×2) → aws_internet_gateway / aws_nat_gatewayaws_route_table
  2. IAM 層: aws_iam_role (cluster / node) → aws_iam_role_policy_attachment (各ポリシーアタッチ)
  3. EKS 本体: aws_eks_cluster (VPC + IAM + KMS を参照。depends_on = [aws_iam_role_policy_attachment.eks_cluster] が必須)
  4. OIDC 層: aws_iam_openid_connect_provider (EKS クラスタの OIDC 発行 URL を参照するため、クラスタ作成後に作成)
  5. Add-ons 層: aws_eks_addon 4本 (クラスタ起動後に追加。cluster_name を参照)

この依存グラフを崩すと terraform apply が失敗するため、depends_on の設定が重要なポイントとなる。特に aws_eks_cluster への depends_on = [aws_iam_role_policy_attachment.eks_cluster] を省略すると、IAM Policy がアタッチされる前にクラスタ作成が始まり権限エラーになるため要注意だ。

KMS による Secrets 暗号化 (encryption_config) は、etcd に格納される Kubernetes Secrets を AWS KMS で保護する機能であり、本番環境では必須の設定となる。暗号化対象は resources = ["secrets"] と指定する。

OIDC Provider は aws_iam_openid_connect_provider で作成し、EKS クラスタが発行する OIDC トークンを AWS IAM が検証できるようにする。これが IRSA (IAM Roles for Service Accounts) の基盤となり、Vol2 で詳しく解説する。

4-2. VPC / Subnet 設計

EKS と Karpenter が正しく動作するためには、VPC と Subnet に固有の Kubernetes タグを付与する必要がある。Karpenter はこのタグを手がかりにノードを起動する Subnet を選択する。

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "main" {
  cidr_block  = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support= true

  tags = {
 Name = "${var.cluster_name}-vpc"
 "kubernetes.io/cluster/${var.cluster_name}" = "shared"
  }
}

# パブリック Subnet: ALB ターゲット / Bastion 配置 (ap-northeast-1a / 1c)
resource "aws_subnet" "public" {
  count = 2
  vpc_id= aws_vpc.main.id
  cidr_block  = cidrsubnet("10.0.0.0/16", 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
 Name = "${var.cluster_name}-public-${count.index}"
 "kubernetes.io/cluster/${var.cluster_name}" = "shared"
 "kubernetes.io/role/elb"  = "1"
  }
}

# プライベート Subnet: EKS Node (Karpenter) / Pod 配置 (ap-northeast-1a / 1c)
resource "aws_subnet" "private" {
  count = 2
  vpc_id= aws_vpc.main.id
  cidr_block  = cidrsubnet("10.0.0.0/16", 8, count.index + 10)
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
 Name = "${var.cluster_name}-private-${count.index}"
 "kubernetes.io/cluster/${var.cluster_name}" = "shared"
 "kubernetes.io/role/internal-elb"  = "1"
  }
}

タグ設計のポイント:

  • kubernetes.io/cluster/{name}=shared は EKS Control Plane と Karpenter が Subnet を自動検出するために必須。owned ではなく shared を使うことで複数クラスタが同一 VPC を共有できる。
  • public Subnet の kubernetes.io/role/elb=1 は AWS Load Balancer Controller が External ALB を配置する Subnet を検出するために使用する。
  • private Subnet の kubernetes.io/role/internal-elb=1 は Internal ALB / NLB のターゲット Subnet 選択に使用する。
  • Karpenter の EC2NodeClasssubnetSelectorTerms にはこの kubernetes.io/cluster/{name}=shared タグを指定することでノード配置 Subnet を絞り込む。

4-3. aws_eks_cluster 完全実装

EKS Cluster 本体と付随する IAM / OIDC Provider の完全な Terraform HCL を示す。

# KMS Key for EKS Secrets encryption
resource "aws_kms_key" "eks" {
  description = "${var.cluster_name} EKS Secrets encryption"
  deletion_window_in_days = 7
  enable_key_rotation  = true
}

# IAM Role for EKS Control Plane
resource "aws_iam_role" "eks_cluster" {
  name = "${var.cluster_name}-cluster-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy_attachment" "eks_cluster" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  role = aws_iam_role.eks_cluster.name
}

# EKS Cluster 本体
resource "aws_eks_cluster" "main" {
  name  = var.cluster_name
  version  = "1.30"
  role_arn = aws_iam_role.eks_cluster.arn

  vpc_config {
 subnet_ids  = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)
 endpoint_private_access = true
 endpoint_public_access  = true
  }

  encryption_config {
 provider {
key_arn = aws_kms_key.eks.arn
 }
 resources = ["secrets"]
  }

  # IAM ポリシーアタッチ完了を待ってからクラスタを作成
  depends_on = [aws_iam_role_policy_attachment.eks_cluster]
}

# OIDC Provider (IRSA の基盤 — Vol2 で詳解)
data "tls_certificate" "eks" {
  url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

resource "aws_iam_openid_connect_provider" "eks" {
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
  url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

# IAM Role for EKS Node (Karpenter が起動するノードが使用)
resource "aws_iam_role" "eks_node" {
  name = "${var.cluster_name}-node-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy_attachment" "eks_node_worker" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
  role = aws_iam_role.eks_node.name
}

resource "aws_iam_role_policy_attachment" "eks_node_cni" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
  role = aws_iam_role.eks_node.name
}

resource "aws_iam_role_policy_attachment" "eks_node_ecr" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
  role = aws_iam_role.eks_node.name
}

実装のポイント:

  • endpoint_private_access = true は必須。VPC 内からの API Server アクセスが PrivateLink 経由になり、インターネット経由の依存を排除できる。
  • endpoint_public_access = true は開発時の便宜のため有効にしているが、セキュリティ要件が高い場合は false に変更し Bastion または VPN 経由でのみアクセスする構成を推奨する。
  • node role への 3 ポリシー (AmazonEKSWorkerNodePolicy / AmazonEKS_CNI_Policy / AmazonEC2ContainerRegistryReadOnly) は Karpenter 管理ノードでも必須となる。

4-4. EKS Add-ons 管理

EKS Add-ons は aws_eks_addon リソースで管理する。4 本のコア Add-on はクラスタ作成直後に有効化することを推奨する。

# VPC CNI: Pod に VPC IP を直接割り当てる (必須)
resource "aws_eks_addon" "vpc_cni" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "vpc-cni"
  # 固定する場合: addon_version = "v1.18.0-eksbuild.1"

  depends_on = [aws_eks_cluster.main]
}

# CoreDNS: クラスタ内 DNS 解決 (必須)
resource "aws_eks_addon" "coredns" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "coredns"

  depends_on = [aws_eks_addon.vpc_cni]
}

# kube-proxy: ノード間通信ルーティング (必須)
resource "aws_eks_addon" "kube_proxy" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "kube-proxy"

  depends_on = [aws_eks_cluster.main]
}

# EBS CSI Driver: PersistentVolume (EBS) の動的プロビジョニング (推奨)
resource "aws_eks_addon" "ebs_csi_driver" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "aws-ebs-csi-driver"
  service_account_role_arn = aws_iam_role.ebs_csi.arn

  depends_on = [aws_iam_openid_connect_provider.eks]
}

バージョン管理方針:

  • addon_version を省略した場合、Terraform は EKS クラスタバージョンに対応するデフォルト推奨バージョンを自動選択する。
  • 本番環境では addon_version を明示的に固定し、terraform plan で意図しないバージョン変更がないことを確認することを推奨する。
  • EKS クラスタのマイナーバージョンアップグレード時は、Add-ons もサポートバージョンへ更新する必要がある。aws eks describe-addon-versions --kubernetes-version 1.30 で対応バージョンを事前確認すること。

4-5. AWS CLI / コンソール手順

Terraform 以外の操作方法として、AWS CLI とコンソールからの EKS Cluster 作成手順を示す。

AWS CLI による作成手順:

# EKS Cluster 作成
aws eks create-cluster \
  --name my-eks-cluster \
  --version 1.30 \
  --role-arn arn:aws:iam::123456789012:role/EKSClusterRole \
  --resources-vpc-config \
 subnetIds=subnet-aaaa1111,subnet-bbbb2222,subnet-cccc3333,subnet-dddd4444,endpointPrivateAccess=true,endpointPublicAccess=true \
  --encryption-config '[{"resources":["secrets"],"provider":{"keyArn":"arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx"}}]' \
  --region ap-northeast-1

# クラスタ作成完了まで待機 (ACTIVE になるまで 10-15 分)
aws eks wait cluster-active --name my-eks-cluster --region ap-northeast-1

# クラスタ状態確認
aws eks describe-cluster \
  --name my-eks-cluster \
  --region ap-northeast-1 \
  --query 'cluster.status'
# → "ACTIVE"

# kubeconfig 取得
aws eks update-kubeconfig \
  --name my-eks-cluster \
  --region ap-northeast-1

# Add-on 追加 (クラスタ ACTIVE 確認後)
aws eks create-addon --cluster-name my-eks-cluster --addon-name vpc-cni --region ap-northeast-1
aws eks create-addon --cluster-name my-eks-cluster --addon-name coredns --region ap-northeast-1
aws eks create-addon --cluster-name my-eks-cluster --addon-name kube-proxy --region ap-northeast-1

# ノード確認 (Karpenter セットアップ後)
kubectl get nodes -o wide

コンソール手順:

  1. EKS コンソール (ap-northeast-1) を開き「クラスタを作成」をクリック
  2. クラスタ設定: 名前 / バージョン (1.30) / クラスタサービスロール (EKSClusterRole) を入力
  3. ネットワーク: VPC / Subnet (private ×2 + public ×2 全 4 枚) を選択 / エンドポイントアクセス = プライベートとパブリック
  4. ログ: API Server / Authenticator / Scheduler ログを有効化 (推奨)
  5. 「作成」ボタンをクリック → ステータスが「作成中」→「アクティブ」に変わるまで待機 (約 10-15 分)
  6. 作成完了後「アドオン」タブを開き「アドオンを追加」から vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver を順次追加

5. Karpenter による Node プロビジョニング

fig04: Karpenter NodePool / EC2NodeClass + spot 動作フロー

Karpenter は EC2 API を直接操作するオートスケーラーであり、従来の Cluster Autoscaler (CA) では実現困難だった秒オーダーのノード起動と多様なインスタンスタイプ混在を可能にする。本章では Karpenter v1 (2024 GA) が導入した NodePool / EC2NodeClass という 2 つの CRD の設計思想から、Terraform + Helm による実装、spot + Consolidation を組み合わせた本番設計まで体系的に解説する。まず Cluster Autoscaler との違いを整理し、Karpenter が EKS 本番環境でどう機能するかを明確にしよう。

5-1. Karpenter v1 概要

Cluster Autoscaler との違い

従来の Cluster Autoscaler (CA) は Auto Scaling Group (ASG) を介してノードを追加するアーキテクチャであり、いくつかの制約があった。

比較項目Cluster AutoscalerKarpenter v1
スケーリング方式ASG 経由 (間接制御)EC2 API 直接呼び出し
スケーリング速度60-90 秒 (ASG 遅延あり)30 秒未満
インスタンスタイプNodeGroup 単位で固定Pod 要求に基づき動的選択
spot 対応NodeGroup ごとに設定NodePool requirements で柔軟指定
ノード統合基本的に手動 or 外部ツールConsolidation 機能で自動
CRDなし (ASG 設定のみ)NodePool + EC2NodeClass

CA では「m5.xlarge で構成した NodeGroup」のような固定構成が前提だった。一方 Karpenter は Pod のリソース要求 (CPU / Memory / GPU) と NodePool の requirements を照合して最適なインスタンスタイプを EC2 API から動的に選択する。これにより spot 市場の空き状況に応じてインスタンスファミリーを自動的に切り替えることが可能になり、コスト最適化と可用性の両立が実現する。

Karpenter v1 の主要 CRD

Karpenter v1 (2024 年 GA) では以下の 2 つの CRD が中心となる。

NodePool: スケジューリングポリシーを定義する CRD。Pod のノードセレクタや Taint / Toleration に基づいてどのインスタンスを起動するかを requirements ブロックで宣言する。limits でクラスタ全体の最大 CPU / Memory を制限し、disruption で Consolidation の挙動を制御する。

EC2NodeClass: AWS 固有の設定を定義する CRD。AMI セレクタ (al2023@latest など)、Subnet セレクタ (タグベース)、SecurityGroup セレクタ、IAM Instance Profile、UserData などを管理する。NodePool から nodeClassRef で参照する構造になっており、複数 NodePool から 1 つの EC2NodeClass を共有することも可能だ。

5-2. NodePool / EC2NodeClass 設計

QG-3: Karpenter NodePool / EC2NodeClass 設定チェックリスト

  • NodePool requirements — アーキテクチャ指定: kubernetes.io/arch: ["amd64"] を必ず指定。未指定だと arm64 / amd64 混在でコンテナイメージ不一致が発生する
  • instance-family — 複数ファミリー列挙: karpenter.k8s.aws/instance-family: ["m5", "m6i", "c5", "c6i"] のように 3 ファミリー以上を列挙。spot 中断時の代替候補を確保するため多様性が重要
  • capacity-type — spot + on-demand 両指定: karpenter.sh/capacity-type: ["spot", "on-demand"] の両方を指定。Karpenter はコスト最小化のため spot を優先し、spot 枯渇時に on-demand へ自動フォールバック
  • EC2NodeClass amiSelectorTerms: alias: al2023@latest を使用。固定 AMI ID では EKS バージョンアップ時に手動更新が必要になるため alias 形式を推奨
  • subnetSelectorTerms / securityGroupSelectorTerms: karpenter.sh/discovery: <cluster-name> タグで自動検出。Terraform で VPC / Subnet / SG にこのタグを必ず付与すること
  • Consolidation policy の選定: WhenEmpty (完全空ノードのみ削除) は安全だが効果限定的。WhenUnderutilized (低利用ノードを統合) はコスト削減効果大だが PDB (PodDisruptionBudget) との併用必須
  • spot interruption handling: SQS Queue + EventBridge Rule を設定し、EC2 Spot Interruption 通知を Karpenter が受信できるようにする。terminationGracePeriodSeconds はデフォルト 30 秒 (EC2 中断通知は 2 分前のため余裕あり)

NodePool と EC2NodeClass の完全な YAML 実装を示す。本番環境では 2 つの NodePool (spot 優先 / on-demand 固定) を用途別に分離する構成が堅牢だ。

# nodepool-default.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
 metadata:
labels:
  billing: shared
 spec:
requirements:
  - key: kubernetes.io/arch
 operator: In
 values: ["amd64"]
  - key: karpenter.k8s.aws/instance-family
 operator: In
 values: ["m5", "m6i", "c5", "c6i", "r5", "r6i"]
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["spot", "on-demand"]
  - key: karpenter.k8s.aws/instance-size
 operator: NotIn
 values: ["nano", "micro", "small"]
nodeClassRef:
  apiVersion: karpenter.k8s.aws/v1
  kind: EC2NodeClass
  name: default
terminationGracePeriod: 48h
  limits:
 cpu: 200
 memory: 800Gi
  disruption:
 consolidationPolicy: WhenUnderutilized
 consolidateAfter: 30s
 budgets:
- nodes: "20%"
---
# nodepool-ondemand.yaml — システム Pod 用 on-demand 固定
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: on-demand-system
spec:
  template:
 metadata:
labels:
  billing: system
 spec:
taints:
  - key: dedicated
 value: system
 effect: NoSchedule
requirements:
  - key: kubernetes.io/arch
 operator: In
 values: ["amd64"]
  - key: karpenter.k8s.aws/instance-family
 operator: In
 values: ["m6i", "c6i"]
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["on-demand"]
nodeClassRef:
  apiVersion: karpenter.k8s.aws/v1
  kind: EC2NodeClass
  name: default
  limits:
 cpu: 50
  disruption:
 consolidationPolicy: WhenEmpty
 consolidateAfter: 5m
---
# ec2nodeclass-default.yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiSelectorTerms:
 - alias: al2023@latest
  subnetSelectorTerms:
 - tags:
  karpenter.sh/discovery: my-eks-cluster
  securityGroupSelectorTerms:
 - tags:
  karpenter.sh/discovery: my-eks-cluster
  role: KarpenterNodeRole-my-eks-cluster
  tags:
 Environment: production
 ManagedBy: karpenter

5-3. helm_release による Karpenter インストール

Karpenter は OCI レジストリ (public.ecr.aws/karpenter) で配布されており、Helm による管理が公式推奨だ。Terraform の helm_release リソースで管理することでバージョン固定と IaC への統合が実現する。

# karpenter.tf

locals {
  karpenter_version = "1.0.8"
}

resource "helm_release" "karpenter" {
  namespace  = "karpenter"
  create_namespace = true
  name = "karpenter"
  repository = "oci://public.ecr.aws/karpenter"
  chart= "karpenter"
  version = local.karpenter_version

  set {
 name  = "settings.clusterName"
 value = aws_eks_cluster.main.name
  }
  set {
 name  = "settings.clusterEndpoint"
 value = aws_eks_cluster.main.endpoint
  }
  set {
 name  = "settings.interruptionQueue"
 value = aws_sqs_queue.karpenter_interruption.name
  }
  set {
 name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
 value = aws_iam_role.karpenter_controller.arn
  }
  set {
 name  = "controller.resources.requests.cpu"
 value = "1"
  }
  set {
 name  = "controller.resources.requests.memory"
 value = "1Gi"
  }

  depends_on = [
 aws_eks_cluster.main,
 aws_sqs_queue.karpenter_interruption,
 aws_iam_role.karpenter_controller,
  ]
}

resource "aws_sqs_queue" "karpenter_interruption" {
  name = "karpenter-interruption-${var.cluster_name}"
  message_retention_seconds = 300
  sqs_managed_sse_enabled= true
}

resource "aws_sqs_queue_policy" "karpenter_interruption" {
  queue_url = aws_sqs_queue.karpenter_interruption.url
  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = ["events.amazonaws.com", "sqs.amazonaws.com"] }
Action = "sqs:SendMessage"
Resource  = aws_sqs_queue.karpenter_interruption.arn
 }]
  })
}

IRSA (IAM Roles for Service Accounts) による Karpenter Controller の IAM 権限付与は Vol2 で詳解する。serviceAccount.annotationseks.amazonaws.com/role-arn を設定することで、Karpenter Pod が EC2 / SQS / SSM 等の AWS API を IAM Role の権限で呼び出せるようになる仕組みだ。

5-4. spot + Consolidation の本番設計

spot 採用戦略

Karpenter の capacity-type requirements に ["spot", "on-demand"] を並記すると、Karpenter はコスト最小化のために spot を優先的に選択する。spot が枯渇したインスタンスタイプでは自動的に別のファミリー or on-demand へフォールバックする。

本番環境での推奨構成は以下の 2 NodePool 分離パターンだ。

NodePool対象ワークロードcapacity-type理由
defaultステートレス Web / バッチspot + on-demandコスト削減優先
on-demand-systemKarpenter 自身 / CoreDNS / モニタリングon-demand のみ中断リスク排除

Karpenter 自身を spot ノードで運用すると、spot 中断時に Karpenter が停止してノード補充ができなくなるため、on-demand-system NodePool に Taint を設定し Karpenter Pod に Toleration を付与する設計が必須だ。

Consolidation の設計指針

Consolidation は稼働中のノードを統合して空きノードを削除するコスト最適化機能だ。2 つのポリシーの使い分けを理解することが重要になる。

WhenEmpty: Pod が一切存在しない完全空ノードのみを削除する。ワークロード移動が発生しないため安全だが、低利用率ノード (CPU 10% 程度) は削除されない。開発環境やバースト後の残存ノード処理に適する。

WhenUnderutilized: Pod の合計リソース要求が少ないノードを検出し、他ノードへ移動させた上でノードを削除する。より積極的なコスト削減が可能だが、Pod の再スケジュールが発生するため PDB (PodDisruptionBudget) の設定が必須になる。consolidateAfter: 30s の設定で低利用状態の検出から削除開始まで 30 秒のバッファを確保できる。

spot 中断ハンドリング

EC2 Spot インスタンスは AWS のキャパシティ状況によって 2 分前通知で中断されるリスクがある。Karpenter は SQS Queue + EventBridge Rule を経由してこの中断通知を受信し、対象ノードを Cordon + Drain することで Pod の安全な退避を実現する。

resource "aws_cloudwatch_event_rule" "spot_interruption" {
  name = "karpenter-spot-interruption"
  event_pattern = jsonencode({
 source= ["aws.ec2"]
 detail-type = ["EC2 Spot Instance Interruption Warning"]
  })
}

resource "aws_cloudwatch_event_target" "spot_interruption" {
  rule= aws_cloudwatch_event_rule.spot_interruption.name
  target_id = "karpenter-interruption-queue"
  arn = aws_sqs_queue.karpenter_interruption.arn
}

5-5. 落とし穴

落とし穴 1: NodePool requirements にアーキテクチャ指定なし

kubernetes.io/arch を NodePool requirements に含め忘れると、arm64 (Graviton) と amd64 (x86) のノードが混在して起動する。amd64 専用コンテナイメージを arm64 ノードで実行しようとした場合に exec format error が発生し Pod が CrashLoopBackOff になる。必ず values: ["amd64"] または values: ["arm64"] を明示すること。

落とし穴 2: EC2NodeClass の subnetSelector / securityGroupSelector タグ漏れ

subnetSelectorTermssecurityGroupSelectorTerms にはタグベースのセレクタを指定する。Terraform で VPC / Subnet / SG を作成する際に karpenter.sh/discovery: <cluster-name> タグを付与し忘れると、Karpenter がターゲットの Subnet / SG を見つけられずノード起動が NodeNotFound エラーで失敗する。

tags = {
  "karpenter.sh/discovery" = var.cluster_name
}

落とし穴 3: helm chart バージョン非固定

version を未指定、または latest を使用すると Karpenter のマイナーバージョンが自動更新される。v0.x → v1.x 間は NodePool / EC2NodeClass の CRD スキーマが変更されており、チャートが更新されても CRD が古いままだと spec.disruption.consolidationPolicy などのフィールドが無効になりサイレント障害が発生する。version = local.karpenter_version のように変数で固定し、意図的なアップグレード時にのみ更新すること。

落とし穴 4: Karpenter Controller の IAM 権限不足

Karpenter Controller は ec2:RunInstances / ec2:TerminateInstances / ec2:DescribeInstances / sqs:ReceiveMessage などの権限を必要とする。ec2:RunInstances の Resource に個別リソース (arn:aws:ec2:*::image/* / arn:aws:ec2:*:*:subnet/* など) の許可が漏れていると、ノード起動時に UnauthorizedOperation エラーが発生してスケールアウトが停止する。AWS 公式の Karpenter IAM ポリシードキュメントを参照してポリシーを構成すること。


6. アプリデプロイ + Cluster アクセス手順

fig05: アプリデプロイ + aws eks update-kubeconfig フロー

EKS クラスタと Karpenter の構築が完了したら、いよいよアプリケーションをデプロイする段階だ。本章では aws eks update-kubeconfig による kubeconfig 取得から始まり、Deployment / Service の YAML 適用、Pod 起動確認まで一気通貫で解説する。ECS/Fargate との操作体験の違いも整理しながら、EKS 本番運用の第一歩を踏み出そう。

6-1. aws eks update-kubeconfig

EKS クラスタへアクセスするには、まず aws eks update-kubeconfig~/.kube/config にクラスタ認証情報を追記する。このコマンドは AWS CLI が内部で EKS Token Endpoint を呼び出し、IAM Role ベースの一時トークンを取得する仕組みになっている。

# kubeconfig 取得 (IAM Role を指定して権限を明示する)
aws eks update-kubeconfig \
  --name my-eks-cluster \
  --region ap-northeast-1 \
  --role-arn arn:aws:iam::123456789012:role/EKSAdminRole

# 現在の context を確認
kubectl config current-context
# → arn:aws:eks:ap-northeast-1:123456789012:cluster/my-eks-cluster

# 複数クラスタ管理時は context を明示的に切り替える
kubectl config get-contexts
kubectl config use-context arn:aws:eks:ap-northeast-1:123456789012:cluster/my-eks-cluster

# ノード一覧で疎通確認
kubectl get nodes -o wide

IAM 認証フロー: kubectl コマンドを実行すると、背後では次の流れが走る。

  1. kubectl → kubeconfig の exec セクションで aws eks get-token を呼び出す
  2. AWS STS が署名付きトークンを返却
  3. EKS API Server が aws-iam-authenticator でトークンを検証
  4. aws-auth ConfigMap の mapRoles / mapUsers で Kubernetes RBAC グループに紐付け
  5. RBAC ポリシーに従いリソースへのアクセスが許可される

--role-arn を省略すると現在の IAM User/Role で認証されるが、本番環境では明示的に専用の EKS Admin Role を指定することを推奨する。

6-2. Deployment / Service / Pod 適用

QG-4: デプロイ・クラスタアクセス手順チェックリスト

  • aws eks update-kubeconfig --name {cluster} --role-arn {role} で kubeconfig 取得・IAM Role 明示必須
  • kubectl apply -f deployment.yaml で Deployment 適用・kubectl rollout status で確認
  • kubectl apply -f service.yaml で Service 適用 (本 Vol では LoadBalancer / NLB を使用)
  • kubectl get pods / kubectl logs / kubectl describe pod で Pod 起動・ヘルスチェック確認

EKS 上にサンプルアプリを Deployment + Service で公開する完全な YAML を示す。readinessProbe を設定することで、Pod が準備完了前にトラフィックが流入するのを防ぐのがポイントだ。

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
  labels:
 app: sample-app
spec:
  replicas: 2
  selector:
 matchLabels:
app: sample-app
  template:
 metadata:
labels:
  app: sample-app
 spec:
containers:
  - name: sample-app
 image: nginx:1.27
 ports:
- containerPort: 80
 readinessProbe:
httpGet:
  path: /
  port: 80
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
 livenessProbe:
httpGet:
  path: /
  port: 80
initialDelaySeconds: 15
periodSeconds: 20
 resources:
requests:
  cpu: "100m"
  memory: "128Mi"
limits:
  cpu: "500m"
  memory: "256Mi"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-app-svc
  namespace: default
  annotations:
 service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
 app: sample-app
  ports:
 - name: http
port: 80
targetPort: 80
protocol: TCP

デプロイ手順と動作確認コマンドを示す。

# Deployment と Service を適用
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# Pod 起動を監視 (Running になるまで待機)
kubectl get pods -w

# Deployment のロールアウト状態確認
kubectl rollout status deployment/sample-app

# Service の EXTERNAL-IP (NLB DNS名) を確認
kubectl get svc sample-app-svc
# → EXTERNAL-IP 列に NLB の DNS 名が表示される (数分かかる場合あり)

# Pod の詳細確認 (スケジューリング状況・イベント)
kubectl describe pod <pod-name>

# Pod ログのリアルタイム確認
kubectl logs -f <pod-name>

# 疎通確認 (NLB DNS が払い出された後)
curl http://<EXTERNAL-IP>/

6-3. ALB Ingress 予告 (Vol3)

本 Vol1 では Service type=LoadBalancer を使って AWS NLB (Network Load Balancer) 経由でアプリを公開した。NLB は L4 (TCP) レベルの転送であり、シンプルなユースケースには十分だ。

一方、パスベースルーティング・HTTPS 終端・ホストベースルーティングなど L7 機能が必要な場合は AWS Load Balancer Controller を導入し、Ingress リソースを使って ALB (Application Load Balancer) を管理するのが本番のベストプラクティスとなる。

# Vol3 で扱う ALB Ingress の例 (参考)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
 kubernetes.io/ingress.class: alb
 alb.ingress.kubernetes.io/scheme: internet-facing
 alb.ingress.kubernetes.io/target-type: ip
spec:
  rules:
 - host: app.example.com
http:
  paths:
 - path: /
pathType: Prefix
backend:
  service:
 name: sample-app-svc
 port:
number: 80

ALB Ingress Controller のインストール (Helm)、IRSA による IAM 権限連携、複数サービスのルーティング設計については Vol3 で詳しく解説予定だ。本 Vol1 段階では「NLB で公開 → Vol3 で ALB に移行する」という段階的構成を意識してほしい。

6-4. ECS / ecspresso デプロイとの対比

ECS/Fargate でのデプロイ経験があるエンジニアにとって、EKS の操作モデルは「宣言的 YAML 管理」という点では共通しているが、コマンド体系が大きく異なる。以下の対比表を参考に頭を切り替えてほしい。

操作ECS / ecspressoEKS / kubectl
デプロイecspresso deploy / aws ecs update-servicekubectl apply -f manifest.yaml
ロールバックecspresso rollbackkubectl rollout undo deployment/<name>
スケールアウトaws ecs update-service --desired-count Nkubectl scale deployment/<name> --replicas=N
ログ確認aws logs filter-log-events / CloudWatch Logs Insightskubectl logs <pod-name> / CloudWatch Container Insights
サービス再起動aws ecs update-service --force-new-deploymentkubectl rollout restart deployment/<name>
Exec 接続aws ecs execute-commandkubectl exec -it <pod-name> -- /bin/sh
デプロイ状態確認aws ecs describe-serviceskubectl rollout status deployment/<name>

ECS Fargate CICD の構築手順については以下の記事も参照してほしい。

ECS では「タスク定義バージョン + サービス更新」という概念でデプロイを管理するが、EKS では Kubernetes の ReplicaSet がローリングアップデートを自動制御する。どちらも Blue/Green デプロイは別途ツール (CodeDeploy / Argo Rollouts) が必要になる点は共通だ。

6-5. 落とし穴

落とし穴①: kubectl get nodes が Unauthorized になる

aws eks update-kubeconfig 後に kubectl get nodes を実行すると error: You must be logged in to the server (Unauthorized) が返る場合、IAM Role が aws-auth ConfigMap の mapRoles に未登録のことが多い。

# aws-auth ConfigMap の内容確認
kubectl -n kube-system describe configmap aws-auth

# 修正例: mapRoles に EKS Admin Role を追加
kubectl -n kube-system edit configmap aws-auth
# → mapRoles: に以下を追記
#- rolearn: arn:aws:iam::123456789012:role/EKSAdminRole
#  username: admin
#  groups: ["system:masters"]

落とし穴②: Service の EXTERNAL-IP が <pending> のまま解消しない

kubectl get svc で EXTERNAL-IP が <pending> のまま数分経過する場合、VPC サブネットへの kubernetes.io/role/elb: "1" タグ漏れが最多原因だ。パブリックサブネットへのタグ設定を Terraform で確認しよう。

# サービスのイベントを確認
kubectl describe svc sample-app-svc
# → Events: に "Error creating load balancer" が出る場合はタグ設定を見直す

落とし穴③: Pod が Pending のまま起動しない

Karpenter が新規 Node を起動してくれない場合、NodePoolrequirements と Pod の nodeSelector / tolerations が一致していない可能性がある。

kubectl describe pod <pod-name>
# → Events: に "0/0 nodes are available: 0 Insufficient cpu" などが表示される
kubectl get nodepool -o yaml  # NodePool の requirements を確認

落とし穴④: readinessProbe 未設定で 502 エラーが断続発生

readinessProbe を設定しないと、Pod がコンテナ起動直後 (アプリ初期化中) でも Service に組み込まれトラフィックが流れる。必ず initialDelaySeconds を設定し、アプリの実際の起動時間に合わせて調整すること。


7. 監視 (Container Insights + Fluent Bit)

fig06: Container Insights + Fluent Bit 監視スタック構成

EKS 本番環境では Pod の stdout ログ収集・コンテナレベルのメトリクス監視・分散トレーシングの 3 層を整備することが運用品質の最低ラインとなる。本章では AWS が公式サポートする CloudWatch Container Insights と Fluent Bit DaemonSet を中心に、Terraform 完全実装で標準監視スタックを構築する。IRSA 経由の権限設計は Vol2 で深掘りするが、本章でその入口となる OIDC Provider 連携を予告として示す。

7-1. CloudWatch Container Insights

CloudWatch Container Insights は amazon-cloudwatch-observability EKS Add-on として提供されており、EKS クラスタの CPU・Memory・Network・Pod 数などをコンテナ単位で収集できる。2024 年以降は enhanced モードで Prometheus 互換メトリクスもサポートし、Karpenter ノードのリソース利用率可視化にも対応している。

Terraform による Add-on 有効化

resource "aws_eks_addon" "cloudwatch_observability" {
  cluster_name = aws_eks_cluster.main.name
  addon_name= "amazon-cloudwatch-observability"
  resolve_conflicts_on_update = "OVERWRITE"

  # IRSA 連携 (Vol2 で深掘り): addon が使用する ServiceAccount に IAM Role を付与
  service_account_role_arn = aws_iam_role.cloudwatch_agent.arn

  depends_on = [
 aws_eks_cluster.main,
 aws_eks_node_group.system,
  ]
}

# CloudWatch エージェント用 IAM ロール (IRSA 用)
resource "aws_iam_role" "cloudwatch_agent" {
  name = "${var.cluster_name}-cloudwatch-agent"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Federated = aws_iam_openid_connect_provider.eks.arn }
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
  StringEquals = {
 "${replace(aws_eks_cluster.main.identity[0].oidc[0].issuer, "https://", "")}:sub" =
"system:serviceaccount:amazon-cloudwatch:cloudwatch-agent"
  }
}
 }]
  })
}

resource "aws_iam_role_policy_attachment" "cloudwatch_agent" {
  role = aws_iam_role.cloudwatch_agent.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}

AWS CLI / コンソール手順

# Add-on 利用可能バージョン確認
aws eks describe-addon-versions \
  --addon-name amazon-cloudwatch-observability \
  --kubernetes-version 1.30 \
  --query 'addons[].addonVersions[].addonVersion' --output text

# Add-on インストール
aws eks create-addon \
  --cluster-name my-cluster \
  --addon-name amazon-cloudwatch-observability \
  --service-account-role-arn arn:aws:iam::ACCOUNT_ID:role/cloudwatch-agent-role

# 動作確認
kubectl get pods -n amazon-cloudwatch

コンソール手順: EKS クラスタ → 「アドオン」タブ → 「アドオンを取得」→ amazon-cloudwatch-observability 選択 → IAM ロール ARN を入力して有効化。

7-2. Fluent Bit DaemonSet

QG-5: 監視設定 (Container Insights + Fluent Bit) チェックリスト

  • amazon-cloudwatch-observability Add-on 有効化: Terraform aws_eks_addon + IAM ロール ARN 設定
  • Fluent Bit DaemonSet: Pod stdout → CloudWatch Logs グループ (/aws/containerinsights/{cluster}/application) への転送確認
  • IAM 権限 (IRSA): logs:CreateLogStream / logs:PutLogEvents / logs:DescribeLogGroups を ServiceAccount に付与 (Vol2 IRSA で深掘り)
  • Container Insights メトリクス確認: CloudWatch コンソール → Container Insights → クラスタ名 でノード・Pod 単位メトリクス表示

amazon-cloudwatch-observability Add-on を有効化すると Fluent Bit DaemonSet が自動的に各ノードにデプロイされ、Pod の stdout / stderr を CloudWatch Logs に転送する。ただし IAM 権限が適切に設定されていないと転送が静かに失敗するため注意が必要だ。

Fluent Bit 設定の確認

# Fluent Bit DaemonSet の動作確認
kubectl get daemonset -n amazon-cloudwatch
kubectl logs -n amazon-cloudwatch -l app.kubernetes.io/name=fluent-bit --tail=20

# CloudWatch Logs グループ確認
aws logs describe-log-groups \
  --log-group-name-prefix /aws/containerinsights/my-cluster \
  --query 'logGroups[].logGroupName'

CloudWatch Logs には以下のグループが自動作成される:

ロググループ内容
/aws/containerinsights/{cluster}/applicationアプリコンテナの stdout / stderr
/aws/containerinsights/{cluster}/hostノード OS ログ
/aws/containerinsights/{cluster}/dataplanekube-apiserver / etcd 等のコントロールプレーンログ
/aws/containerinsights/{cluster}/performanceCPU / Memory / ネットワーク等のパフォーマンスメトリクス

7-3. ダッシュボードとアラート

Container Insights ダッシュボードでは Namespace・Node・Pod・Service 単位のリソース使用率をグラフで確認できる。

# コンソール: CloudWatch → Container Insights → Performance Monitoring
# → クラスタ名選択 → ノード・Pod 単位のメトリクス確認

# CLI: 特定 Pod の CPU メトリクスを取得
aws cloudwatch get-metric-statistics \
  --namespace ContainerInsights \
  --metric-name pod_cpu_utilization \
  --dimensions Name=ClusterName,Value=my-cluster Name=Namespace,Value=default \
  --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 300 \
  --statistics Average

CloudWatch アラートの設定

Pod 数が急減したときや CPU 利用率が閾値を超えたときにアラートを発火させる:

resource "aws_cloudwatch_metric_alarm" "pod_cpu_high" {
  alarm_name = "${var.cluster_name}-pod-cpu-high"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name= "pod_cpu_utilization"
  namespace  = "ContainerInsights"
  period  = 300
  statistic  = "Average"
  threshold  = 80
  alarm_description= "EKS Pod CPU utilization exceeds 80%"

  dimensions = {
 ClusterName = var.cluster_name
  }

  alarm_actions = [aws_sns_topic.alerts.arn]
}

7-4. X-Ray ADOT 連携 (補足)

分散トレーシングが必要な場合は AWS Distro for OpenTelemetry (ADOT) Collector を EKS 上の DaemonSet または Deployment として展開することで、アプリケーションの X-Ray トレースを収集できる。

ADOT の基本構成:

  1. ADOT Operatoraws-otel-eks-addon (EKS Add-on) として有効化
  2. OpenTelemetryCollector カスタムリソースで収集設定を定義
  3. アプリ側に AWS X-Ray SDK または OpenTelemetry SDK を組み込み
# ADOT Add-on の確認
aws eks describe-addon-versions \
  --addon-name adot \
  --kubernetes-version 1.30

# ADOT Collector の稼働確認
kubectl get pods -n opentelemetry-operator-system

本格的なマイクロサービス観測が必要な場合は ADOT + X-Ray の組み合わせを検討する。Lambda 応用シリーズ Vol3 で解説した Powertools Tracer との統合も可能だ。

7-5. IRSA 連携の入口 (Vol2 予告)

本章では CloudWatch エージェントと Fluent Bit の IAM 権限を aws_iam_role で設定したが、このアプローチはノード IAM ロールに権限を付与する方式 (EC2 Instance Profile 方式) と混在しがちで、最小権限の原則に反するリスクがある。

IRSA (IAM Roles for Service Accounts) は OIDC Provider を通じて Kubernetes ServiceAccount 単位で IAM ロールを引き受ける仕組みであり、Pod ごとに必要最小限の権限を付与できる。

本記事では aws_iam_openid_connect_provider (§4 で構築) を使って CloudWatch エージェント用の IRSA ロールを設定したが、その詳細設計—OIDC issuer URL の取得・ServiceAccount アノテーション・Trust Policy の条件式—は Vol2「IRSA 完全活用編」で完全解説する

# §4 で構築した OIDC Provider の確認
aws iam list-open-id-connect-providers \
  --query 'OpenIDConnectProviderList[].Arn'

# ServiceAccount への IAM ロール付与 (IRSA) の確認
kubectl get serviceaccount cloudwatch-agent -n amazon-cloudwatch -o yaml | grep eks.amazonaws.com

7-6. 監視スタック全体の落とし穴

監視設定では IAM 権限の設定ミスが最多障害原因となる。よくある障害パターンを示す。

症状原因対処法
CloudWatch に Pod メトリクスが出ないamazon-cloudwatch-observability Add-on 未有効aws eks create-addon でインストール
Fluent Bit が Crash ループIAM 権限不足 (logs:PutLogEvents 未付与)IRSA ロールポリシーを確認
ログが届くが古いものだけCloudWatch Logs のリテンション設定aws logs put-retention-policy で期間設定
メトリクスの粒度が粗いContainer Insights の enhanced モード無効Add-on 設定で enableContainerInsights: enhanced
X-Ray トレースが届かないADOT Add-on 未設定 / IAM 権限漏れaws eks create-addon --addon-name adot

費用の目安

Container Insights と CloudWatch Logs の費用はクラスタ規模に依存する。

  • Container Insights: ノード数 × Pod 数 × メトリクス数 × $0.30/メトリクス/月 (概算)
  • CloudWatch Logs: ログデータ取り込み $0.76/GB + 保存 $0.033/GB/月
  • コスト抑制策: CloudWatch Logs のリテンションを 7-30 日に設定 / 不要なロググループを削除 / Application Signals への移行 (費用効率向上)

本番環境では月初に CloudWatch の費用を確認し、異常な急増がないかモニタリングすることを推奨する。


8. まとめ + Vol2 IRSA 予告 + 落とし穴 10 選

8-1. 本記事のチートシート

本記事で構築した EKS + Karpenter 本番運用基盤を一覧にまとめる。

フェーズ実施内容対応章主要リソース
ECS との選定判断5 軸比較・移行判断フロー§3比較表・判断ツリー
Cluster 構築VPC / Subnet / IAM / OIDC / aws_eks_cluster§4aws_eks_cluster, aws_iam_role
Add-ons 管理vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver§4aws_eks_addon
Karpenter 導入NodePool / EC2NodeClass / helm_release§5NodePool, EC2NodeClass
spot 設計Consolidation + interruption handling§5disruption, SQS
アプリデプロイaws eks update-kubeconfig + kubectl apply§6Deployment, Service
監視スタックContainer Insights + Fluent Bit§7amazon-cloudwatch-observability
IRSA 設計OIDC Provider + ServiceAccount 権限§7 / Vol2aws_iam_openid_connect_provider

最小 Terraform 構成チェックリスト:

# 作成すべきリソース一覧確認
terraform plan | grep "will be created" | grep -E "aws_vpc|aws_subnet|aws_eks|aws_iam|aws_eks_addon|helm_release|aws_cloudwatch"

必須リソース: aws_vpc / aws_subnet×4 / aws_internet_gateway / aws_nat_gateway / aws_eks_cluster / aws_eks_node_group (system) / aws_iam_role×2 / aws_iam_openid_connect_provider / aws_eks_addon×5 / helm_release (karpenter) / aws_eks_addon (cloudwatch-observability)

8-2. 本番投入の落とし穴 10 選

EKS + Karpenter 本番投入 — 落とし穴 10 選

  1. VPC Subnet の kubernetes.io タグ漏れ: kubernetes.io/cluster/{name}=shared および kubernetes.io/role/elb=1 タグが private / public subnet にないと Karpenter がノードを起動できず、Service type=LoadBalancer も EXTERNAL-IP が pending のまま。Terraform tags ブロックに必ず明記する
  2. aws-auth ConfigMap の IAM ロール未追加: aws eks update-kubeconfig 後に kubectl get nodes で Unauthorized エラー。kubectl edit configmap aws-auth -n kube-system で作業用 IAM ロールを mapRoles に追加する
  3. EKS Add-ons のバージョン非互換: クラスタバージョンアップグレード時に Add-ons が古いままだと kube-proxy が起動しない。aws eks describe-addon-versions で事前に互換バージョンを確認し、Terraform の addon_version を更新する
  4. Karpenter NodePool requirements の設定ミス: kubernetes.io/archamd64 のみ指定しているのに arm64 イメージを使う Pod をデプロイするとスケジュール失敗。requirements と Pod の nodeSelector / toleration を一致させる
  5. spot interruption handling の漏れ: spot インスタンスが中断される 2 分前に EC2 が通知を送るが、Karpenter の SQS Queue (interruption handling) が未設定だと Pod が強制終了される。helm_releasesettings.interruptionQueue に SQS Queue 名を設定する
  6. helm_release version の未固定: version を省略すると helm upgrade 時に最新チャートが自動適用されKarpenter API が変わる可能性がある。本番環境では必ず version = "1.0.0" のようにピン留めする
  7. IRSA OIDC Provider 未設定で Add-on 権限取得失敗: amazon-cloudwatch-observability 等の Add-on に IAM ロールを付与するには §4 で構築した aws_iam_openid_connect_provider が必須。未設定だと CloudWatch Logs へのアクセスが拒否される。詳細は Vol2「IRSA 完全活用編」で解説
  8. Service type=LoadBalancer の NLB / ALB 選定ミス: type: LoadBalancer はデフォルトで Classic LB (廃止予定) または NLB を作成する。HTTP パスルーティングや WAF 統合が必要な場合は ALB Ingress Controller (Vol3 で解説) を使う。アノテーションで service.beta.kubernetes.io/aws-load-balancer-type: nlb を付けることで NLB を明示指定できる
  9. Container Insights Add-on 未有効でメトリクス取得不能: CloudWatch コンソールで Container Insights ダッシュボードが表示されない場合、amazon-cloudwatch-observability Add-on が未インストールの可能性がある。aws eks list-addons --cluster-name {name} で確認する
  10. Fluent Bit の IAM 権限漏れで CloudWatch Logs 転送失敗: Fluent Bit DaemonSet が稼働しているのにログが CloudWatch に届かない場合、IRSA ロールに logs:CreateLogStream / logs:PutLogEvents が付与されていない可能性がある。kubectl logs -n amazon-cloudwatch -l app.kubernetes.io/name=fluent-bit でエラーを確認する

8-3. Vol2 予告: IRSA 完全活用編

本記事では §4 で aws_iam_openid_connect_provider を構築し、§7 で CloudWatch エージェント用の IRSA ロールを設定した。しかしこれは IRSA の入口にすぎない。

IRSA の真価は「Pod 単位で異なる IAM ロールを付与できる」点にある。例えば S3 に書き込む Pod と DynamoDB を読む Pod に別々の最小権限ロールを付与することで、ノード IAM ロールに過剰な権限を持たせる必要がなくなる。これは ECS Task Role に相当する概念だが、Kubernetes の ServiceAccount を介した OIDC トークン認証という仕組みで実現する。

Vol2「IRSA 完全活用編」では以下を完全実装する:

  • OIDC issuer URL の取得と Trust Policy の条件式設計
  • ServiceAccount へのアノテーション付与 (eks.amazonaws.com/role-arn)
  • 複数 Pod への異なる IAM ロール付与パターン
  • ECS Task Role との比較で理解する IRSA の設計思想
  • Karpenter・Fluent Bit・aws-ebs-csi-driver の IRSA 設定完全実装

8-4. 本記事で構築した環境の確認コマンド集

本記事の各フェーズで使用した確認コマンドをまとめる。本番投入前の最終チェックに活用してほしい。

# ===== EKS Cluster 確認 =====
# クラスタ一覧
aws eks list-clusters --region ap-northeast-1

# クラスタ詳細 (status=ACTIVE であること)
aws eks describe-cluster --name my-cluster \
  --query 'cluster.{status:status,version:version,endpoint:endpoint}' --output table

# ノード一覧 (Karpenter ノード含む)
kubectl get nodes -o wide

# ===== Karpenter 確認 =====
# NodePool / EC2NodeClass の状態
kubectl get nodepool
kubectl get ec2nodeclass

# Karpenter コントローラのログ
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=30

# ===== Add-ons 確認 =====
# 全 Add-ons の状態
aws eks list-addons --cluster-name my-cluster \
  --query 'addons' --output table

# ===== アプリデプロイ確認 =====
# Deployment / Service の状態
kubectl get deploy,svc -n default
kubectl get pods -n default -o wide

# ===== 監視確認 =====
# CloudWatch エージェント / Fluent Bit の稼働確認
kubectl get pods -n amazon-cloudwatch
kubectl get daemonset -n amazon-cloudwatch

8-5. まとめ: EKS + Karpenter 本番運用基盤の全体像

本記事では ECS / ecspresso 経験者が EKS + Karpenter の本番運用基盤を構築するための一気通貫ガイドを提供した。以下に本記事の要点を振り返る。

§3 では ECS / Fargate / ecspresso との5軸比較から「移るべきか・併用すべきか」の判断軸を示した。ECS が優位なケース (小規模・学習リソース不足・Fargate で十分) と EKS が優位なケース (Kubernetes エコシステム必要・大規模 spot 活用・マルチクラウド) を明確に分けることが選定の第一歩だ。

§4 では Terraform による EKS Cluster の完全実装を示した。VPC の public / private Subnet の kubernetes.io タグ設計・IAM ロールの cluster / node 分離・OIDC Provider の構築が成功の鍵であり、Add-ons 4 本 (vpc-cni / coredns / kube-proxy / aws-ebs-csi-driver) の Terraform 管理方式を確立した。

§5 では Karpenter v1 の NodePool / EC2NodeClassによる本番ノードプロビジョニングを実装した。Cluster Autoscaler (ASG 依存・60-90 秒) に対して Karpenter は EC2 API 直接呼び出しで 30 秒未満のスケーリングを実現する。spot + Consolidation の組み合わせがコスト最適化の核心だ。

§6・§7 でデプロイ・監視スタックを完成させ、EKS 本番運用の最小構成を網羅した。Vol2 IRSA・Vol3 ALB Ingress + Argo CD GitOps と連続して学ぶことで、フル機能の EKS 本番環境が完成する。

【シリーズ】EKS 本番運用シリーズ — 次巻予告

  • Vol2 (次回): IRSA (IAM Roles for Service Accounts) 完全活用編 — ServiceAccount 単位の最小権限設計と Pod 認証の本番運用 (準備中)
  • Vol3: ALB Ingress Controller + Argo CD GitOps 編 — ALB Ingress の本番設計と GitOps デプロイパイプライン (準備中)

Vol2 IRSA 完全活用編 (準備中)

8-4. 関連記事 (ECS / ecspresso クロスリンク)