目次
21 章

EKS クラスタセットアップ

AWS EKS の上に本物の運用クラスタを最初から立ち上げる流れを扱います。Terraform で VPC · EKS コントロールプレーン · ノードグループ · IRSA · 必須アドオン (VPC CNI · CoreDNS · kube-proxy · EBS CSI) を一つのコードベースで宣言し、eksctl の素早いセットアップオプション、Karpenter のノードオートスケーリング、そして最初の点検 · コストモデルまでを一つのサイクルで整理します。

4部 (EKS 実戦) の最初の章です。1 ~ 3部の20章がマニフェスト一枚のモデルからポリシーエンジンと可観測性まで K8s をオブジェクトの次元でたどってきた道だったとすれば、4部の6章はその上に 本物のサービスを一つ載せて運用する流れ です。仮想のバックエンドサービス myshop-api を EKS に載せ、RDS とつなぎ、CI/CD で配備し、モニタリング · 運用段階までを一つのまとまりとしてたどります。本章はその流れの出発点 — EKS クラスタを最初から立ち上げる段階 です。Terraform で VPC とクラスタを宣言し、ノードグループと IRSA をセットアップし、必須アドオンまで載せる流れを整理します。

本章の終わりでは 空の EKS クラスタ1台 + IRSA / OIDC provider / 必須アドオン / kubeconfig が準備された状態 が手に入ります。4部の残り5章が、この空のクラスタの上に少しずつ一つのサービスを載せていく流れです。

myshop-api — 4部を貫く仮想サービス #

4部をまとめるシナリオを一行で押さえておきます。仮想の会社 myshop が自社のバックエンド API サービス myshop-api を EKS に載せると仮定します。仕様は単純です。

  • Python (FastAPI) または Go で書いた小さな REST API
  • RDS PostgreSQL をデータストアとして使用
  • HTTPS で外部公開
  • prod / dev の二環境
  • トラフィックの変動に応じて Pod 数を自動調整

このシナリオを、第21章のクラスタから第26章の運用サイクルまで一貫してたどります。各章が次の章の入力になる構造で、最後の第26章までたどってきた時点であれば、実際の運用クラスタの全体像が手に入ります。

セットアップツールの選択 — Terraform vs eksctl #

EKS クラスタを作る選択肢はいくつもあります。

ツールモデル適した場所
AWS コンソールクリック学習 / 一度だけ見る
eksctlYAML 一枚 + CLIPoC / 素早いセットアップ / 学習
TerraformHCL で宣言運用クラスタ / マルチ環境 / IaC 標準
AWS CDK / Pulumiコード (TypeScript、Python など) で宣言コードに馴染んだチーム / 複雑な分岐

運用クラスタの事実上の標準は Terraform です。VPC · IAM · EKS コントロールプレーン · ノードグループ · アドオン · RDS · Route53 までを一つのコードベースで宣言して git に保管できるので、クラスタ自体がコードで再現可能になります。同じマニフェストが dev / prod の二環境に同一に適用され、変更は PR レビューを経て入ります。第20章 GitOps のパターンが K8s マニフェストだけでなくクラスタ自体のインフラにもそのまま拡張されます。

eksctl がその位置を置き換えるには抽象化の質感が EKS にあまりに限定的ですが、素早いセットアップと学習 には最も直感的です。本章では Terraform をメインに扱い、eksctl を比較オプションとして短く触れます。

Terraform プロジェクト構造 #

myshop-api インフラの Terraform コード構造から固めます。

terraform/ ディレクトリ構造
terraform/
├── modules/
│   ├── network/         # VPC, サブネット, NAT, ルーティング
│   ├── eks/             # EKS クラスタ + ノードグループ
│   └── addons/          # VPC CNI, EBS CSI, IRSA roles
├── envs/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── ...
└── versions.tf

modules/ に再利用可能な単位を置き、envs/ で dev / prod を別々にインスタンス化する構造です。dev / prod の違いはインスタンスタイプ、ノード数、マルチ AZ 程度で、モジュール自体は共有します。第7章 Namespace とラベル の環境別マニフェスト分岐が K8s マニフェストの質感だったとすれば、本章の envs/ 構造はインフラの質感です。

