EKS本番運用 Vol1: クラスタ設計 × IRSA × ALB Ingress 〜IAM入門4巻読了者向け統合入門〜


title: “EKS本番運用 Vol1: クラスタ設計 × IRSA × ALB Ingress 〜IAM入門4巻読了者向け統合入門〜”
wp_title: “EKS本番運用 Vol1: クラスタ設計 × IRSA × ALB Ingress 〜IAM入門4巻読了者向け統合入門〜”
wp_seo_title: “EKS本番運用 Vol1 クラスタ設計 IRSA ALB Ingress IAM入門4巻読了者向け統合入門”
wp_meta_description: “EKSクラスタ設計・Managed Node Group vs Karpenter 選定・IRSA信頼ポリシー (sts:AssumeRoleWithWebIdentity)・ALB Ingress Controller (Helm) を1本で統合解説。IAM入門4巻読了者向けの統合入門版。詰まりポイント7選+演習5問付き。”
wp_slug: “eks-production-cluster-design-irsa-alb-ingress”
wp_status: “draft”
wp_id: 2678
wp_series: “EKS本番運用”
wp_series_no: 1
wp_tags: [“EKS”, “Kubernetes”, “IRSA”, “ALB Ingress”, “Karpenter”, “AWS入門”]wp_category: “AWS / コンテナ”
prev_article: “https://www.watchittrend.com/iam-sts-cross-account/”


目次

1. なぜEKSが詰まるか — IAM入門4巻からの架橋と新シリーズ起点告知

1-1. EKSで詰まる3大シーン

EKS (Elastic Kubernetes Service) の学習でよく聞く声は「Kubernetes の概念は理解できたが、AWSに乗せようとしたら詰まった」というものだ。VPC や IAM の基礎知識があっても、EKS ではそれらを組み合わせる箇所で手が止まる。詰まりやすい場面は大きく3つある。

シーン1: kubeconfig 設定 — aws eks update-kubeconfig の認証エラー

aws eks update-kubeconfig --name my-cluster --region ap-northeast-1

このコマンドが成功しても kubectl get nodesUnauthorized エラーが返ることがある。原因は EKS クラスタの RBAC 設定にある。EKS クラスタは作成者のIAMエンティティのみが初期状態でシステム管理者権限を持つ。別のIAMユーザーやロールからアクセスするには aws-auth ConfigMap への登録が必要だ。

# よくあるエラー
error: You must be logged in to the server (Unauthorized)

# 確認コマンド — aws-auth ConfigMap の現在の内容を確認する
kubectl describe configmap aws-auth -n kube-system

Terraform で EKS クラスタを作成した場合は、Terraform 実行ロールとアプリケーション開発者のロールが別になっていることが多く、開発者が aws-auth に登録されていないケースがよく発生する。

シーン2: IRSA 設定 — OIDCプロバイダーと信頼ポリシーの連携失敗

Pod から S3 や DynamoDB にアクセスさせるとき、初学者はノード (EC2) のインスタンスプロファイルに権限を付与しようとしがちだ。しかし本番環境のベストプラクティスは IRSA (IAM Roles for Service Accounts) — Pod 単位で IAM ロールを紐付ける仕組みだ。

IRSA で詰まる箇所は信頼ポリシーの記述だ。以下の3点が1つでも欠けると AccessDeniedException が発生する:

{
  "Version": "2012-10-17",
  "Statement": [{
 "Effect": "Allow",
 "Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLID1234"
 },
 "Action": "sts:AssumeRoleWithWebIdentity",
 "Condition": {
"StringEquals": {
  "oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLID1234:sub":
 "system:serviceaccount:default:my-service-account"
}
 }
  }]
}

詰まりポイント: (1) OIDCプロバイダーのURL誤記、(2) Condition の :sub に指定するNamespace / ServiceAccount名のタイポ、(3) OIDCプロバイダー自体がAWSアカウントに未登録。この構造は §4 で詳しく解説する。

シーン3: ALB Ingress Controller — annotation 不足と ALB 未作成

Ingress リソースを作成しても ALB が作成されないケースがある。多くの場合、必須 annotation が不足している:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
 kubernetes.io/ingress.class: alb
 alb.ingress.kubernetes.io/scheme: internet-facing
 alb.ingress.kubernetes.io/target-type: ip
 alb.ingress.kubernetes.io/subnets: subnet-aaa,subnet-bbb
spec:
  rules:
 - http:
  paths:
 - path: /
pathType: Prefix
backend:
  service:
 name: my-service
 port:
number: 80

さらに、ALB Ingress Controller 自体が IRSA で動作するため、シーン2 の IRSA 設定ミスが ALB 作成失敗として表れることもある。IRSAとALBの問題が絡み合うため、エラーの原因特定が難しい。


1-2. IAM入門4巻からの架橋

IAM入門シリーズ全4巻を読んできた読者には、EKS の仕組みが「既知の構造の組み合わせ」として見えてくる。特に以下の対応関係を意識すると、EKS の学習コストが大幅に下がる。

AssumeRole → sts:AssumeRoleWithWebIdentity

IAM入門 Vol4 で学んだ sts:AssumeRole と信頼ポリシーの構造は、IRSA でも本質的に同じだ。違いは「誰がロールを引き受けるか」という点だけだ:

場面呼び出し元Action
クロスアカウント (Vol4)別アカウントのIAMロールsts:AssumeRole
IRSA (EKS)KubernetesのPod (OIDC JWT経由)sts:AssumeRoleWithWebIdentity

IRSA は「OIDC プロバイダーを通じて、Kubernetes のサービスアカウントが JWT トークンを STS に提示し、IAMロールを引き受ける」仕組みだ。Vol4 で学んだ Condition による絞り込みと同じ考え方で、:sub (Subject) Condition を使って「どの Namespace のどの ServiceAccount だけが AssumeRole できるか」を制限する。

Principal の構造

Vol4 の信頼ポリシーでは Principal.AWS に他アカウントの IAM エンティティを指定した。IRSA では Principal.Federated 形式で OIDC プロバイダーの ARN を指定する。どちらも「誰が信頼されるか」を定義する構造は同一だ:

// Vol4: クロスアカウント AssumeRole
"Principal": { "AWS": "arn:aws:iam::999999999999:role/ExternalRole" }

// IRSA: OIDC プロバイダー経由
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/XXXX" }

最小権限をPodレベルで実現

IAM入門 Vol1 で学んだ最小権限原則は、EKS では「Node レベル」ではなく「Pod レベル」で実現される。IRSA によって各 Pod に必要な権限のみを付与できる。ノード全体に S3 フルアクセスを与えるのではなく、S3 に書き込む Pod のみに s3:PutObject を付与する構成が本番運用のベストプラクティスだ。

Confused Deputy → :sub Condition の役割

Vol4 で学んだ Confused Deputy 問題 (第三者が信頼されたエンティティを経由して不正アクセスする攻撃) のEKS版が、IRSA の Condition 不足だ。StringEquals:sub Condition を省略すると、同じ OIDC プロバイダーを使うすべての ServiceAccount がロールを引き受けられてしまう。Vol4 の ExternalId に相当する役割を :sub Condition が担っている。


1-3. 本記事のロードマップ

本記事は IAM 入門4巻読了者を対象に、EKS 本番運用の全体像を1本で把握することを目標としている。各セクションの概要と、深掘り記事との対応関係は以下のとおりだ:

セクション内容深掘り記事
§2: クラスタ設計VPC/Subnet 設計・Control Plane vs Data Plane・マルチAZ構成
§3: ノード戦略Managed Node Group vs Karpenter の選定基準・Spot 活用EKS Cluster + Karpenter 本番運用
§4: IRSA信頼ポリシー構造・OIDC Provider 登録・Helm でのロール指定EKS IRSA 完全活用
§5: ALB IngressALB Ingress Controller 導入 (Helm)・Ingress リソース設定EKS ALB Ingress + Argo CD GitOps
§6: 詰まりポイント7選よくあるエラーと根本原因・解決コマンド一覧
§7: 演習5問ハンズオン形式の演習 (クラスタ構築〜Ingress 公開)
§8: まとめシリーズ案内・次のステップ

本記事を読むことで「EKS本番運用の全体像と各要素の関係」を把握できる。§3〜§5の各テーマをさらに深掘りしたい場合は、後述するナビゲーションの深掘り記事3本を参照してほしい。


1-4. 前提条件

本記事を読むにあたって必要な知識は以下のとおりだ。Kubernetes / EKS の事前経験は不要で、IAM入門4巻読了者であれば §4 IRSA の信頼ポリシーも自然に理解できる。

必須 (これがあれば本記事を読み進められる):
– IAM ロールとポリシーの基本操作 (作成・編集・アタッチ)
– VPC / Subnet / Security Group の概念と基本設定
– AWS CLI のセットアップ (aws configure 完了済み)

あると理解が深まる:
– IAM入門 Vol4 (STS × Cross-Account 実践) — 信頼ポリシーと sts:AssumeRole の仕組みを理解していると §4 IRSA がスムーズに入る
– Docker コンテナの基礎知識 (Dockerfile / コンテナイメージのビルド)

なくても大丈夫:
– kubectl の使用経験 — 本記事内でコマンドを逐一説明する
– Kubernetes のアーキテクチャ詳細 — §2 で必要な部分を解説する
– Helm の使用経験 — §4・§5 でゼロから手順を示す

ハンズオン環境 (§7 演習で使用するツール):

# バージョン確認コマンド
aws --version # AWS CLI v2 推奨 (v2.13 以上)
kubectl version --client  # 1.28 以上
helm version  # v3.12 以上
eksctl version# 0.170 以上

