TerraformでEKSクラスターを構築する方法|managed node groupとPod Identityの実践設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでEKSクラスターを構築する方法|managed node groupとPod Identityの実践設計
「Terraformで書いたEKSクラスターが起動するまで10分近くかかって不安になる」
「aws_eks_node_groupの設定がわからず、とりあえずコピペしたが何が正しいのか理解していない」

EKSをTerraformで管理しようとすると、クラスター本体・IAMロール・node group・add-onと、設定すべきリソースが多く、どこから手を付ければいいか迷うエンジニアが多い。

この記事では、EKSクラスターをTerraformでゼロから構築する実践手順を解説します。managed node groupの最小構成から、IRSAの後継であるPod Identityの設定方法、EKS managed add-onのバージョン管理、managed node groupとFargateプロファイルの設計判断まで、設計軸で体系的に身につけられます。

実行環境:Terraform 1.8.x / AWS provider 5.x(EKS 1.30、ap-northeast-1で動作確認済み)

この記事のポイント

・aws_eks_clusterとnode groupリソースで最小構成EKSを作れる
・Pod IdentityはIRSAの後継でOIDC設定が不要になる
・EKS add-onはaws_eks_addonでバージョン固定できる
・managed node group vs Fargate profileの設計判断基準も解説


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜEKSをTerraformで管理するのか

EKSクラスターをAWSコンソールから作成すると、IAMロール・VPC設定・セキュリティグループ・node groupと、多数の設定を手作業で組み合わせることになります。設定内容が暗黙知になり、「なぜこのセキュリティグループが付いているのか」「本番と開発で何が違うのか」が後から追えなくなります。

TerraformでEKSを管理する利点は次の3点です。

構成の再現性:HCLに書かれたリソース定義がそのまま設計書になる。dev環境とprod環境でVPCサイズやnode group数だけ違う構成を、変数1つで切り替えられる
変更の安全性:terraform planで変更差分を事前確認してからapplyする。コンソール操作では「消すつもりのないリソースを誤って変更した」という事故が起きやすい
GitOpsへの接続:HCLをGitで管理することで、GitHub ActionsのCI/CDパイプラインにterraform planを組み込み、PRベースのインフラレビューが可能になる

実務では「コンソールで作ってみたが、terraform importで後からTerraform管理に移した」というケースも多い。これについてはterraform importで既存AWSリソースをコード化する方法で詳しく解説しています。

EKSクラスターのHCL基本構成

EKSをTerraformで構築するために最低限必要なリソースは次の4種類です。

aws_eks_cluster:EKSクラスター本体(コントロールプレーン)
aws_iam_role(クラスター用):EKSコントロールプレーンがAWSサービスにアクセスするためのロール
aws_eks_node_group:ワーカーノードのmanaged node group
aws_iam_role(ノード用):ワーカーノードのEC2インスタンスに付与するロール

1. EKSクラスターリソースの定義

まずクラスター本体のIAMロールを作成し、EKSに引き受けさせます。

# クラスター用IAMロール 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" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.eks_cluster.name } # EKSクラスター本体 resource "aws_eks_cluster" "main" { name = var.cluster_name version = "1.30" role_arn = aws_iam_role.eks_cluster.arn vpc_config { subnet_ids = var.private_subnet_ids endpoint_private_access = true endpoint_public_access = false } access_config { authentication_mode = "API_AND_CONFIG_MAP" } depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy] }

【注意】endpoint_public_access = false を設定すると、EKS APIエンドポイントへのアクセスはVPC内からのみに限定されます。踏み台サーバーやAWS Systems Manager Session Managerを経由しなければkubectlが通らなくなるため、設定前にVPC内からkubectlを実行できる踏み台環境が整っていることを確認してください。本番環境ではこの設定を推奨します。

access_config.authentication_mode = "API_AND_CONFIG_MAP" はEKS 1.23以降で推奨される設定です。以前のaws-auth ConfigMapによる認証に加え、EKS Access Entries(APIベースのアクセス制御)が使えるようになります。

2. managed node groupの設定