Provider と backend #

envs/prod/main.tf — provider と state backend
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {
    bucket         = "myshop-tfstate"
    key            = "eks/prod/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "myshop-tfstate-lock"
    encrypt        = true
  }
}

provider "aws" {
  region = "ap-northeast-2"

  default_tags {
    tags = {
      Project     = "myshop"
      Environment = "prod"
      ManagedBy   = "terraform"
    }
  }
}

state ファイルを S3 に、ロックを DynamoDB に置く標準パターンです。default_tags がこの provider で作ったすべてのリソースに自動でタグを付けてくれるので、第28章 コスト最適化 の Cost Allocation Tags ベースのコスト追跡が自然に回ります。

VPC — EKS の土台 #

EKS は自分の VPC を作りません。ユーザーが作った VPC の上にコントロールプレーンの ENI を挿す構造です。したがってクラスタを作る前に VPC とサブネットから決めなければなりません。

VPC モジュール — terraform-aws-modules/vpc/aws #

VPC を最初から直接定義せず、コミュニティモジュールを使うのが標準です。

modules/network/main.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "${var.project}-${var.env}"
  cidr = "10.10.0.0/16"

  azs              = ["ap-northeast-2a", "ap-northeast-2c"]
  private_subnets  = ["10.10.1.0/24",  "10.10.2.0/24"]
  public_subnets   = ["10.10.101.0/24", "10.10.102.0/24"]
  database_subnets = ["10.10.201.0/24", "10.10.202.0/24"]

  enable_nat_gateway     = true
  single_nat_gateway     = var.env == "dev"
  one_nat_gateway_per_az = var.env == "prod"

  enable_dns_hostnames = true
  enable_dns_support   = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = "1"
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = "1"
  }
}

EKS とつながるときの核心は最後の二つのタグです。

  • kubernetes.io/role/elb = 1 — public サブネットに付け、AWS Load Balancer Controller が外部用 LB をこのサブネットに作ります。
  • kubernetes.io/role/internal-elb = 1 — private サブネットに付け、内部用 LB (クラスタ内部 + VPC 内) をこちらに作ります。

このタグがないと LB がどのサブネットに入るか分からず、第22章 アプリ配備の骨格 で作る Ingress が動作しません。

single NAT vs NAT per AZ #

single_nat_gateway は dev でだけ有効にします。NAT Gateway 一つのコストが少なくない (時間当たり + データ転送量当たり) ので dev 環境では一つにまとめ、prod は AZ ごとに一つずつ置いて、一つの AZ が落ちても別の AZ のワークロードが NAT を失わないようにします。

EKS モジュール — コントロールプレーン + ノードグループ #

VPC が準備できたら EKS クラスタ自体を定義します。

modules/eks/main.tf
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "${var.project}-${var.env}"
  cluster_version = "1.32"

  vpc_id     = var.vpc_id
  subnet_ids = var.private_subnet_ids

  cluster_endpoint_public_access  = true
  cluster_endpoint_private_access = true

  enable_irsa = true

  cluster_addons = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    vpc-cni = {
      most_recent = true
    }
    aws-ebs-csi-driver = {
      most_recent              = true
      service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
    }
  }

  eks_managed_node_groups = {
    general = {
      desired_size = var.env == "prod" ? 3 : 2
      min_size     = 2
      max_size     = 10

      instance_types = ["t3.medium"]
      capacity_type  = var.env == "prod" ? "ON_DEMAND" : "SPOT"

      labels = {
        role = "general"
      }
    }
  }
}

この一つのモジュールが次のすべてを作り出します。

  • EKS コントロールプレーン (マネージド K8s 1.32)
  • クラスタの IAM Role
  • OIDC provider (IRSA の基盤 — enable_irsa = true)
  • Managed Node Group (EC2 インスタンスでワーカーノードを生成)
  • 標準アドオン4種 (VPC CNI, CoreDNS, kube-proxy, EBS CSI Driver)