eksctl は EKS クラスタのプロビジョニングを CLI で行うツールだ。Terraform による構築も解説するが、まず eksctl で全体像を掴んでから Terraform に移行するアプローチを推奨する。


1-5. IAM入門4巻 × EKS本番運用 ナビゲーション

IAM入門4巻 × EKS本番運用 学習ルートガイド

IAM入門シリーズ全4巻と、本記事の深掘りシリーズです。

本記事で全体像を把握後、各テーマを深掘りする場合:


2. EKSクラスタ設計の全体像 — VPC/Subnet設計 × Control Plane vs Data Plane × 本番運用マルチAZ構成

EKS のクラスタ設計は、AWS が管理する Control Plane とユーザーが管理する Data Plane、そしてネットワーキング層の3層で構成されます。この3層の責任境界を正確に把握することが、本番 EKS 運用の第一歩です。

2-1. EKSクラスタ設計の3層アーキテクチャ

Control Plane (AWS 完全管理)

EKS の Control Plane は AWS が管理するマネージドサービスです。ユーザーは Kubernetes API を通じてクラスタを操作しますが、Control Plane のインフラ自体は意識不要です。

コンポーネント役割管理者
API Serverkubectl / REST API のエンドポイント。全 Kubernetes 操作の入口AWS
etcdクラスタの状態 (Pod / Node / Deployment 定義) を永続化する分散 KVSAWS
Scheduler新しい Pod をどの Node に配置するか決定AWS
Controller ManagerNode / ReplicaSet / Deployment の状態を継続的に reconcileAWS

Control Plane は AWS の責任範囲 (SLA 99.95%) です。ユーザーが担うのはバージョン更新の判断・実行のみです。

Data Plane (ユーザー管理)

Data Plane は実際に Pod が動く Worker Node の集合です。EKS では以下の3種のプロビジョニング方式があります。

方式管理負荷特徴適用シーン
Managed Node GroupAWS が Node の AMI 更新・スケールを管理本番安定運用 (推奨)
Self-managed Node GroupEC2 を完全に自己管理カスタム AMI が必要な場合
KarpenterPod スペックに応じて最適な EC2 を動的プロビジョニングコスト最適化・バースト対応

ノード戦略の詳細な選定基準は §3 で解説します。

Networking (VPC CNI Plugin)

EKS の Pod は Amazon VPC CNI Plugin (aws-node DaemonSet) によって VPC 内の IP アドレスを直接割り当てられます。

  • Pod IP = VPC Subnet IP: Pod が VPC 内の他リソース (RDS / ElastiCache 等) と直接通信可能
  • kube-proxy DaemonSet: Service リソースの iptables / IPVS ルールを各 Node で管理
  • CoreDNS: クラスタ内 DNS 解決 (Service 名 → ClusterIP の名前解決)

2-2. VPC/Subnet 設計 (本番運用推奨)

Public Subnet vs Private Subnet の役割分担

本番 EKS クラスタでは Subnet を用途で明確に分離します。

Subnet 種別配置リソースインターネット接続CIDR 例
Public SubnetALB / NAT Gateway直接接続 (Internet Gateway)10.0.0.0/24 〜 10.0.2.0/24
Private SubnetWorker Node / PodNAT Gateway 経由のみ10.0.128.0/18 〜 (Pod 数に応じた大きめ CIDR)

Worker Node は必ず Private Subnet に配置します。 ALB のみ Public Subnet に配置し、ALB → Worker Node (Private) へトラフィックをルーティングする構成が標準です。

マルチAZ構成 (最低3AZ必須)

本番運用では 最低3AZ に Worker Node を分散配置します。ap-northeast-1 (東京リージョン) では 1a / 1b / 1c の3AZを使用します。

# Terraform: マルチAZ Subnet 設計変数
locals {
  azs = ["ap-northeast-1a", "ap-northeast-1b", "ap-northeast-1c"]
  public_subnets  = ["10.0.0.0/24","10.0.1.0/24","10.0.2.0/24"]
  private_subnets = ["10.0.128.0/18", "10.0.192.0/18", "10.0.160.0/18"]
}

Subnet タグ設定 (ALB Ingress Controller 必須)

AWS Load Balancer Controller が ALB を自動作成するには Subnet に以下のタグが必要です。このタグが欠けると Ingress リソース作成時にエラーが発生します (§6 詰まりポイント参照)。

# Public Subnet タグ (ALB 配置用)
resource "aws_subnet" "public" {
  count = length(local.azs)
  vpc_id= aws_vpc.main.id
  cidr_block  = local.public_subnets[count.index]
  availability_zone = local.azs[count.index]

  tags = {
 "kubernetes.io/cluster/${var.cluster_name}" = "shared"
 "kubernetes.io/role/elb"  = "1"
  }
}