ノード用のIAMロールにはEC2ワーカーノードに必要な3つのマネージドポリシーを付与します。

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 } resource "aws_eks_node_group" "main" { cluster_name = aws_eks_cluster.main.name node_group_name = "main-ng" node_role_arn = aws_iam_role.eks_node.arn subnet_ids = var.private_subnet_ids # ami_type: AL2023_x86_64_STANDARD (x86_64) or AL2023_ARM_64_STANDARD (Graviton) ami_type = "AL2023_x86_64_STANDARD" instance_types = ["t3.medium"] scaling_config { desired_size = 2 max_size = 5 min_size = 1 } update_config { max_unavailable = 1 } depends_on = [ aws_iam_role_policy_attachment.eks_node_worker, aws_iam_role_policy_attachment.eks_node_cni, aws_iam_role_policy_attachment.eks_node_ecr, ] }

ami_type = "AL2023_x86_64_STANDARD" はAmazon Linux 2023ベースのEKS最適化AMIを指定します。AL2(Amazon Linux 2)は2025年6月にサポート終了しているため、新規構築では必ずAL2023を選んでください。Graviton3(arm64)を使う場合は AL2023_ARM_64_STANDARD を指定します。

update_config.max_unavailable = 1 はローリングアップデート時に最大1台ずつノードを入れ替えることを意味します。本番でノード数が少ない場合は max_unavailable_percentage で割合指定するほうが安全です。

terraform applyを実行した際の実際の出力です。EKSコントロールプレーンの作成に約9分、その後node groupに約3分かかるのが通常の待機時間です。

aws_iam_role.eks_cluster: Creating... aws_iam_role.eks_cluster: Creation complete after 1s [id=my-eks-cluster-role] aws_iam_role_policy_attachment.eks_cluster_policy: Creation complete after 0s aws_eks_cluster.main: Creating... aws_eks_cluster.main: Still creating... [1m0s elapsed] aws_eks_cluster.main: Still creating... [9m0s elapsed] aws_eks_cluster.main: Creation complete after 9m23s [id=my-eks-cluster] aws_eks_node_group.main: Creating... aws_eks_node_group.main: Still creating... [1m0s elapsed] aws_eks_node_group.main: Creation complete after 2m47s [id=my-eks-cluster:main-ng] Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

apply完了後にkubeconfigを更新してノードの状態を確認します。

$ aws eks update-kubeconfig --name my-eks-cluster --region ap-northeast-1 Updated context arn:aws:eks:ap-northeast-1:123456789012:cluster/my-eks-cluster in /home/ec2-user/.kube/config $ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-1-45.ap-northeast-1.compute.internal Ready 87s v1.30.2-eks-1552ad0 ip-10-0-2-112.ap-northeast-1.compute.internal Ready 91s v1.30.2-eks-1552ad0

2台のノードがSTATUS: Readyになれば、managed node groupの構築は完了です。

Pod IdentityでPodにIAMロールを付与する

EKSでPodにAWSリソース(S3・DynamoDB等)へのアクセス権を与えるには、IAMロールをKubernetesのサービスアカウントに紐付けます。これまでの標準手法はIRSA(IAM Roles for Service Accounts)でしたが、2023年11月にGA(一般公開)されたPod Identityを使うほうが設定が大幅にシンプルになります。

1. IRSAとPod Identityの設計上の違い

観点 IRSA(旧方式) Pod Identity(新方式)
OIDC設定 aws_iam_openid_connect_providerが必要 不要(EKS側で自動処理)
IAMロールのTrust Policy OIDCエンドポイントのURL埋め込みが必要 Service: pods.eks.amazonaws.comを指定
追加のアドオン なし EKS Pod Identity Agentアドオンが必要
対応EKSバージョン 1.13以降 1.24以降
Terraform側の複雑さ OIDC ProviderのARN取得処理が煩雑 aws_eks_pod_identity_associationで完結
IRSAはOIDCプロバイダーのURL(クラスターごとに異なる)をIAMロールのTrust Policyにハードコードする必要がありました。EKS 1.24以降の新規構築では、設定がシンプルなPod Identityを採用することを推奨します。

2. Pod IdentityのHCL設定

Pod Identityを使うには、EKS Pod Identity AgentアドオンとIAMロール・アソシエーションの3つを定義します。

# Step 1: Pod Identity Agentアドオンを有効化 resource "aws_eks_addon" "pod_identity_agent" { cluster_name = aws_eks_cluster.main.name addon_name = "eks-pod-identity-agent" depends_on = [aws_eks_node_group.main] } # Step 2: PodのIAMロール(pods.eks.amazonaws.comが引き受ける) resource "aws_iam_role" "s3_reader" { name = "${var.cluster_name}-s3-reader" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Service = "pods.eks.amazonaws.com" } Action = ["sts:AssumeRole", "sts:TagSession"] }] }) } resource "aws_iam_role_policy_attachment" "s3_reader" { policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" role = aws_iam_role.s3_reader.name } # Step 3: サービスアカウントとIAMロールの紐付け resource "aws_eks_pod_identity_association" "s3_access" { cluster_name = aws_eks_cluster.main.name namespace = "default" service_account = "s3-reader-sa" role_arn = aws_iam_role.s3_reader.arn depends_on = [aws_eks_addon.pod_identity_agent] }

アソシエーションを作成したら、Kubernetes側でサービスアカウントを作成してPodに割り当てるだけです。IRSAのように eks.amazonaws.com/role-arn アノテーションを手動でサービスアカウントに付ける必要はなく、Pod Identityエージェントが自動的に認証情報を注入します。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformによるEKSクラスター設計・Pod Identity設定・add-on管理まで、実務で使えるIaCスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

EKS managed add-onをTerraformでバージョン管理する

EKS managed add-onはkube-proxy・CoreDNS・vpc-cniなどKubernetesの動作に必要なコンポーネントをAWSが管理してくれる機能です。Terraformで定義しておくことで、add-onのバージョンもコード管理できます。

1. 基本3アドオンの定義(vpc-cni・CoreDNS・kube-proxy)

resource "aws_eks_addon" "vpc_cni" { cluster_name = aws_eks_cluster.main.name addon_name = "vpc-cni" addon_version = "v1.18.3-eksbuild.2" depends_on = [aws_eks_node_group.main] } resource "aws_eks_addon" "coredns" { cluster_name = aws_eks_cluster.main.name addon_name = "coredns" addon_version = "v1.11.3-eksbuild.1" depends_on = [aws_eks_node_group.main] } resource "aws_eks_addon" "kube_proxy" { cluster_name = aws_eks_cluster.main.name addon_name = "kube-proxy" addon_version = "v1.30.3-eksbuild.5" depends_on = [aws_eks_node_group.main] }

addon_version を省略するとデフォルトバージョンが自動選択されますが、バージョンが変わる度にterraform planに差分が出る原因になります。バージョンを明示指定して変更管理の主導権を持つことを推奨します。

最新の addon_version は次のコマンドで確認できます。

$ aws eks describe-addon-versions \ --kubernetes-version 1.30 \ --addon-name vpc-cni \ --query 'addons[].addonVersions[].addonVersion' \ --output text \ --region ap-northeast-1 v1.18.3-eksbuild.2 v1.18.2-eksbuild.1 v1.18.1-eksbuild.3

2. EBS CSI Driverアドオンの追加

EBSボリューム(PersistentVolumeClaim)をPodで使うにはEBS CSI Driverが必要です。このアドオンはIAMロールが必要なため、Pod Identityで連携させます。

# EBS CSI Driver用のIAMロール resource "aws_iam_role" "ebs_csi_driver" { name = "${var.cluster_name}-ebs-csi-driver" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Service = "pods.eks.amazonaws.com" } Action = ["sts:AssumeRole", "sts:TagSession"] }] }) } resource "aws_iam_role_policy_attachment" "ebs_csi_driver" { policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" role = aws_iam_role.ebs_csi_driver.name } resource "aws_eks_addon" "ebs_csi_driver" { cluster_name = aws_eks_cluster.main.name addon_name = "aws-ebs-csi-driver" addon_version = "v1.35.0-eksbuild.1" service_account_role_arn = aws_iam_role.ebs_csi_driver.arn depends_on = [aws_eks_node_group.main] }

service_account_role_arn を指定することで、EBS CSI DriverのPodに対してIAMロールを紐付けます。IRSAとPod Identityのどちらでも指定できますが、EKS 1.24以降ではPod Identityの利用を推奨します。

managed node group vs Fargateプロファイルの設計判断

EKSのワーカーノード方式としてmanaged node groupとFargateプロファイルの2種類があります。どちらを選ぶかはワークロードの特性によって異なります。
観点 managed node group Fargateプロファイル
コスト EC2料金(ノードを常時起動) Pod単位の秒課金
スケーリング速度 Cluster Autoscalerが必要(2分前後) Podごとに即座に起動(数秒)
DaemonSet 利用可能 利用不可
EBSボリューム 利用可能 利用不可(EFSのみ)
GPUワークロード 利用可能(GPU対応AMI) 利用不可
向き先 常時稼働サービス・ステートフルアプリ バッチ・イベント駆動の短命なワークロード
実務では「常時稼働のWebサービスはmanaged node group、バッチ処理はFargate」のハイブリッド構成が多い。DaemonSetを使うFluentBit(ログ収集)やDatadog AgentはFargate上では動かないため、ログ収集をノードレベルで行う場合はmanaged node groupが必須です。

コスト最適化を優先するケースでは、managed node groupにSpotインスタンスを組み合わせる方法も有効です。その場合は capacity_type = "SPOT" を指定し、複数のインスタンスタイプを指定してスポット枯渇リスクを分散させます。

トラブルシュート:よくあるエラーと対処

1. 「InvalidParameterException: No subnets found」エラー

EKSクラスターのサブネットにはKubernetesがロードバランサーをプロビジョニングできるようタグが必要です。PublicサブネットにはALBを作るために kubernetes.io/role/elb = 1、PrivateサブネットにはInternal ALB用に kubernetes.io/role/internal-elb = 1 タグを付けてください。

# VPCモジュールを使っている場合の例 module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.13.0" private_subnet_tags = { "kubernetes.io/cluster/${var.cluster_name}" = "shared" "kubernetes.io/role/internal-elb" = "1" } public_subnet_tags = { "kubernetes.io/cluster/${var.cluster_name}" = "shared" "kubernetes.io/role/elb" = "1" } }

2. terraform planで「node groupの変更」が毎回出る

ami_type を指定していてもEKSがAMIリリースを新しくした場合、Terraformがドリフトを検知してnode groupの変更差分を出し続けることがあります。これは正常な動作ですが、予期しないnode groupのローリングアップデートを防ぐには lifecycle.ignore_changes でAMI関連フィールドを無視する設定を検討してください。

resource "aws_eks_node_group" "main" { # ... 省略 ... lifecycle { ignore_changes = [ # AMIが更新されても自動でnode groupを変更しない ami_type, release_version, ] } }

3. 「AccessDenied: Not authorized to perform sts:AssumeRole」

Pod Identityを使ったIAMロールの引き受けに失敗するケースのほとんどは、IAMロールのTrust PolicyのPrincipalが pods.eks.amazonaws.com ではなく ec2.amazonaws.com になっているミスです。Pod Identity用のTrust PolicyはService: pods.eks.amazonaws.comを必ず使ってください。IRSAのTrust PolicyコピーからService名を直し忘れると発生します。

本記事のまとめ

やりたいこと Terraformリソース
EKSクラスター本体の作成 aws_eks_cluster
ワーカーノードの追加 aws_eks_node_group
PodへのIAMロール紐付け(新方式) aws_eks_pod_identity_association
vpc-cni / CoreDNS / kube-proxy管理 aws_eks_addon
kubeconfigの更新 aws eks update-kubeconfig
add-onバージョン確認 aws eks describe-addon-versions
次に読む記事
TerraformでAWS VPCとEC2を構築する方法|HCL記法とterraformコマンドの実践
Terraformのmoduleとfor_each・countで構成を再利用する設計パターン
Terraformのtfstate管理とS3バックエンド設定|チーム運用で壊さないための基礎
Terraformのsensitive変数とシークレット管理|SSMパラメータストア参照で秘密情報を安全に扱う設計
Terraformのdepends_onと依存関係設計|暗黙依存とterraform graphでリソース作成順序を制御する方法
EKSクラスター設計からPod Identity・add-on管理まで、Terraformで本番インフラをコード管理するスキルを体系的に習得したい方へ。実機ハンズオン形式のセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。