第16章 RBAC / ServiceAccount 深掘り の IRSA 節で扱った OIDC provider がここで自動で有効化され、以降 第23章 DB 連携 で ServiceAccount を IAM Role と連携させるとき、その OIDC が信頼の基盤になります。

Managed Node Group という標準 #

EKS のワーカーノードは三つの方向で構成できます。

種類モデル
Managed Node GroupEKS が EC2 ノードのライフサイクルを管理。最も標準的な道
Self-managed Node Groupユーザーが直接 EC2 グループを運用。高度なカスタマイズ時
Fargateサーバーレス。ノード自体を気にしません。コスト · 制約あり

Managed Node Group が最初の導入の標準です。インスタンスタイプ · サイズ · capacity_type (ON_DEMAND / SPOT) をマニフェストで定義すれば、EKS がノードの参加 · 削除 · アップグレードを自動で管理します。ノード自体の OS は Amazon Linux 2 または Bottlerocket で、kubelet · containerd · CNI エージェントがあらかじめインストールされた AMI を受け取ります。

クラスタ endpoint の二つのモード #

cluster_endpoint_public_accesscluster_endpoint_private_access の二つのフラグの組み合わせが、クラスタ API サーバーへのアクセスを制御します。

publicprivate意味
truefalseインターネットから誰でもアクセス (RBAC だけがセキュリティ境界)
truetrueインターネット + VPC 内部の両方 (最も一般的な設定)
falsetrueVPC 内部からのみ。最も厳格。bastion か VPN が必要

prod 環境のセキュリティガイドは通常は最後 (private only) ですが、GitHub Actions から直接 kubectl を呼ぶには public が有効になっている必要があります。一般的には public + IP 制限 (cluster_endpoint_public_access_cidrs) を一緒にかける折衷がよく使われます。第24章 CI / CD パイプライン では、この endpoint モードを GitHub Actions と ArgoCD のどちらが呼ぶかに応じて分けて決める流れを扱います。

IRSA — アドオンへの IAM Role 付与 #

EBS CSI Driver、AWS Load Balancer Controller、External Secrets のようなアドオンはクラスタの中で AWS API を呼びます。この呼び出しに IRSA で権限を付与するパターンを押さえます。第16章 で見たモデルが本格的な Terraform セットアップへつながる段階です。

modules/eks/irsa-ebs-csi.tf
module "ebs_csi_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.0"

  role_name = "${var.project}-${var.env}-ebs-csi"

  attach_ebs_csi_policy = true

  oidc_providers = {
    main = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
    }
  }
}

このモジュールが次を自動で作ります。

  • IAM Role (名前が myshop-prod-ebs-csi)
  • ポリシー (EBS ディスクの生成 · 削除 · attach 権限)
  • Trust policy (第16章 のその trust policy — kube-system ネームスペースの ebs-csi-controller-sa ServiceAccount だけがこの Role を取得できるよう制限)

この IAM Role の ARN が上の cluster_addons.aws-ebs-csi-driver.service_account_role_arn に渡され、EKS が EBS CSI アドオンの ServiceAccount に自動で annotation を付けてくれます。結果として 第9章 PV / PVC / StorageClass で扱った PVC を作ると EBS ボリュームが自動でプロビジョニングされる流れが動作します。

同じパターンが 第23章 の External Secrets にも、第25章 モニタリング · アラート の CloudWatch 側にも適用されます。ワークロードごとに ServiceAccount + IAM Role の 1:1 マッピング が K8s 実戦の標準セキュリティ構造です。

kubeconfig — クラスタへアクセス #

Terraform でクラスタを作ったら、ローカルで kubectl がそのクラスタを呼べるように kubeconfig を取得しなければなりません。第2章 ローカル環境 で見た kubeconfig モデルがそのまま EKS に適用されます。

kubeconfig 更新
aws eks update-kubeconfig \
  --region ap-northeast-2 \
  --name myshop-prod
確認
kubectl get nodes
kubectl get pods -A
期待される出力
NAME                                            STATUS   ROLES    AGE   VERSION
ip-10-10-1-145.ap-northeast-2.compute.internal  Ready    <none>   3m    v1.32.0
ip-10-10-2-201.ap-northeast-2.compute.internal  Ready    <none>   3m    v1.32.0
ip-10-10-2-83.ap-northeast-2.compute.internal   Ready    <none>   3m    v1.32.0