# Private Subnet タグ (Worker Node / 内部 ALB 用)
resource "aws_subnet" "private" {
  count = length(local.azs)
  vpc_id= aws_vpc.main.id
  cidr_block  = local.private_subnets[count.index]
  availability_zone = local.azs[count.index]

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

CIDR 設計 (Pod IP 枯渇対策)

EKS の Pod は VPC CNI によって Subnet から IP を直接取得します。1 Node あたりの最大 Pod 数はインスタンスタイプの ENI 数 × ENI あたりの IP 数で決まります (例: m5.large は最大 29 Pod)。

設計ポイント推奨値理由
Private Subnet CIDR/18 以上 (16,384 IP)Pod × Node 数が数千になると /24 では枯渇
VPC CIDR/16 (65,536 IP)Subnet を複数 AZ × 複数用途で分割するため
Subnet 数AZ 数 × 2 (Public + Private)AZ ごとに Public / Private を分離

2-3. Control Plane vs Data Plane 管理境界

管理責任マトリクス

管理項目Control Plane (AWS)Data Plane (ユーザー)
バージョン更新EKS マイナーバージョン提供Node AMI 更新・適用
可用性 SLA99.95% (AWS 保証)Node 障害時の Pod 再配置はユーザー設計次第
スケーリング自動 (AWS 管理)Node Group スケール設定 / Karpenter 設定
セキュリティパッチAWS が自動適用OS パッチ・AMI 更新はユーザー担当
ログ収集CloudWatch Logs への出力設定Node / Pod メトリクス・ログ収集設定

エンドポイントアクセス設定

EKS API Server のエンドポイントは プライベートエンドポイント有効化を推奨 します。

# kubeconfig を更新して kubectl をクラスタに接続
aws eks update-kubeconfig--name my-eks-cluster--region ap-northeast-1--alias my-cluster

# 接続確認
kubectl get nodes
kubectl cluster-info

本番環境ではパブリックエンドポイントを無効 (endpoint_public_access = false) にし、踏み台サーバーや VPN 経由でアクセスする構成が推奨されます。パブリックエンドポイントを残す場合は public_access_cidrs で許可 CIDR を絞ってください。


2-4. 本番運用マルチAZ設計

Node 配置とトポロジーラベル

Managed Node Group は topology.kubernetes.io/zone ラベルを Node に自動付与します。このラベルを活用して Pod を AZ 間に分散配置します。

# Pod Anti-Affinity: 同じ AZ への集中を避けて分散配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
 spec:
affinity:
  podAntiAffinity:
 preferredDuringSchedulingIgnoredDuringExecution:
 - weight: 100
podAffinityTerm:
  labelSelector:
 matchLabels:
app: my-app
  topologyKey: topology.kubernetes.io/zone

preferredDuringScheduling (ソフト制約) を使い、AZ に空きがない場合でも同一 AZ への配置を許容します。requiredDuringScheduling (ハード制約) にすると Pod が Pending 状態になるリスクがあります。

PodDisruptionBudget (計画的なノード停止対策)

Node 更新やドレイン時に最低限の Pod 可用性を保証するには PodDisruptionBudget を設定します。

# PodDisruptionBudget: ドレイン中も最低1Pod を稼働維持
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: 1
  selector:
 matchLabels:
app: my-app

minAvailable: 1 は「最低1Pod は稼働し続ける」という制約です。Node を drain する際にこの制約が満たせなければ drain がブロックされ、意図しないサービス断を防止できます。


2-5. Terraform 実装例 (aws_eks_cluster)

resource "aws_eks_cluster" "main" {
  name  = var.cluster_name
  version  = "1.32"
  role_arn = aws_iam_role.eks_cluster.arn

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

  kubernetes_network_config {
 service_ipv4_cidr = "172.20.0.0/16"
 ip_family= "ipv4"
  }

  enabled_cluster_log_types = [
 "api", "audit", "authenticator",
 "controllerManager", "scheduler"
  ]

  depends_on = [
 aws_iam_role_policy_attachment.eks_cluster_policy,
  ]

  tags = {
 Environment = var.environment
 ManagedBy= "terraform"
  }
}

variable "cluster_name" {
  type  = string
  description = "EKS クラスタ名"
}

variable "vpc_id" {
  type  = string
  description = "EKS クラスタを配置する VPC ID"
}

variable "subnet_ids" {
  type  = list(string)
  description = "EKS クラスタのサブネット ID リスト (マルチAZ 必須)"
}

variable "environment" {
  type  = string
  description = "環境名 (dev / stg / prd)"
}

enabled_cluster_log_types で Control Plane のログを CloudWatch Logs に出力します。audit ログは誰が何の API を呼んだかを記録するため本番環境では必須です。endpoint_public_access = false によりパブリックアクセスを無効化し、VPC 内部からのアクセスのみを許可します。

fig01: EKSクラスタ設計全体像 (VPC/Subnet/Control Plane/Data Plane/マルチAZ)

EKSクラスタ設計3鉄則

  1. VPC独立: EKS専用VPCを用意し既存VPCと分離 (セキュリティグループ管理の単純化)
  2. マルチAZ: Worker Nodeを最低3AZに分散配置 (AZ障害時の可用性確保)
  3. IAM最小権限: Node IAM Role はEKS必要権限のみ / IRSA でPod単位に分離 (§4で詳解)

3. ノード戦略 — Managed Node Group vs Karpenter 選択基準・Pros/Cons・適用シーン

EKS クラスタのノードプロビジョニングには大きく2つの方式があります。AWS が管理する Managed Node Group と、Kubernetes-native な Karpenter です。どちらを選ぶかはワークロードの性質・コスト感・運用チームのスキルセットによって変わります。本章では両方式の設計詳細と選定基準を解説します。


3-1. ノードプロビジョニングの2方式概要

Managed Node Group

AWS が EC2 Auto Scaling Group を管理し、ノードのプロビジョニング・アップデート・削除を半自動化する方式です。

特徴:
– AWS が AMI アップデートとローリングアップデートを管理
– EKS コントロールプレーンとの統合が深く、互換性リスクが低い
– 設定項目が少なく、Kubernetes 運用経験が浅いチームでも導入しやすい
– スケールアウトに数分かかる(Auto Scaling Group の起動待ち)

Karpenter

Kubernetes クラスタ内で動作するノードプロビジョニングコントローラです。Pod のスケジュール待ち状態を検知し、必要なノードを直接 EC2 で起動します。

特徴:
– スケールアウトが高速(Auto Scaling Group を経由せず EC2 を直接起動)
– Spot Instance との親和性が高く、コスト最適化効果が大きい
– NodePool / EC2NodeClass という CRD (Custom Resource Definition) で柔軟に制御
– Karpenter 自体の運用知識が必要(アップグレード / IRSA 設定 / Disruption 設計)

2方式の簡易比較

評価軸Managed Node GroupKarpenter
運用複雑度低(AWS管理)中〜高(CRD設計が必要)
コスト効率高(Spot/統合最適化)
スケール速度低〜中(数分)高(数十秒)
EKS 互換性高(AWS公式)高(EKS公式サポート済)
移行難度中(既存 MNG から移行する場合)

3-2. Managed Node Group 詳細

Terraform 実装例

resource "aws_eks_node_group" "app" {
  cluster_name = aws_eks_cluster.main.name
  node_group_name = "app-node-group"
  node_role_arn= aws_iam_role.node.arn
  subnet_ids= var.private_subnet_ids

  ami_type = "AL2_x86_64"
  capacity_type  = "ON_DEMAND"
  instance_types = ["m5.xlarge", "m5a.xlarge"]

  scaling_config {
 min_size  = 2
 max_size  = 10
 desired_size = 3
  }

  update_config {
 max_unavailable_percentage = 25
  }

  launch_template {
 id= aws_launch_template.node.id
 version = aws_launch_template.node.latest_version
  }

  labels = {
 role  = "app"
 node-group  = "managed"
  }

  taint {
 key = "dedicated"
 value  = "app"
 effect = "NO_SCHEDULE"
  }

  tags = {
 Environment = var.environment
 ManagedBy= "terraform"
  }

  lifecycle {
 ignore_changes = [scaling_config[0].desired_size]
  }
}

resource "aws_launch_template" "node" {
  name_prefix= "eks-node-"
  update_default_version = true

  block_device_mappings {
 device_name = "/dev/xvda"
 ebs {
volume_size  = 50
volume_type  = "gp3"
delete_on_termination = true
encrypted = true
 }
  }

  metadata_options {
 http_endpoint= "enabled"
 http_tokens  = "required"
 http_put_response_hop_limit = 2
  }

  tag_specifications {
 resource_type = "instance"
 tags = {
Name = "eks-node"
 }
  }
}

設計ポイント

capacity_type の選択:
ON_DEMAND: 安定した基盤ノード(システムコンポーネント / ステートフルワークロード)
SPOT: コスト削減を優先するアプリノード(中断耐性のあるワークロードのみ)

launch_template との組み合わせ:
– IMDSv2 強制(http_tokens = "required")はセキュリティ上必須
– EBS 暗号化を有効化してデータ保護を確保
gp3 ボリュームは gp2 より高性能かつ低コスト

ローリングアップデート:
update_config.max_unavailable_percentage = 25 で同時に落とせるノード数を制御
– Kubernetes バージョンアップ時は max_unavailable_percentage を低めに設定して可用性を維持

適用シーン

  • 運用シンプルさを優先したい場合(Karpenter の学習コストを避けたい)
  • コンプライアンス要件で特定の AMI バージョン固定が求められる場合
  • ステートフルなワークロード(データベース / キャッシュ)を安定的に運用する場合
  • 既存 ECS からの移行で Kubernetes の学習コストを最小化したい場合

3-3. Karpenter 詳細

NodePool + EC2NodeClass の設計

Karpenter は NodePool(プロビジョニングポリシー)と EC2NodeClass(AWS固有設定)の2つの CRD で設定します。

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: app-nodepool
spec:
  template:
 metadata:
labels:
  role: app
 spec:
nodeClassRef:
  group: karpenter.k8s.aws
  kind: EC2NodeClass
  name: app-nodeclass
requirements:
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["spot", "on-demand"]
  - key: node.kubernetes.io/instance-type
 operator: In
 values: ["m5.large", "m5.xlarge", "m5a.large", "m5a.xlarge", "m6i.large", "m6i.xlarge"]
  - key: topology.kubernetes.io/zone
 operator: In
 values: ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
taints:
  - key: dedicated
 value: app
 effect: NoSchedule
  limits:
 cpu: "100"
 memory: 400Gi
  disruption:
 consolidationPolicy: WhenUnderutilized
 consolidateAfter: 30s
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: app-nodeclass
spec:
  amiFamily: AL2023
  role: "KarpenterNodeRole-${cluster_name}"
  subnetSelectorTerms:
 - tags:
  karpenter.sh/discovery: "${cluster_name}"
  securityGroupSelectorTerms:
 - tags:
  karpenter.sh/discovery: "${cluster_name}"
  blockDeviceMappings:
 - deviceName: /dev/xvda
ebs:
  volumeSize: 50Gi
  volumeType: gp3
  encrypted: true

Spot Instance 活用と中断ハンドリング

Karpenter は Spot Instance の中断通知(Spot Interruption Notice)を自動検知し、Pod を事前に退避させます。

# Spot 中断耐性のある Pod 設計 (Deployment)
spec:
  template:
 spec:
terminationGracePeriodSeconds: 120
tolerations:
  - key: "karpenter.sh/capacity-type"
 operator: "Equal"
 value: "spot"
 effect: "NoSchedule"
topologySpreadConstraints:
  - maxSkew: 1
 topologyKey: topology.kubernetes.io/zone
 whenUnsatisfiable: DoNotSchedule
 labelSelector:
matchLabels:
  app: my-app

terminationGracePeriodSeconds を十分に確保し、AZ 分散(topologySpreadConstraints)を設定することで Spot 中断時の影響を最小化します。

Karpenter Controller 用 IRSA

Karpenter コントローラ自身が EC2 を起動・終了するために IRSA が必要です(§4 の IRSA 設計と連動)。

module "karpenter_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"

  role_name  = "karpenter-controller"
  attach_karpenter_controller_policy = true
  karpenter_controller_cluster_name  = aws_eks_cluster.main.name
  karpenter_controller_node_iam_role_arns = [
 aws_iam_role.karpenter_node.arn
  ]

  oidc_providers = {
 ex = {
provider_arn= aws_iam_openid_connect_provider.eks.arn
namespace_service_accounts = ["karpenter:karpenter"]
 }
  }
}

適用シーン

  • コスト最適化が最優先でSpot Instanceを積極活用したい場合
  • Pod のスケールアウト速度(Horizontal Pod Autoscaler 連動)が重要な場合
  • バッチ系ワークロードで大量ノードを短時間で起動・削除するサイクルが頻繁な場合
  • 複数のインスタンスファミリーを柔軟に組み合わせたい場合

3-4. 5軸比較マトリクス

fig02: Managed Node Group vs Karpenter 比較図

評価軸Managed Node GroupKarpenter
運用複雑度★☆☆ 低(AWS管理 / 設定項目少)★★★ 高(CRD / IRSA / Disruption 設計が必要)
コスト効率★★☆ 中(Spot利用は可能だが手動設定)★★★ 高(Spot自動選択 / Consolidation で未使用ノード自動削除)
スケール速度★★☆ 中(Auto Scaling Group 経由で数分)★★★ 高(EC2直接起動で数十秒)
EKS 互換性★★★ 高(AWS公式サポート / EKS最適化AMI)★★★ 高(v1.0以降EKS公式サポート)
移行難度★★☆ 中(既存 MNG からの移行は段階的に実施)

3-5. 選定フローチャート(mermaid01)

flowchart TD
 A[ノード戦略選択] --> B{コスト最適化が最優先?}
 B -- Yes --> C{Spot Instance 活用可能?<br/>中断耐性あり?}
 C -- Yes --> D[Karpenter + Spot 推奨<br/>NodePool に spot/on-demand 混在]
 C -- No --> E[Karpenter + On-Demand<br/>Consolidation でコスト削減]
 B -- No --> F{運用シンプルさ優先?<br/>Kubernetes 経験浅い?}
 F -- Yes --> G[Managed Node Group 推奨<br/>ON_DEMAND + ローリングアップデート]
 F -- No --> H{既存 ECS からの移行?<br/>コンプライアンス要件あり?}
 H -- Yes --> G
 H -- No --> I{急激なスケールアウトが頻発?<br/>バッチ系ワークロード?}
 I -- Yes --> D
 I -- No --> J[混在パターン推奨<br/>System: MNG / App: Karpenter]

