TerraformAWS

AWS EKS Cluster

Managed Kubernetes cluster with managed node groups, OIDC provider for IRSA, and CoreDNS.

kubernetesekscontainersiam

Prerequisites

  • VPC with private subnets (use aws-vpc template)
  • Terraform >= 1.5.0
  • AWS provider >= 5.0
  • kubectl installed locally

Template Code

# ─────────────────────────────────────────────────────────────────────────────
# AWS EKS Cluster with Managed Node Groups
# ─────────────────────────────────────────────────────────────────────────────

variable "cluster_name"    { default = "production-cluster" }
variable "cluster_version" { default = "1.29" }
variable "region"          { default = "us-east-1" }

# Assumes VPC/subnets created separately (e.g. via aws-vpc template)
variable "private_subnet_ids" {
  type = list(string)
}

# ── IAM Role for EKS Control Plane ──────────────────────────────────────────
resource "aws_iam_role" "eks_cluster" {
  name = "${var.cluster_name}-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 Cluster ───────────────────────────────────────────────────────────────
resource "aws_eks_cluster" "main" {
  name     = var.cluster_name
  version  = var.cluster_version
  role_arn = aws_iam_role.eks_cluster.arn

  vpc_config {
    subnet_ids              = var.private_subnet_ids
    endpoint_private_access = true
    endpoint_public_access  = true  # Set to false for private-only clusters
  }

  # Enable logging
  enabled_cluster_log_types = ["api", "audit", "authenticator"]

  depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}

# ── OIDC Provider (enables IRSA — IAM Roles for Service Accounts) ─────────────
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 Node Group ───────────────────────────────────────────────────
resource "aws_iam_role" "node_group" {
  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" "node_group_policies" {
  for_each = toset([
    "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
    "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy",
    "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
  ])
  policy_arn = each.value
  role       = aws_iam_role.node_group.name
}

# ── Managed Node Group ────────────────────────────────────────────────────────
resource "aws_eks_node_group" "main" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "main"
  node_role_arn   = aws_iam_role.node_group.arn
  subnet_ids      = var.private_subnet_ids

  instance_types = ["t3.medium"]

  scaling_config {
    desired_size = 2
    min_size     = 1
    max_size     = 5
  }

  update_config {
    max_unavailable = 1
  }

  depends_on = [aws_iam_role_policy_attachment.node_group_policies]
}

# ── Outputs ───────────────────────────────────────────────────────────────────
output "cluster_name"      { value = aws_eks_cluster.main.name }
output "cluster_endpoint"  { value = aws_eks_cluster.main.endpoint }
output "oidc_issuer_url"   { value = aws_eks_cluster.main.identity[0].oidc[0].issuer }

Usage

terraform init
terraform apply -var="cluster_version=1.29"
# Update kubeconfig:
aws eks update-kubeconfig --name production-cluster --region us-east-1