三つのノードが立ち上がっていて、kube-system ネームスペースのシステム Pod (coredns, kube-proxy, aws-node, ebs-csi-controller) がすべて Running 状態であればクラスタは正常です。

アクセス権限 — aws-auth ConfigMap の役割 #

EKS クラスタを作った IAM ユーザー / Role が自動で system:masters 権限を持ちます。別の IAM ユーザーにアクセス権限を与えるには aws-auth ConfigMap を修正するか、1.23+ の新しいモデルである EKS Access Entries を使います。Access Entries の方が標準的で、Terraform モジュールでも推奨されます。第14章 RBAC / NetworkPolicy / ResourceQuota の RBAC 標準 ClusterRole (view / edit / admin) が EKS Access Entries と自然につながります。

EKS Access Entries — Terraform
access_entries = {
  developers = {
    principal_arn = "arn:aws:iam::123456789012:role/Developer"

    policy_associations = {
      view = {
        policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
        access_scope = {
          type = "cluster"
        }
      }
    }
  }
}

IAM Role 一つをクラスタ全体の view 権限と連携させるマニフェストです。K8s RBAC の view ClusterRole が自動でマッピングされ、この Role を持つユーザーはクラスタのすべてのオブジェクトを読めるようになります。

eksctl — 素早いセットアップの道 #

学習や PoC で素早いクラスタが必要なとき、eksctl は一行で終わります。

cluster.yaml — eksctl マニフェスト
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: myshop-dev
  region: ap-northeast-2
  version: "1.32"

vpc:
  nat:
    gateway: Single

managedNodeGroups:
  - name: general
    instanceType: t3.medium
    desiredCapacity: 2
    minSize: 2
    maxSize: 5
    spot: true

addons:
  - name: vpc-cni
  - name: coredns
  - name: kube-proxy
  - name: aws-ebs-csi-driver

iam:
  withOIDC: true
クラスタ生成
eksctl create cluster -f cluster.yaml

この一つのコマンドで VPC · EKS · ノードグループ · OIDC provider · 基本アドオンまで自動で作られます。15 ~ 20分ほどかかり、終わると kubeconfig も自動で更新されます。

eksctl の質感を整理すると次のとおりです。

  • 長所 — 学習曲線が最も低く、一つのマニフェストでクラスタ一台をまるごと作ります。
  • 短所 — マルチ環境 / RDS / Route53 のような周辺リソースと一つのまとまりとして管理するのが難しいです。CloudFormation を内部的に使うので Terraform と state が分離されます。

運用クラスタの目標はほぼ常に Terraform ですが、最初の学習や一回限りの PoC には eksctl が最も速いです。

Karpenter — ノードオートスケーリングの新しい道 #

Managed Node Group は自前のオートスケーリング (Cluster Autoscaler) が可能ですが、インスタンスタイプがあらかじめ決められたいくつかに制限されます。トラフィックの変動が大きく、ワークロードのリソース要求が多様な環境では Karpenter が新しい標準として定着しつつあります。

第13章 オートスケーリング §「Karpenter — EKS のより速い代替」で触れたモデルが、ここで本格的なマニフェストへつながります。Pending 状態の Pod を見て、その Pod のリソース要求に合うインスタンスタイプをリアルタイムで選び、新しいノードを立ち上げます。 決められたインスタンスプールではなく、AWS のすべての EC2 タイプの中から最も適したものを選択します。