Managed Node Group vs Karpenter 選定サマリー

  • Managed Node Group を選ぶべきとき: 運用シンプルさ優先 / コンプライアンス要件あり(特定AMI固定) / Karpenter移行コスト許容不可 / ステートフルワークロード中心
  • Karpenter を選ぶべきとき: コスト最適化最優先 / Spot活用 / スケールアウト速度重視 / バッチ系ワークロード中心
  • 混在パターン(推奨): システムコンポーネント(CoreDNS / kube-proxy / Karpenter自身)は Managed Node Group、アプリワークロードは Karpenter という組み合わせが本番で最も多い構成。Karpenter コントローラが起動するノードを Karpenter 自身で管理させないことが重要。

4. IRSA (IAM Roles for Service Accounts) 設計実践 — IAM入門Vol4 STS知識を EKS に応用 (山場)

IRSA (IAM Roles for Service Accounts) は、Kubernetes Pod 単位で AWS IAM Role を付与する仕組みだ。一言で表すと 「ECS タスクロール」の EKS 版 であり、IAM入門Vol4 で学んだ sts:AssumeRoleWithWebIdentity がそのまま登場する。


4-1. IRSA とは何か — IAM入門Vol4 STS との接続

ECS では各タスクに task_role_arn を指定するだけで IAM Role が付与された。しかし EKS では Pod が Kubernetes のスケジューラで動くため、AWS の仕組みを直接使えない。そこで IRSA が登場した。

IRSA の認証フローの核心:

IAM入門Vol4 §3 で学んだ「外部 IdP を Federated Principal として信頼する」パターンがここで直接活きる。

  • Principal: Federated (OIDC Provider ARN)
  • Action: sts:AssumeRoleWithWebIdentity
  • Condition: StringEqualssub: system:serviceaccount:NAMESPACE:SA_NAME

EKS クラスターが OIDC Provider として機能し、Pod に自動注入された JWT トークンを使って STS に AssumeRoleWithWebIdentity リクエストを送る。STS は OIDC Provider で JWT を検証し、成功すると一時認証情報 (AccessKey / SecretKey / SessionToken) を返す。Pod はこの一時認証情報で AWS API を呼ぶ。

IRSA がなかった場合の問題点:

方法問題
EC2 Instance Profile (Node の IAM Role を使う)Node 上の全 Pod が同じ権限を持つ。最小権限原則に違反。
環境変数でアクセスキーを注入キーの定期ローテーションが困難。漏洩リスクが高い。
IRSAPod ごとに異なる IAM Role。自動ローテーション。最小権限を徹底できる。

IRSA を使うことで「S3 読み取りが必要な Pod」と「RDS 接続が必要な Pod」に、それぞれ最小限の権限だけを付与できる。


4-2. IRSA の3コンポーネント詳細

IRSA は ① OIDC Provider / ② ServiceAccount アノテーション / ③ IAM Role 信頼ポリシー の 3 コンポーネントを正しく設定することで機能する。

① OIDC Provider の設定

EKS クラスターには固有の OIDC Issuer URL がある。この URL を使って AWS に OIDC Provider を登録する。

OIDC Issuer URL の確認:

# EKS クラスターの OIDC Issuer URL を確認
aws eks describe-cluster \
  --name my-eks-cluster \
  --query 'cluster.identity.oidc.issuer' \
  --output text
# 出力例: https://oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE

eksctl を使った OIDC Provider の登録:

# OIDC Provider を AWS IAM に登録 (eksctl)
eksctl utils associate-iam-oidc-provider \
  --region ap-northeast-1 \
  --cluster my-eks-cluster \
  --approve

# 登録確認
aws iam list-open-id-connect-providers

Terraform での OIDC Provider 設定:

# EKS クラスターの OIDC 証明書のサムプリントを取得
data "tls_certificate" "eks" {
  url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}

# AWS IAM に OIDC Provider を登録
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

  tags = {
 Name = "eks-oidc-provider"
 Cluster = aws_eks_cluster.main.name
  }
}

② ServiceAccount アノテーション

ServiceAccount に eks.amazonaws.com/role-arn アノテーションを付与する。このアノテーションが IRSA の起動トリガーだ。アノテーションがない ServiceAccount では IRSA は機能しない。

# IRSA 対応 ServiceAccount の作成
resource "kubernetes_service_account" "app" {
  metadata {
 name= "my-app-sa"
 namespace = "production"
 annotations = {
"eks.amazonaws.com/role-arn" = aws_iam_role.irsa_role.arn
 }
  }

  depends_on = [aws_iam_role.irsa_role]
}

③ IAM Role 信頼ポリシー

OIDC Provider を Federated Principal として信頼し、対象 ServiceAccount を sub Condition で絞り込む。

信頼ポリシーの JSON 形式 (構造理解のため):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringEquals": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLE:sub":
"system:serviceaccount:production:my-app-sa",
 "oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLE:aud":
"sts.amazonaws.com"
  }
}
 }
  ]
}

sub の値は system:serviceaccount:<namespace>:<serviceaccount_name> の形式で記述する。この Condition を省略すると、クラスター内の全 Pod がこの IAM Role を引き受けられてしまうため 必須の設定 だ。


4-3. Terraform 完全実装例

3コンポーネントを統合した Terraform 実装:

# ① OIDC Provider
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
}

# ② ローカル変数 (OIDC URL から https:// を除去)
locals {
  oidc_url = replace(aws_iam_openid_connect_provider.eks.url, "https://", "")
  oidc_arn = aws_iam_openid_connect_provider.eks.arn
}

# ③ IAM Role 信頼ポリシー (IRSA 用)
data "aws_iam_policy_document" "irsa_assume_role" {
  statement {
 effect  = "Allow"
 actions = ["sts:AssumeRoleWithWebIdentity"]

 principals {
type  = "Federated"
identifiers = [local.oidc_arn]
 }

 condition {
test  = "StringEquals"
variable = "${local.oidc_url}:sub"
values= ["system:serviceaccount:${var.namespace}:${var.service_account_name}"]
 }

 condition {
test  = "StringEquals"
variable = "${local.oidc_url}:aud"
values= ["sts.amazonaws.com"]
 }
  }
}

# ④ IAM Role 本体
resource "aws_iam_role" "irsa_role" {
  name= "${var.app_name}-irsa-role"
  assume_role_policy = data.aws_iam_policy_document.irsa_assume_role.json

  tags = {
 Namespace= var.namespace
 ServiceAccount = var.service_account_name
  }
}

# ⑤ 権限ポリシー (例: S3 読み取り)
resource "aws_iam_role_policy" "irsa_policy" {
  name = "${var.app_name}-policy"
  role = aws_iam_role.irsa_role.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect= "Allow"
  Action= ["s3:GetObject", "s3:ListBucket"]
  Resource = [
 "arn:aws:s3:::${var.s3_bucket_name}",
 "arn:aws:s3:::${var.s3_bucket_name}/*"
  ]
}
 ]
  })
}

# ⑥ Kubernetes ServiceAccount (アノテーション付き)
resource "kubernetes_service_account" "irsa_sa" {
  metadata {
 name= var.service_account_name
 namespace = var.namespace
 annotations = {
"eks.amazonaws.com/role-arn" = aws_iam_role.irsa_role.arn
 }
  }
}

この Terraform を適用後、Pod の serviceAccountNamevar.service_account_name を指定するだけで IRSA が自動的に機能する。Pod 内では AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE 環境変数が自動設定され、AWS SDK がこれを読み取って STS からの認証情報取得を自動的に行う。

IRSA 動作確認 — kubectl で環境変数を確認する:

Terraform 適用後、Pod が正しく IRSA を使っているか確認するには以下のコマンドを使う。

# Pod 内の IRSA 関連環境変数を確認
kubectl exec -n production my-app-pod -- env | grep -E 'AWS_ROLE|AWS_WEB_IDENTITY'
# 期待される出力:
# AWS_ROLE_ARN=arn:aws:iam::123456789012:role/my-app-irsa-role
# AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token

# 一時認証情報が正しく取得できているか確認 (AWS CLI がある Pod の場合)
kubectl exec -n production my-app-pod -- aws sts get-caller-identity
# 期待される出力:
# { "UserId": "AROAEXAMPLEID:botocore-session-...", "Account": "123456789012",
#"Arn": "arn:aws:sts::123456789012:assumed-role/my-app-irsa-role/botocore-session-..." }

AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE の両方が設定されていれば IRSA は正しく機能している。get-caller-identity の結果に assumed-role/my-app-irsa-role が含まれていれば、期待通りの IAM Role で動作していることが確認できる。

Pod Spec での ServiceAccount 指定:

Kubernetes の Deployment / Job では .spec.template.spec.serviceAccountName で IRSA 対応の ServiceAccount を指定する。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 2
  selector:
 matchLabels:
app: my-app
  template:
 metadata:
labels:
  app: my-app
 spec:
serviceAccountName: my-app-sa# IRSA 対応 ServiceAccount を指定
containers:
  - name: app
 image: my-app:latest

serviceAccountName を指定しない場合は default ServiceAccount が使われるが、default SA には通常アノテーションがないため IRSA は機能しない。


4-4. mermaid02: IRSA AssumeRoleWithWebIdentity シーケンス図

sequenceDiagram
 participant Pod as Pod<br>(アプリコンテナ)
 participant Mutator as EKS Mutating<br>Webhook
 participant OIDC as OIDC Provider<br>(EKS 発行)
 participant STS as AWS STS
 participant Role as IAM Role
 participant S3 as AWSリソース<br>(S3 等)

 Pod->>Mutator: Pod 起動 (ServiceAccount 指定)
 Mutator-->>Pod: JWT トークン + 環境変数注入<br>(projected volume)
 Pod->>STS: AssumeRoleWithWebIdentity<br>(JWT + RoleArn)
 STS->>OIDC: JWT 検証<br>(issuer / sub / aud)
 OIDC-->>STS: 検証 OK
 STS->>Role: 信頼ポリシー確認<br>(Principal:Federated + Condition:sub)
 Role-->>STS: 許可
 STS-->>Pod: 一時認証情報<br>(AccessKey / SecretKey / Token)
 Pod->>S3: API コール (一時認証情報使用)
 S3-->>Pod: レスポンス

4-5. Pod Identity との比較と移行判断

EKS 1.24 以降では IRSA の後継として EKS Pod Identity が登場した。新規構築の場合に Pod Identity が推奨されるケースも増えているため、違いを把握しておく。

比較項目IRSAEKS Pod Identity
対応 EKS バージョン全バージョン1.24 以上
OIDC Provider の管理必要 (1クラスター1個)不要 (EKS が内部管理)
IAM Role 設定場所IAM Role 信頼ポリシーに OIDC Provider を記述aws_eks_pod_identity_association リソース
クロスアカウント対応可 (OIDC Provider ARN を別アカウントが信頼)基本的に同一アカウント内
既存クラスターへの影響なし信頼ポリシーの書き換えが必要

移行判断基準:

  • EKS 1.23 以下、または既存 IRSA ベースクラスター → IRSA を継続 するのが現実的
  • EKS 1.24 以上の新規クラスター、クロスアカウントが不要 → Pod Identity も選択肢
  • クロスアカウント IRSA が必要な場合 → IRSA を選択 (Pod Identity はクロスアカウントに弱い)

本記事の実装は IRSA ベースで解説する。


fig03: IRSA 信頼関係構造図 (OIDC Provider × ServiceAccount × IAM Role)

IRSA 設計3鉄則 (必ず守ること)

  1. OIDC Provider は1クラスター1個: EKS クラスターごとに専用の OIDC Provider を作成する。複数クラスターで共有すると、あるクラスターの Pod が別クラスター向けの IAM Role を引き受けられる状態になり、意図しない権限昇格が起きる。
  2. ServiceAccount Annotation は必須: eks.amazonaws.com/role-arn アノテーションがない ServiceAccount は IRSA が機能しない。Terraform の kubernetes_service_account リソースで明示的に設定し、漏れを防ぐ。
  3. 信頼ポリシーの Condition sub を必ず設定する: system:serviceaccount:NAMESPACE:SA_NAME 形式で名前空間と ServiceAccount 名を絞ること。省略すると同じ OIDC Provider を持つクラスターの全 Pod がこの IAM Role を引き受けられる状態になる。

EKS IRSA 公式ドキュメント → 確認する


5. AWS Load Balancer Controller (ALB Ingress) 設計実装 — Helm Chart + Annotation

5-1. AWS Load Balancer Controller とは

AWS Load Balancer Controller (旧称: ALB Ingress Controller) は、Kubernetes の Ingress リソースを AWS Application Load Balancer (ALB) に対応付けるコントローラーだ。EKS クラスターにデプロイすることで、YAML マニフェストを apply するだけで ALB を自動作成・管理できる。

コントローラーの役割

クラスター内で動作するコントローラーが Ingress オブジェクトを継続的に監視し、対応する AWS リソース (ALB / TargetGroup / Listener / Routing Rule) を自動的に作成・更新・削除する。アプリケーションチームは AWS コンソールを操作せず、Kubernetes マニフェストだけで ALB の設定を完全管理できる。