Karpenter NodePool — ノード自動プロビジョニングポリシー
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", "on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["t3.medium", "t3.large", "m5.large", "m5.xlarge"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: 100
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized

Karpenter の導入は第21章の出発点では負担が大きいので、Managed Node Group で始めてトラフィックパターンが定着したら Karpenter へ移行する流れが自然です。第26章 運用チェックリスト第28章 コスト最適化 で改めて触れます。

クラスタセットアップ後の最初の点検 #

クラスタが立ち上がった直後に一度ずつ回しておくとよい点検コマンドです。

バージョンとノードのヘルス
kubectl version --short
kubectl get nodes -o wide
システム Pod
kubectl get pods -n kube-system
OIDC provider 確認 (IRSA が有効化されたか)
aws eks describe-cluster \
  --name myshop-prod \
  --region ap-northeast-2 \
  --query "cluster.identity.oidc.issuer" \
  --output text
EKS アドオンの状態
aws eks list-addons --cluster-name myshop-prod --region ap-northeast-2
aws eks describe-addon --cluster-name myshop-prod \
  --addon-name vpc-cni --region ap-northeast-2

この四つのコマンドで、クラスタ · ノード · システム Pod · OIDC · アドオンがすべて正常かが確認されます。異常が見えたら次の章に進む前に押さえておくのが安全です。

コストの第一印象 #

EKS クラスタのコストは大きく三つです。

項目コスト (ap-northeast-2 基準)
EKS コントロールプレーン時間当たり $0.10 (≈ 月 $73)
EC2 ノード (t3.medium 3台)月 約 $80 (ON_DEMAND) / $25 (SPOT)
NAT Gateway月 約 $35 + データ転送
EBS / Load Balancer / データ転送使用量に応じて

最も小さい prod クラスタの開始コストが月 $200 ~ $300 程度です。dev 環境で SPOT インスタンス + Single NAT を使えばその半分以下に下がります。コストは 第28章 コスト最適化 で本格的に扱いますが、クラスタを立ち上げる時点から認識して始めるのがよいです。

練習問題 #

  1. ご自身が学習用に使える AWS アカウントに本章の Terraform コードを適用して、dev EKS クラスタ一台を立ち上げてみます。aws eks update-kubeconfig で kubeconfig を更新したあと kubectl get nodes -o widekubectl get pods -n kube-system の出力を記録します。§「クラスタセットアップ後の最初の点検」の四つのコマンドを順に回し、OIDC provider URL · アドオンの状態 · システム Pod 9個以上がすべて正常かを点検します。
  2. cluster_endpoint_public_access / cluster_endpoint_private_access の三つの組み合わせ (public-only / public + private / private-only) のトレードオフを、ご自身の運用シナリオに照らして一段落で比較します。第24章 CI / CD パイプライン で GitHub Actions が直接 kubectl を呼ぶのか、それとも ArgoCD が git だけを watch する pull モデルで行くのかが、この endpoint の決定とどう連動するかをメモします。
  3. 本章の EBS CSI IRSA モジュールを参考に、AWS Load Balancer Controller と External Secrets Operator の IRSA Terraform マニフェストの骨格を書いてみます。各 ServiceAccount のネームスペース · 名前 · 必要な IAM ポリシーが何かを 第16章 の trust policy モデルで整理し、「namespace + SA name の両方を明示」原則がなぜ隔離に重要かを一段落で説明します。

一行まとめ: EKS 運用クラスタのセットアップは、Terraform で VPC · EKS コントロールプレーン · ノードグループ · IRSA · 標準アドオン (VPC CNI · CoreDNS · kube-proxy · EBS CSI) を一つのコードベースで宣言する流れ。eksctl は素早い学習 · PoC の道、Karpenter はトラフィックパターンが定着したあと Managed Node Group を置き換えるノードオートスケーリングの新しい標準。クラスタ自体のコストはコントロールプレーン + ノード + NAT の三つが主軸で、prod の開始コストは月 $200 ~ $300 水準。

次の章 #

この時点でクラスタは空の状態です — ノードは立ち上がっていてシステム Pod は生きていますが、私たちが載せようとしている myshop-api はまだ一行もマニフェストに入っていません。次の章ではその空いた場所を埋めます。

第22章 アプリ配備の骨格 では、第4章 Deployment / 第5章 Service / 第10章 Ingress / 第6章 ConfigMap · Secret の五つのオブジェクトを一つのまとまりに整理し、Helm チャートで束ねて環境別の配備までつなげる流れを扱います。AWS Load Balancer Controller と cert-manager の本格的なセットアップもこの章で続きます。

X