ALB を Ingress として使うメリット:

  • パスベースルーティング: /api/* → Service A、/web/* → Service B のように URL パスで振り分ける
  • ヘッダーベースルーティング: Host ヘッダーや HTTP ヘッダーの値でルーティングを制御する
  • HTTPS 終端: ACM 証明書を ALB にアタッチし、Pod へは HTTP で転送することで証明書管理を一元化する
  • Ingress Grouping: 複数の Ingress リソースを1つの ALB に集約し、ALB 料金を節約する
  • Weighted Target Groups: Blue/Green デプロイや Canary リリースを ALB レベルで制御できる

IRSA との連携

コントローラーは EKS クラスター内の Pod として動作するが、ALB の作成・管理には AWS API へのアクセスが必要だ。§4 で設定した IRSA を利用してコントローラーに AWS 権限を付与する:

  1. コントローラー用 IAM Policy を作成する (ELB / EC2 / ACM / Route53 操作権限)
  2. IAM Role を作成し Trust Policy に EKS OIDC Provider を設定する
  3. Kubernetes ServiceAccount に IAM Role ARN を eks.amazonaws.com/role-arn Annotation で付与する
  4. コントローラーが IRSA 経由で一時認証情報を取得し AWS API を呼び出す

VPC サブネットタグの設定 (ALB 自動検出の前提条件)

ALB Controller が正しいサブネットを自動検出するには、VPC サブネットに特定のタグを付ける必要がある。このタグがないと no matching subnet エラーが発生する。

サブネット種別必要なタグ
パブリック (internet-facing ALB 用)kubernetes.io/role/elb1
プライベート (internal ALB 用)kubernetes.io/role/internal-elb1
EKS クラスターが使用するサブネットkubernetes.io/cluster/<クラスター名>owned または shared

Terraform で EKS クラスターを構築している場合は terraform-aws-modules/eks がこれらのタグを自動付与する。既存 VPC に後から EKS を追加した場合は手動でタグを追加すること。

5-2. Helm Chart インストール手順

インストールは AWS 側の IAM 設定と Kubernetes 側の Helm デプロイの2段階で進める。

前提条件

  • EKS クラスターが稼働済みであること
  • kubectl / helm / eksctl / AWS CLI がセットアップ済みであること
  • §3 で作成した EKS OIDC Provider が有効であること
  • VPC の パブリックサブネットに kubernetes.io/role/elb: 1 タグが付いていること (ALB 自動検出用)

IAM Policy + IRSA の設定

# コントローラー用 IAM Policy の作成 (公式マニフェストを使用)
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json

aws iam create-policy--policy-name AWSLoadBalancerControllerIAMPolicy--policy-document file://iam_policy.json

# IRSA 用 IAM ServiceAccount の作成 (OIDC Provider と自動連携)
eksctl create iamserviceaccount--cluster=my-production-cluster--namespace=kube-system--name=aws-load-balancer-controller--attach-policy-arn=arn:aws:iam::123456789012:policy/AWSLoadBalancerControllerIAMPolicy--override-existing-serviceaccounts--region=ap-northeast-1--approve

Helm Chart のインストール

# EKS Helm リポジトリの追加
helm repo add eks https://aws.github.io/eks-charts
helm repo update

# AWS Load Balancer Controller のインストール
helm install aws-load-balancer-controller eks/aws-load-balancer-controller-n kube-system--set clusterName=my-production-cluster--set serviceAccount.create=false--set serviceAccount.name=aws-load-balancer-controller--set region=ap-northeast-1--set vpcId=vpc-0a1b2c3d4e5f67890

# インストール確認
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller

serviceAccount.create=falseserviceAccount.name=aws-load-balancer-controller を指定することで、eksctl で作成した IRSA 設定済みの ServiceAccount を使う。新しい ServiceAccount を作成すると IRSA の Annotation が失われるため注意が必要だ。

インストール後はコントローラーが正常に動作しているかを確認する。READY 2/2 と表示されれば問題なく稼働している。エラーが出ている場合は kubectl logs でログを確認し、IAM 権限またはサブネットタグの設定ミスを疑うこと。

5-3. Ingress リソース設計 — Annotation を使いこなす

Ingress リソースの設定は Annotation で細かく制御する。以下は本番環境で使う主要な Annotation を網羅したサンプルだ。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: production-ingress
  namespace: production
  annotations:
 # コントローラー指定 (必須)
 kubernetes.io/ingress.class: alb

 # ALB タイプ (internet-facing: 外部公開 / internal: VPC内部のみ)
 alb.ingress.kubernetes.io/scheme: internet-facing

 # ルーティングモード (ip: Pod直接 / instance: NodePort経由)
 alb.ingress.kubernetes.io/target-type: ip

 # HTTPS 設定 (ACM 証明書 ARN)
 alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:123456789012:certificate/abc12345-de67-fg89-hi01-jklmnopqrstu

 # リスナーポート設定 (HTTP / HTTPS 両方を有効化)
 alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'

 # HTTP → HTTPS リダイレクト
 alb.ingress.kubernetes.io/ssl-redirect: "443"

 # Ingress グルーピング (複数Ingressを1ALBに集約してコスト削減)
 alb.ingress.kubernetes.io/group.name: production-alb

 # サブネット明示指定 (自動検出より明示を推奨)
 alb.ingress.kubernetes.io/subnets: subnet-0a1b2c3d4e5f67890,subnet-0b2c3d4e5f67890a1

 # ヘルスチェックパスとタイムアウト設定
 alb.ingress.kubernetes.io/healthcheck-path: /health
 alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
 alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5"
 alb.ingress.kubernetes.io/healthy-threshold-count: "2"
 alb.ingress.kubernetes.io/unhealthy-threshold-count: "3"

 # ALB アイドルタイムアウト (デフォルト60秒)
 alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120

 # TargetGroup のドレイン設定 (ローリングデプロイ時の接続切断を防ぐ)
 alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
spec:
  rules:
 - host: api.example.com
http:
  paths:
 - path: /api
pathType: Prefix
backend:
  service:
 name: api-service
 port:
number: 8080
 - path: /
pathType: Prefix
backend:
  service:
 name: frontend-service
 port:
number: 3000

target-type の選択基準

ip モード (Pod 直接ルーティング) は Fargate 使用時に必須で、EC2 ノードでも ENI から直接 Pod に転送するため遅延が低い。instance モードは NodePort 経由のため Pod 移動のたびに NodePort マッピングが変わる可能性があり、Pod 数が多いクラスターでは ip が推奨だ。

5-4. Annotation 重要一覧 — 設計マトリクス

Annotation用途推奨値 / 選択肢
schemeALB 公開範囲internet-facing (外部) / internal (内部VPC)
target-typePod へのルーティング方式ip (推奨・Fargate必須) / instance
certificate-arnACM 証明書 (HTTPS必須)ACM 証明書の完全 ARN
listen-portsリスナーポート定義'[{"HTTP":80},{"HTTPS":443}]'
ssl-redirectHTTP→HTTPS リダイレクト"443"
group.nameIngress グルーピング任意の名前 (同名で同ALBに集約)
subnetsALB 配置サブネットパブリックサブネット ID (カンマ区切り)
healthcheck-pathヘルスチェック URL パス/health / /ping
load-balancer-attributesALB 詳細設定idle_timeout.timeout_seconds=120
target-group-attributesTargetGroup 設定deregistration_delay.timeout_seconds=30

表中の Annotation は全て alb.ingress.kubernetes.io/ プレフィックスを省略している。

Ingress Grouping のコスト効果

ALB は1台あたり最低でも月額約 $20 (LCU 別) が発生する。group.name を設定することで複数の Ingress を1台の ALB に集約し、Namespace や Deployment が増えても ALB 台数を抑えられる。ただし ALB 1台あたりのルール数上限 (デフォルト 100) に注意すること。

5-5. Terraform での ALB Controller IRSA 設定

Helm + eksctl のワンライナーでも動作するが、本番環境では Terraform で IAM Role および Helm Release の設定を IaC 管理することが多い。

# コントローラー用 IAM Policy (公式 JSON から読み込み)
resource "aws_iam_policy" "alb_controller" {
  name  = "AWSLoadBalancerControllerIAMPolicy"
  description = "IAM policy for AWS Load Balancer Controller"
  policy= file("${path.module}/iam_policy.json")
}

# IRSA 用 IAM Role (terraform-aws-modules を使用)
module "alb_controller_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.0"

  role_name = "AWSLoadBalancerControllerRole"

  attach_load_balancer_controller_policy = true

  oidc_providers = {
 main = {
provider_arn= module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
 }
  }
}

# Kubernetes ServiceAccount (IRSA Annotation 付き)
resource "kubernetes_service_account" "alb_controller" {
  metadata {
 name= "aws-load-balancer-controller"
 namespace = "kube-system"
 annotations = {
"eks.amazonaws.com/role-arn" = module.alb_controller_irsa.iam_role_arn
 }
  }
}

# Helm Release (Terraform で Helm 管理)
resource "helm_release" "alb_controller" {
  name = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart= "aws-load-balancer-controller"
  namespace  = "kube-system"
  version = "1.7.2"

  set {
 name  = "clusterName"
 value = var.cluster_name
  }

  set {
 name  = "serviceAccount.create"
 value = "false"
  }

  set {
 name  = "serviceAccount.name"
 value = "aws-load-balancer-controller"
  }

  set {
 name  = "region"
 value = var.aws_region
  }

  set {
 name  = "vpcId"
 value = module.vpc.vpc_id
  }

  depends_on = [kubernetes_service_account.alb_controller]
}

attach_load_balancer_controller_policy = true を使うと、terraform-aws-modules が公式推奨の IAM Policy を自動でアタッチする。iam_policy.json を手動管理する場合は aws_iam_policy リソースに切り替える。Helm Chart のバージョンは EKS クラスターのバージョンとの互換性を確認してから固定すること。

Helm Chart バージョンと EKS バージョンの対応

EKS バージョン推奨 Helm Chart バージョン備考
1.30 以降1.8.x 以降Pod Identity 対応版
1.28 〜 1.291.7.xIRSA / Pod Identity 両対応
1.26 〜 1.271.6.xIRSA のみ

Helm Chart のバージョンは helm search repo eks/aws-load-balancer-controller --versions で最新版を確認すること。アップグレード時は helm upgrade を使い、設定値 (values.yaml) を差分チェックしてから実行する。

アップグレード前には必ず helm diff プラグインで変更差分を確認し、Annotation や ServiceAccount 設定が意図せず上書きされないことを検証することを強く推奨する。

ALB Ingress Annotation 最重要5選

  • alb.ingress.kubernetes.io/scheme: internet-facing: 外部公開 ALB (internal は VPC 内部のみ)
  • alb.ingress.kubernetes.io/target-type: ip: Pod 直接ルーティング (Fargate 必須、EC2でも推奨)
  • alb.ingress.kubernetes.io/certificate-arn: ACM 証明書 ARN (HTTPS 設定に必須)
  • alb.ingress.kubernetes.io/group.name: 複数 Ingress を1つの ALB に集約してコスト削減
  • alb.ingress.kubernetes.io/subnets: ALB 配置サブネットを明示指定 (自動検出より安定)

ALB Ingress + Argo CD GitOps 深掘り → 詳細はこちら


6. 詰まりポイント7選 図解 — EKS本番運用で詰まる7つの落とし穴

EKS クラスターを初めてセットアップした際、ほぼ全員が同じ箇所で詰まる。IAM の設定ミス、Kubernetes リソースの設定漏れ、Helm Chart の設定誤りが複合的に絡み合い、エラーメッセージだけでは原因を特定しにくい。ここでは現場で頻出する7つの落とし穴を図解と対策コードで整理する。

fig04: 詰まりポイント7パターン体系図


詰まりポイント1: kubeconfig 更新忘れ

EKS クラスターを作成した後に kubectl コマンドが Unable to connect to the server エラーを返す場合、kubeconfig が更新されていないか、context が切り替わっていないことが多い。Terraform や eksctl でクラスターを作成しても、ローカルの kubeconfig は自動で更新されない点を見落としやすい。

  • 誤解: EKS クラスターを作成すれば自動的に kubeconfig が設定される
  • 実態: aws eks update-kubeconfig を実行しないと、新しいクラスターへの接続情報が追加されない
  • 対策: クラスター作成後に必ず update-kubeconfig を実行し、現在の context を確認する
# kubeconfig を更新する
aws eks update-kubeconfig \
  --region ap-northeast-1 \
  --name my-eks-cluster

# 現在の context を確認
kubectl config current-context

# 利用可能な context 一覧を確認
kubectl config get-contexts

# 特定の context に切り替える
kubectl config use-context arn:aws:eks:ap-northeast-1:ACCOUNT_ID:cluster/my-eks-cluster

# 接続確認
kubectl get nodes

詰まりポイント1 対処法

kubeconfig 更新後は kubectl config current-context で正しいクラスターを向いているか確認する。複数クラスターを管理する場合は kubectx などのツールで context を明示管理し、誤ったクラスターへの操作を防ぐ。


詰まりポイント2: aws-auth ConfigMap 更新漏れ

新しい IAM ユーザーやロールを EKS クラスターに追加した後も kubectlYou must be logged in to the server (Unauthorized) エラーを返すケースがある。aws-auth ConfigMap への追加を忘れているのが原因だ。IAM ポリシーでは EKS クラスターへのアクセス権限を表現できず、Kubernetes 側の ConfigMap での登録が別途必要になる。

  • 誤解: IAM ロールに EKS の権限があれば kubectl が使える
  • 実態: EKS クラスターへのアクセスには aws-auth ConfigMap への登録が必要
  • 対策: mapRoles または mapUsers に IAM エンティティを追加する
# aws-auth ConfigMap の現在の設定を確認
kubectl -n kube-system describe configmap aws-auth
# aws-auth ConfigMap に新しいロールを追加する例
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
 - rolearn: arn:aws:iam::ACCOUNT_ID:role/NodeInstanceRole
username: system:node:{{EC2PrivateDNSName}}
groups:
  - system:bootstrappers
  - system:nodes
 - rolearn: arn:aws:iam::ACCOUNT_ID:role/DeveloperRole
username: developer
groups:
  - system:masters

詰まりポイント2 対処法

aws-auth ConfigMap の編集は kubectl edit configmap aws-auth -n kube-system で行うか、eksctl や Terraform の aws_eks_access_entry リソースで管理する。ConfigMap の YAML 構文ミス (インデント誤り) も Unauthorized エラーの原因になるため、編集後は必ず kubectl get nodes で疎通確認する。


詰まりポイント3: OIDC Provider 未作成

IRSA を設定したにもかかわらず Pod が AWS 一時認証情報を取得できず、No valid credential sources found エラーが出るケースがある。OIDC Provider がクラスターに紐付けられていないのが原因だ。EKS クラスターは OIDC Issuer URL を持つが、IAM 側に対応する OIDC Provider を登録するまで IRSA は機能しない。

  • 誤解: EKS クラスターを作成すれば IRSA がすぐに使える
  • 実態: IAM OIDC Provider を別途作成・登録しないと IRSA は機能しない
  • 対策: クラスターの OIDC Issuer URL を確認し、対応する OIDC Provider を IAM に登録する
# クラスターの OIDC Issuer URL を確認
aws eks describe-cluster \
  --name my-eks-cluster \
  --query "cluster.identity.oidc.issuer" \
  --output text

# 登録済み OIDC Provider 一覧を確認 (空なら IRSA 未設定)
aws iam list-open-id-connect-providers

# OIDC Provider を作成 (eksctl を使う場合)
eksctl utils associate-iam-oidc-provider \
  --region ap-northeast-1 \
  --cluster my-eks-cluster \
  --approve
# Terraform: EKS クラスターの OIDC Provider を作成
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

  tags = {
 Cluster = aws_eks_cluster.main.name
  }
}

詰まりポイント3 対処法

IRSA を使うすべてのクラスターで OIDC Provider の作成が必要だ。Terraform で EKS クラスターを管理する場合は aws_iam_openid_connect_provider リソースをクラスターと同時に作成するのが確実。eksctl では associate-iam-oidc-provider コマンドを使う。


詰まりポイント4: ServiceAccount Annotation の namespace 誤り

IRSA を設定した ServiceAccount に IAM ロール ARN の Annotation を追加したにもかかわらず、Pod が AWS API を認証なしで呼んでいる。信頼ポリシーの sub Condition と ServiceAccount の実際の名前空間 (namespace) が一致していないのが原因だ。

  • 誤解: ServiceAccount に eks.amazonaws.com/role-arn Annotation を付ければ IRSA が機能する
  • 実態: IAM ロールの信頼ポリシーの sub Condition に正確な namespace と ServiceAccount 名を設定しないと AssumeRoleWithWebIdentity が失敗する
  • 対策: 信頼ポリシーの sub 値を system:serviceaccount:NAMESPACE:SA_NAME 形式で正確に記述する
# ServiceAccount に IRSA Annotation を付与する例
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-service-account
  namespace: production # この namespace を信頼ポリシーの sub に反映する
  annotations:
 eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/MyIRSARole
// IAM ロール信頼ポリシー — sub Condition の namespace を ServiceAccount と一致させる
{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringEquals": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:aud": "sts.amazonaws.com",
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:production:my-service-account"
  }
}
 }
  ]
}

詰まりポイント4 対処法

信頼ポリシーの sub 値は system:serviceaccount:NAMESPACE:SA_NAME の形式で namespace と SA 名の両方を正確に指定する。Pod が正しい一時認証情報を取得しているか確認するには kubectl exec -it <pod> -- aws sts get-caller-identity を実行する。


詰まりポイント5: Karpenter NodePool の requirements 設定漏れ

Karpenter を導入したにもかかわらず、Pod が Pending 状態のまま Node が追加されない。NodePool の requirements に必須フィールドが設定されていないのが原因だ。kubernetes.io/archkarpenter.sh/capacity-type を明示しないと、Karpenter がどのインスタンスタイプを選択すべきか判断できない。

  • 誤解: NodePool を作成すれば Karpenter が適切なインスタンスタイプを自動選択してくれる
  • 実態: kubernetes.io/archkubernetes.io/oskarpenter.sh/capacity-type などの必須フィールドが欠けていると、Node の起動条件が曖昧になりスケーリングが動かない
  • 対策: NodePool に最低限の requirements を明示的に設定し、意図しないインスタンスタイプの起動も防ぐ
# Karpenter NodePool — requirements を明示的に設定する例
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
 spec:
requirements:
  - key: kubernetes.io/arch
 operator: In
 values: ["amd64"]
  - key: kubernetes.io/os
 operator: In
 values: ["linux"]
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["on-demand", "spot"]
  - key: karpenter.k8s.aws/instance-category
 operator: In
 values: ["c", "m", "r"] # 高コストインスタンスを除外
nodeClassRef:
  apiVersion: karpenter.k8s.aws/v1
  kind: EC2NodeClass
  name: default
  limits:
 cpu: 100
  disruption:
 consolidationPolicy: WhenEmptyOrUnderutilized
 consolidateAfter: 1m

詰まりポイント5 対処法

Karpenter がスケーリングしない原因は kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter のログで確認できる。NodePool の requirements が原因の場合は「no instance type offered」などのメッセージが出る。karpenter.k8s.aws/instance-category で制限を追加し、意図しない高コストインスタンスの起動も合わせて防ぐ。


詰まりポイント6: IRSA 信頼ポリシーの aud Condition 設定漏れ

IRSA 設定が完了しているのに sts:AssumeRoleWithWebIdentity が失敗する。信頼ポリシーに aud Condition が設定されていないのが原因だ。IAM入門 Vol4 で解説した Confused Deputy 対策と同じ原理で、aud がないと OIDC トークンの audience 検証がスキップされてしまう。

  • 誤解: sub Condition だけを設定すれば IRSA が動作する
  • 実態: OIDC トークンの aud (audience) も sts.amazonaws.com で Condition 検証する必要がある
  • 対策: 信頼ポリシーに aud: sts.amazonaws.com の Condition を必ず追加する
// 誤り: aud Condition がない → AssumeRoleWithWebIdentity が失敗
{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringEquals": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:default:my-sa"
  }
}
 }
  ]
}
// 正解: aud Condition を追加 (sts.amazonaws.com は固定値)
{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringEquals": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:aud": "sts.amazonaws.com",
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:default:my-sa"
  }
}
 }
  ]
}

詰まりポイント6 対処法

Terraform モジュールで IRSA ロールを作成する場合は audsub の両 Condition が自動生成されるか確認する。手書きの信頼ポリシーでは aud: sts.amazonaws.com の行を忘れやすい。IAM入門 Vol4 で解説した Confused Deputy 対策でも aud は必須の検証項目だ。


詰まりポイント7: Helm Chart の values.yaml 設定ミス

aws-load-balancer-controller を Helm でインストールした後、Ingress リソースを作成しても ALB が作成されない。values.yaml の必須パラメーターが設定されていないのが原因だ。Controller が ALB を作成するためには、対象クラスターと VPC の情報が必要だが、デフォルト値では設定されない。

  • 誤解: Helm Chart をデフォルト値でインストールすれば ALB Ingress Controller が動作する
  • 実態: clusterNamevpcIdregion の3つを正確に指定しないと Controller が ALB を作成できない
  • 対策: helm install 時に必須パラメーターを --set か values.yaml で明示的に設定する
# Helm status で Pod の状態を確認
helm status aws-load-balancer-controller -n kube-system

# Controller の Pod ログで設定エラーを確認
kubectl logs -n kube-system \
  -l app.kubernetes.io/name=aws-load-balancer-controller \
  --tail=50

# Ingress のイベントで ALB 作成エラーを確認
kubectl describe ingress my-ingress -n production
# values.yaml — aws-load-balancer-controller の必須パラメーター
clusterName: my-eks-cluster # EKS クラスター名 (必須)
region: ap-northeast-1# リージョン (必須)
vpcId: vpc-xxxxxxxxxxxxxxxxx# VPC ID (必須)
serviceAccount:
  create: false # IRSA の ServiceAccount を使う場合は false
  name: aws-load-balancer-controller
image:
  repository: 602401143452.dkr.ecr.ap-northeast-1.amazonaws.com/amazon/aws-load-balancer-controller
  tag: v2.8.1

詰まりポイント7 対処法

Ingress を作成後に ALB が作成されない場合は、まず Controller Pod のログを確認する。次に kubectl describe ingress <name> でイベントを確認し、エラーメッセージを特定する。クラスター名・VPC ID・リージョンの3つが正しいかを最初に確認するのが最も効率的だ。


7. アンチパターン→正解パターン変換演習 — 5問で実践力を身につける

理論を身につけた後は「壊れた設定を直す」演習が定着を促す。EKS本番運用でよくある誤った設定を見て、どこが問題かを特定し正解パターンに修正する5問の演習を用意した。YAML・JSON・Terraform (HCL) の形式を使い分け、実装力を高めよう。

演習5問 解答・解説を確認する


演習1: aws-auth ConfigMap に IAM Role ARN が間違っている

アンチパターン — account ID の桁が抜け、role name にタイプミスがある:

# 誤り: account ID が11桁 / role name にスペルミス
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
 - rolearn: arn:aws:iam::12345678901:role/NodeInstnceRole
username: system:node:{{EC2PrivateDNSName}}
groups:
  - system:bootstrappers
  - system:nodes

正解パターン — account ID を12桁に修正し、role name のスペルを正す:

# 正解: account ID 12桁 / role name を正確に記載
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
 - rolearn: arn:aws:iam::123456789012:role/NodeInstanceRole
username: system:node:{{EC2PrivateDNSName}}
groups:
  - system:bootstrappers
  - system:nodes
 - rolearn: arn:aws:iam::123456789012:role/DeveloperRole
username: developer
groups:
  - system:masters

解説: aws-auth ConfigMap の rolearn フィールドは IAM ロールの完全な ARN を正確に記載する必要がある。ARN の account ID は12桁固定であり、role name は IAM コンソールの表示と完全一致させる。aws iam get-role --role-name NodeInstanceRole で正しい ARN を確認してから記入する。


演習2: IRSA 信頼ポリシーで全 ServiceAccount を許可してしまう

アンチパターン — sub Condition に * を使って全 ServiceAccount を許可している:

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringLike": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:*:*"
  }
}
 }
  ]
}

正解パターン — 特定の namespace と ServiceAccount 名に絞る:

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Principal": {
  "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
  "StringEquals": {
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:aud": "sts.amazonaws.com",
 "oidc.eks.ap-northeast-1.amazonaws.com/id/OIDC_ID:sub": "system:serviceaccount:production:my-app"
  }
}
 }
  ]
}

解説: system:serviceaccount:*:* を許可すると、クラスター内のすべての ServiceAccount が IAM ロールを引き受けられる。最小権限の原則に反し、悪意のある Pod が権限を奪取するリスクがある。StringEquals で namespace と ServiceAccount 名の両方を固定し、正確な1つの ServiceAccount のみに制限する。


演習3: Managed Node Group の scaling_config が最小と最大が逆

アンチパターン — min_sizemax_size より大きい設定でエラーになる:

resource "aws_eks_node_group" "main" {
  cluster_name = aws_eks_cluster.main.name
  node_group_name = "main"
  node_role_arn= aws_iam_role.node.arn
  subnet_ids= var.private_subnet_ids

  scaling_config {
 min_size  = 10# 誤り: min が max より大きい
 max_size  = 3
 desired_size = 5 # 誤り: desired が min-max の範囲外
  }

  instance_types = ["m5.large"]
}

正解パターン — min_sizedesired_sizemax_size の制約を守る:

resource "aws_eks_node_group" "main" {
  cluster_name = aws_eks_cluster.main.name
  node_group_name = "main"
  node_role_arn= aws_iam_role.node.arn
  subnet_ids= var.private_subnet_ids

  scaling_config {
 min_size  = 2 # 正解: min <= desired <= max
 max_size  = 10
 desired_size = 3
  }

  instance_types = ["m5.large"]

  lifecycle {
 ignore_changes = [scaling_config[0].desired_size]  # Autoscaler による変更を無視
  }
}

解説: EKS Managed Node Group の scaling_configmin_sizedesired_sizemax_size の制約がある。Karpenter と組み合わせる場合は lifecycle { ignore_changes = [scaling_config[0].desired_size] } を追加し、Terraform Apply 時に desired_size がリセットされるのを防ぐ。


演習4: ALB Ingress の target-type が instance なのに Pod 直接ルーティングを期待

アンチパターン — target-type: instance に設定してあるが、Pod へ直接リクエストが届くことを期待している:

# 誤り: target-type が instance だと Node 経由のルーティングになる
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: production
  annotations:
 kubernetes.io/ingress.class: alb
 alb.ingress.kubernetes.io/scheme: internet-facing
 alb.ingress.kubernetes.io/target-type: instance # Node Port 経由のルーティング
spec:
  rules:
 - host: api.example.com
http:
  paths:
 - path: /
pathType: Prefix
backend:
  service:
 name: my-service
 port:
number: 8080

正解パターン — target-type: ip で Pod に直接ルーティングする:

# 正解: target-type: ip で Pod 直接ルーティング (レイテンシー低減)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: production
  annotations:
 kubernetes.io/ingress.class: alb
 alb.ingress.kubernetes.io/scheme: internet-facing
 alb.ingress.kubernetes.io/target-type: ip  # Pod 直接ルーティング
 alb.ingress.kubernetes.io/healthcheck-path: /health
 alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
 alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:ACCOUNT_ID:certificate/CERT_ID
spec:
  rules:
 - host: api.example.com
http:
  paths:
 - path: /
pathType: Prefix
backend:
  service:
 name: my-service
 port:
number: 8080

解説: target-type: instance は ALB が Node の NodePort 経由でリクエストをルーティングする。target-type: ip は ALB が Pod の IP に直接ルーティングし、余分なホップが減りレイテンシーが低下する。EKS の Managed Node Group や Fargate では ip モードを推奨する。


演習5: Karpenter NodePool に instance-category 制限がなく高コストインスタンスが起動

アンチパターン — requirements に instance-category 制限がなく、GPU や超大型インスタンスが起動してしまう:

# 誤り: instance-category に制限がない
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
 spec:
requirements:
  - key: kubernetes.io/arch
 operator: In
 values: ["amd64"]
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["spot"]
  # karpenter.k8s.aws/instance-category の制限なし → p4d, u-6tb1 等が起動する可能性
nodeClassRef:
  apiVersion: karpenter.k8s.aws/v1
  kind: EC2NodeClass
  name: default
  limits:
 cpu: 100

正解パターン — instance-category で汎用インスタンスに絞り、コストを制御する:

# 正解: instance-category で c, m, r 系に制限しコスト予測可能にする
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
 spec:
requirements:
  - key: kubernetes.io/arch
 operator: In
 values: ["amd64"]
  - key: kubernetes.io/os
 operator: In
 values: ["linux"]
  - key: karpenter.sh/capacity-type
 operator: In
 values: ["on-demand", "spot"]
  - key: karpenter.k8s.aws/instance-category
 operator: In
 values: ["c", "m", "r"] # GPU・高メモリ特化を除外
  - key: karpenter.k8s.aws/instance-generation
 operator: Gt
 values: ["4"]# 第4世代以降に限定
nodeClassRef:
  apiVersion: karpenter.k8s.aws/v1
  kind: EC2NodeClass
  name: default
  limits:
 cpu: 100
 memory: 400Gi
  disruption:
 consolidationPolicy: WhenEmptyOrUnderutilized
 consolidateAfter: 1m

解説: Karpenter はデフォルトでほぼすべてのインスタンスタイプを選択候補にする。karpenter.k8s.aws/instance-category[c, m, r] に制限することで、GPU インスタンス (p4d, g5 等) や超大型インスタンスの誤起動を防ぐ。instance-generation を4世代以降に限定することで、コスト効率の良い新世代インスタンスを優先させる。


8. まとめ + EKS本番運用 Vol2予告 + 落とし穴10選 + クロスリンク

8-1. 本記事のまとめ

本記事では EKS本番運用 Vol1 として、クラスター設計から IRSA・ALB Ingress まで IAM入門4巻読了者向けに統合解説した。各章の要点を振り返る。

クラスター設計の基本 (§1-2):
– EKS クラスターの2層構造 (Control Plane / Data Plane) と、それぞれの責任範囲を理解した
– Terraform と eksctl それぞれの管理方式のトレードオフを把握した
– VPC 設計 (Public/Private Subnet 分離) とセキュリティグループの基本を整理した

Managed Node Group vs Karpenter の選定 (§3):
– Managed Node Group は設定がシンプルで予測可能。バースト性の低い安定ワークロードに向く
– Karpenter は動的スケーリングとコスト最適化が得意。EC2NodeClass × NodePool の2層で柔軟に設定できる
– 混在構成 (Managed Node Group でベースライン / Karpenter でバースト) が実践的な選択肢になる

IRSA 設計 (§4):
– OIDC Provider → ServiceAccount Annotation → 信頼ポリシー (aud + sub) の3点セットが必須
– IAM入門 Vol4 で学んだ AssumeRoleWithWebIdentityaud/sub Condition の知識が直結する
– Pod 単位の最小権限を実現し、Node の IAM ロールに強い権限を持たせない設計が重要

ALB Ingress Controller 設計 (§5):
– Helm Chart での Controller インストール時に clusterNamevpcIdregion の3つが必須
target-type: ip で Pod への直接ルーティングを実現しレイテンシーを低減する
– HTTPS 終端・ヘルスチェックパス・ACM 証明書 ARN の Annotation 設定を正確に行う

詰まりポイント7選と演習5問 (§6-7):
– 詰まりポイントの共通パターンは「IAM/OIDC 設定の組み合わせ漏れ」「Kubernetes リソースと IAM の不一致」「Helm Chart 必須パラメーター未設定」の3種類に集約される


8-2. EKS本番運用 落とし穴10選

§6 の7選に加え、運用フェーズで遭遇しやすい3つの落とし穴を追加する。

§6 で解説した7選 (再掲):
1. kubeconfig 更新忘れ → aws eks update-kubeconfig を忘れない
2. aws-auth ConfigMap 更新漏れ → mapRoles/mapUsers に追加が必要
3. OIDC Provider 未作成 → IRSA の前提条件として必ず作成
4. ServiceAccount Annotation の namespace 誤り → sub の namespace を正確に一致させる
5. Karpenter NodePool の requirements 設定漏れ → 必須フィールドを明示設定
6. IRSA 信頼ポリシーの aud Condition 設定漏れ → sts.amazonaws.com を必ず指定
7. Helm Chart の values.yaml 設定ミス → clusterName / vpcId / region の3点を確認

運用フェーズで遭遇しやすい追加3選:

落とし穴8: Pod Disruption Budget (PDB) 未設定によるデプロイ時の全 Pod 停止

Node のドレイン (Karpenter consolidation やローリングアップデート) 時に PDB がないと、Deployment の Pod が全台同時に停止する。maxUnavailable: 1 を設定した PodDisruptionBudget を本番 Deployment に必ず追加する。

落とし穴9: Subnet CIDR 枯渇で新しい Node が起動しない

/24 の Subnet では254アドレスしか使えず、Pod 数が増えると CIDR が枯渇して新しい Node も Pod も起動できなくなる。EKS 用の Private Subnet は /19 以上 (8190 アドレス) を推奨する。VPC CNI の prefix delegation も検討する。

落とし穴10: Cluster Autoscaler と Karpenter の共存設定ミスで競合が起きる

同一クラスターで Cluster Autoscaler と Karpenter を両方有効にすると、同じ Node Group を対象にスケーリングが競合する。Karpenter を使う Node Group には Cluster Autoscaler の対象外設定を追加するか、Node Group を明確に分離する。


8-3. EKS本番運用 Vol2: 観測可能性

Vol1 では EKS クラスター設計・IRSA・ALB Ingress の3テーマを統合的に解説した。Vol2 では 観測可能性 (Observability) をテーマに、FluentBit × Container Insights × ADOT の3本柱で「見える状態で動かす」環境を構築する。Vol1 で習得した IRSA がそのまま FluentBit・ADOT の権限設計に活きる。

EKS本番運用 Vol2 公開中

Vol2 では FluentBit DaemonSet / Container Insights / ADOT Collector / X-Ray トレース統合を実践ハンズオンで解説します。詰まりポイント7選・演習5問付き。

EKS本番運用 Vol2: 観測可能性 — FluentBit × Container Insights × ADOT →