Skip to content

Commit 5c418a7

Browse files
authored
🤖 feat: migrate sandbox EKS Terraform to EKS Auto Mode (#39)
## Summary Migrate the Terraform-managed `sandbox-eks` cluster from a managed node group setup to EKS Auto Mode. ## Background The previous Terraform configuration used an EKS managed node group and did not model Auto Mode-managed compute/storage/load balancing behavior. This change aligns the sandbox cluster with EKS Auto Mode and removes now-obsolete node group configuration. ## Implementation - Added EKS Auto Mode IAM policy attachments to the cluster IAM role. - Added a dedicated Auto Mode node IAM role with minimal worker + ECR pull policies. - Enabled EKS Auto Mode in `aws_eks_cluster`: - `compute_config` (`general-purpose` node pool) - `kubernetes_network_config.elastic_load_balancing` - `storage_config.block_storage` - Removed the managed node group resources. - Updated addon dependencies to depend on cluster readiness. - Removed stale node group outputs and variables. - Updated Terraform README docs and estimated-cost guidance for Auto Mode. ## Validation - `make verify-vendor` - `make test` - `make build` - `make lint` - `terraform -chdir=terraform fmt -check -diff` - `terraform -chdir=terraform init -backend=false` - `terraform -chdir=terraform validate` ## Risks - In-place migration may cause workload rescheduling during apply. - Existing node-group-specific automation depending on removed outputs/variables will need to be updated. --- <details> <summary>📋 Implementation Plan</summary> # Plan: Migrate Terraform-managed sandbox EKS to EKS Auto Mode ## Context / Why The current Terraform in `terraform/` provisions a standard EKS cluster with a managed node group and only the core EKS managed add-ons (`coredns`, `kube-proxy`, `vpc-cni`). It does **not** deploy the EBS CSI driver, so dynamic provisioning of EBS-backed PersistentVolumes via PVCs is not guaranteed. Goal: migrate the existing **`sandbox-eks`** cluster to **EKS Auto Mode** so AWS manages: - Compute scaling (managed instances) - Block storage (EBS CSI) - Elastic Load Balancing integration This plan assumes the following (based on your answers): - **In-place migration** (no blue/green cluster) - **PVC/PV data does not need to be preserved** (sandbox) - **No SSH/SSM access to nodes is OK** ## Evidence - Current Terraform cluster + node group + add-ons: `terraform/eks.tf` - IAM roles/policies: `terraform/iam.tf` - AWS provider version pinned: `terraform/.terraform.lock.hcl` (hashicorp/aws **6.31.0**) - AWS provider supports EKS Auto Mode via `aws_eks_cluster.compute_config`, `kubernetes_network_config.elastic_load_balancing`, and `storage_config.block_storage`: hashicorp/aws provider docs for `aws_eks_cluster` (v6.31.0) - AWS EKS Auto Mode overview and managed components: AWS EKS User Guide (`automode.html`) --- ## Migration plan (recommended: staged applies) ### Phase 0 — Pre-flight safety checks 1. **Confirm you can access current cluster** (so you can validate after each apply): - `terraform -chdir=terraform output -raw kubeconfig_command` (then run it) - `kubectl get nodes -o wide` - `kubectl get pods -A` 2. **Capture a quick baseline** (helpful for rollback/debug): - `terraform -chdir=terraform state list` - `terraform -chdir=terraform plan` (save the output) 3. **Communicate expected disruption** - Enabling Auto Mode and later removing the managed node group can cause short-lived scheduling disruption. <details> <summary>Why staged applies?</summary> Auto Mode enablement is a control-plane update plus new IAM requirements. Keeping the managed node group initially provides a safety net while you validate Auto Mode health (and avoids a “no nodes in cluster” scenario if something is misconfigured). </details> --- ### Phase 1 — Enable Auto Mode capabilities (keep existing node group) #### 1) Terraform changes (IAM) Edit `terraform/iam.tf`: 1. **Keep** existing cluster role `aws_iam_role.eks_cluster` but attach the additional AWS-managed policies required for Auto Mode: - `arn:aws:iam::aws:policy/AmazonEKSComputePolicy` - `arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy` - `arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy` - `arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy` 2. **Add** a dedicated node role for Auto Mode managed instances (don’t reuse the managed node group role so it can be deleted later): - Trust principal: `ec2.amazonaws.com` - Attach: - `arn:aws:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy` - `arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly` Shape of the intended additions: ```hcl # terraform/iam.tf resource "aws_iam_role" "auto_mode_node" { name = "${var.cluster_name}-auto-node-role" assume_role_policy = data.aws_iam_policy_document.node_group_assume_role.json tags = { Name = "${var.cluster_name}-auto-node-role" } } resource "aws_iam_role_policy_attachment" "auto_mode_node_worker_minimal" { role = aws_iam_role.auto_mode_node.name policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy" } resource "aws_iam_role_policy_attachment" "auto_mode_node_ecr_pull_only" { role = aws_iam_role.auto_mode_node.name policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly" } resource "aws_iam_role_policy_attachment" "eks_cluster_compute_policy" { role = aws_iam_role.eks_cluster.name policy_arn = "arn:aws:iam::aws:policy/AmazonEKSComputePolicy" } resource "aws_iam_role_policy_attachment" "eks_cluster_block_storage_policy" { role = aws_iam_role.eks_cluster.name policy_arn = "arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy" } resource "aws_iam_role_policy_attachment" "eks_cluster_load_balancing_policy" { role = aws_iam_role.eks_cluster.name policy_arn = "arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy" } resource "aws_iam_role_policy_attachment" "eks_cluster_networking_policy" { role = aws_iam_role.eks_cluster.name policy_arn = "arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy" } ``` #### 2) Terraform changes (EKS cluster Auto Mode config) Edit `terraform/eks.tf` in `aws_eks_cluster.this`: Add **all three** of the following blocks (they must be enabled together): ```hcl resource "aws_eks_cluster" "this" { ... bootstrap_self_managed_addons = false compute_config { enabled = true node_pools = ["general-purpose"] node_role_arn = aws_iam_role.auto_mode_node.arn } kubernetes_network_config { elastic_load_balancing { enabled = true } } storage_config { block_storage { enabled = true } } ... } ``` Also update the cluster `depends_on` to include the new policy attachments so deletion ordering remains correct: ```hcl depends_on = [ aws_iam_role_policy_attachment.eks_cluster_policy, aws_iam_role_policy_attachment.eks_cluster_compute_policy, aws_iam_role_policy_attachment.eks_cluster_block_storage_policy, aws_iam_role_policy_attachment.eks_cluster_load_balancing_policy, aws_iam_role_policy_attachment.eks_cluster_networking_policy, ] ``` #### 3) Apply (Phase 1) Run: - `terraform -chdir=terraform fmt` - `terraform -chdir=terraform init -upgrade -backend-config=backend.hcl` - `terraform -chdir=terraform plan` - `terraform -chdir=terraform apply` #### 4) Validate (Phase 1) 1. Confirm Auto Mode flags are enabled: - `aws eks describe-cluster --region $(terraform -chdir=terraform output -raw region) --name $(terraform -chdir=terraform output -raw cluster_name)` 2. Confirm nodes still exist (node group should still be present): - `kubectl get nodes -o wide` 3. Confirm EBS CSI is available and storage works: - `kubectl get csidrivers` - `kubectl get storageclass` - Create a tiny test PVC + pod and ensure it binds/mounts. --- ### Phase 2 — Retire the managed node group (move fully to Auto Mode) #### 1) Create Auto Mode capacity (if needed) EKS Auto Mode generally provisions nodes based on unschedulable pods. To force a clean cutover: - Cordon + drain one managed node group node and watch for replacement capacity: - `kubectl cordon <node>` - `kubectl drain <node> --ignore-daemonsets --delete-emptydir-data` - `kubectl get nodes -w` If the cluster ends up briefly without schedulable nodes, this is acceptable for sandbox, but you should still watch `kube-system` pods recover. #### 2) Terraform changes (remove node group) Once you’ve confirmed Auto Mode is healthy: 1. Remove `aws_eks_node_group.this` from `terraform/eks.tf`. 2. Remove the node-group IAM role and its attachments from `terraform/iam.tf`: - `aws_iam_role.node_group` - `aws_iam_role_policy_attachment.node_group_*` 3. Update the EKS add-ons’ `depends_on` in `terraform/eks.tf`: - Change from `depends_on = [aws_eks_node_group.this]` to `depends_on = [aws_eks_cluster.this]` (or remove `depends_on` entirely if not needed). 4. Update outputs: - Remove or replace `output "node_group_name"` in `terraform/outputs.tf`. #### 3) Apply (Phase 2) - `terraform -chdir=terraform plan` - `terraform -chdir=terraform apply` #### 4) Validate (Phase 2) - `kubectl get nodes -o wide` (should show only Auto Mode-managed nodes) - `kubectl get pods -A` (all Running/Ready) - Re-run the PVC provisioning test --- ## Cleanup / follow-ups - Update `terraform/README.md` to reflect: - No managed node group - Auto Mode managed components (compute + storage + ELB) - Updated cost estimate (Auto Mode adds an additional management fee) - Optional: remove now-unused variables from `terraform/variables.tf` (`node_*`) after confirming no external automation relies on them. --- ## Rollback strategy (sandbox-friendly) - If Phase 1 fails: revert the Auto Mode blocks and IAM policy attachments, keep the managed node group, and re-apply. - If Phase 2 fails (node group removed and Auto Mode not provisioning): re-introduce `aws_eks_node_group` and apply, or destroy/recreate the sandbox cluster. Because PVC data preservation is not required, the simplest escape hatch is `terraform -chdir=terraform destroy` followed by `apply` once the configuration is corrected. </details> --- _Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.38`_ <!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=0.38 -->
1 parent 7bbbf09 commit 5c418a7

5 files changed

Lines changed: 72 additions & 85 deletions

File tree

terraform/README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ This directory provisions a cost-optimized Amazon EKS sandbox cluster in region
99
- 2 private subnets across the first two standard availability zones in the selected region
1010
- Internet Gateway
1111
- Single NAT Gateway (lower cost than one per AZ)
12-
- IAM roles for EKS control plane and worker nodes
12+
- IAM roles for the EKS control plane and EKS Auto Mode managed instances
1313
- EKS cluster (`sandbox-eks`, Kubernetes `1.35`) with public and private API endpoint access
14-
- One managed node group:
15-
- Instance type: `t3.medium`
16-
- Desired/min/max size: `2/1/3`
17-
- Disk size: `20 GiB`
18-
- AMI type: `AL2023_x86_64_STANDARD`
14+
- EKS Auto Mode enabled for:
15+
- Compute (`general-purpose` node pool)
16+
- Elastic Load Balancing integration
17+
- Block storage integration (EBS CSI managed by EKS)
1918
- EKS managed add-ons: `coredns`, `kube-proxy`, `vpc-cni`
2019
- Remote Terraform state in AWS S3 with lockfile-based locking (no DynamoDB)
2120

@@ -164,11 +163,11 @@ aws eks update-kubeconfig --region eu-central-1 --name sandbox-eks
164163
## Estimated cost (rough)
165164

166165
- EKS control plane: **~$0.10/hour**
167-
- 2x `t3.medium` worker nodes: **~$0.08/hour**
168-
- 1x NAT Gateway: **~$0.045/hour**
169-
- **Total: ~ $0.225/hour (~$5.40/day)**
166+
- EKS Auto Mode: additional per-instance management fee (see current EKS pricing for your region)
167+
- EC2 and EBS usage: varies with actual workload and Auto Mode scaling behavior
168+
- 1x NAT Gateway: **~$0.045/hour** (plus data processing)
170169

171-
> Note: Data transfer and NAT data processing charges are additional.
170+
> Note: Data transfer, NAT data processing, and workload-driven compute/storage usage are additional.
172171
173172
## Cleanup
174173

terraform/eks.tf

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,42 @@ resource "aws_eks_cluster" "this" {
55

66
bootstrap_self_managed_addons = false
77

8-
vpc_config {
9-
subnet_ids = aws_subnet.private[*].id
10-
endpoint_public_access = true
11-
endpoint_private_access = true
8+
access_config {
9+
authentication_mode = "API_AND_CONFIG_MAP"
1210
}
1311

14-
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
15-
}
12+
compute_config {
13+
enabled = true
14+
node_pools = ["general-purpose", "system"]
15+
node_role_arn = aws_iam_role.auto_mode_node.arn
16+
}
1617

17-
resource "aws_eks_node_group" "this" {
18-
cluster_name = aws_eks_cluster.this.name
19-
node_group_name = "${var.cluster_name}-nodes"
20-
node_role_arn = aws_iam_role.node_group.arn
21-
subnet_ids = aws_subnet.private[*].id
18+
kubernetes_network_config {
19+
elastic_load_balancing {
20+
enabled = true
21+
}
22+
}
2223

23-
instance_types = var.node_instance_types
24-
ami_type = "AL2023_x86_64_STANDARD"
25-
disk_size = var.node_disk_size
24+
storage_config {
25+
block_storage {
26+
enabled = true
27+
}
28+
}
2629

27-
scaling_config {
28-
desired_size = var.node_desired_size
29-
min_size = var.node_min_size
30-
max_size = var.node_max_size
30+
vpc_config {
31+
subnet_ids = aws_subnet.private[*].id
32+
endpoint_public_access = true
33+
endpoint_private_access = true
3134
}
3235

3336
depends_on = [
34-
aws_iam_role_policy_attachment.node_group_worker_policy,
35-
aws_iam_role_policy_attachment.node_group_cni_policy,
36-
aws_iam_role_policy_attachment.node_group_ecr_readonly,
37+
aws_iam_role_policy_attachment.auto_mode_node_worker_minimal,
38+
aws_iam_role_policy_attachment.auto_mode_node_ecr_pull_only,
39+
aws_iam_role_policy_attachment.eks_cluster_policy,
40+
aws_iam_role_policy_attachment.eks_cluster_compute_policy,
41+
aws_iam_role_policy_attachment.eks_cluster_block_storage_policy,
42+
aws_iam_role_policy_attachment.eks_cluster_load_balancing_policy,
43+
aws_iam_role_policy_attachment.eks_cluster_networking_policy,
3744
]
3845
}
3946

@@ -43,21 +50,21 @@ resource "aws_eks_addon" "coredns" {
4350
addon_name = "coredns"
4451
resolve_conflicts_on_create = "OVERWRITE"
4552

46-
depends_on = [aws_eks_node_group.this]
53+
depends_on = [aws_eks_cluster.this]
4754
}
4855

4956
resource "aws_eks_addon" "kube_proxy" {
5057
cluster_name = aws_eks_cluster.this.name
5158
addon_name = "kube-proxy"
5259
resolve_conflicts_on_create = "OVERWRITE"
5360

54-
depends_on = [aws_eks_node_group.this]
61+
depends_on = [aws_eks_cluster.this]
5562
}
5663

5764
resource "aws_eks_addon" "vpc_cni" {
5865
cluster_name = aws_eks_cluster.this.name
5966
addon_name = "vpc-cni"
6067
resolve_conflicts_on_create = "OVERWRITE"
6168

62-
depends_on = [aws_eks_node_group.this]
69+
depends_on = [aws_eks_cluster.this]
6370
}

terraform/iam.tf

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
data "aws_iam_policy_document" "eks_cluster_assume_role" {
22
statement {
3-
actions = ["sts:AssumeRole"]
3+
actions = ["sts:AssumeRole", "sts:TagSession"]
44

55
principals {
66
type = "Service"
@@ -23,7 +23,27 @@ resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
2323
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
2424
}
2525

26-
data "aws_iam_policy_document" "node_group_assume_role" {
26+
resource "aws_iam_role_policy_attachment" "eks_cluster_compute_policy" {
27+
role = aws_iam_role.eks_cluster.name
28+
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSComputePolicy"
29+
}
30+
31+
resource "aws_iam_role_policy_attachment" "eks_cluster_block_storage_policy" {
32+
role = aws_iam_role.eks_cluster.name
33+
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy"
34+
}
35+
36+
resource "aws_iam_role_policy_attachment" "eks_cluster_load_balancing_policy" {
37+
role = aws_iam_role.eks_cluster.name
38+
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy"
39+
}
40+
41+
resource "aws_iam_role_policy_attachment" "eks_cluster_networking_policy" {
42+
role = aws_iam_role.eks_cluster.name
43+
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy"
44+
}
45+
46+
data "aws_iam_policy_document" "auto_mode_node_assume_role" {
2747
statement {
2848
actions = ["sts:AssumeRole"]
2949

@@ -34,26 +54,21 @@ data "aws_iam_policy_document" "node_group_assume_role" {
3454
}
3555
}
3656

37-
resource "aws_iam_role" "node_group" {
38-
name = "${var.cluster_name}-node-role"
39-
assume_role_policy = data.aws_iam_policy_document.node_group_assume_role.json
57+
resource "aws_iam_role" "auto_mode_node" {
58+
name = "${var.cluster_name}-auto-node-role"
59+
assume_role_policy = data.aws_iam_policy_document.auto_mode_node_assume_role.json
4060

4161
tags = {
42-
Name = "${var.cluster_name}-node-role"
62+
Name = "${var.cluster_name}-auto-node-role"
4363
}
4464
}
4565

46-
resource "aws_iam_role_policy_attachment" "node_group_worker_policy" {
47-
role = aws_iam_role.node_group.name
48-
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
49-
}
50-
51-
resource "aws_iam_role_policy_attachment" "node_group_cni_policy" {
52-
role = aws_iam_role.node_group.name
53-
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
66+
resource "aws_iam_role_policy_attachment" "auto_mode_node_worker_minimal" {
67+
role = aws_iam_role.auto_mode_node.name
68+
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy"
5469
}
5570

56-
resource "aws_iam_role_policy_attachment" "node_group_ecr_readonly" {
57-
role = aws_iam_role.node_group.name
58-
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
71+
resource "aws_iam_role_policy_attachment" "auto_mode_node_ecr_pull_only" {
72+
role = aws_iam_role.auto_mode_node.name
73+
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly"
5974
}

terraform/outputs.tf

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ output "cluster_security_group_id" {
1919
value = aws_eks_cluster.this.vpc_config[0].cluster_security_group_id
2020
}
2121

22-
output "node_group_name" {
23-
description = "Name of the managed EKS node group."
24-
value = aws_eks_node_group.this.node_group_name
25-
}
26-
2722
output "region" {
2823
description = "AWS region where the cluster is deployed."
2924
value = var.aws_region

terraform/variables.tf

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,32 +35,3 @@ variable "vpc_cidr" {
3535
}
3636
}
3737

38-
variable "node_instance_types" {
39-
description = "EC2 instance types for the managed node group."
40-
type = list(string)
41-
default = ["t3.medium"]
42-
}
43-
44-
variable "node_desired_size" {
45-
description = "Desired number of worker nodes in the managed node group."
46-
type = number
47-
default = 2
48-
}
49-
50-
variable "node_min_size" {
51-
description = "Minimum number of worker nodes in the managed node group."
52-
type = number
53-
default = 1
54-
}
55-
56-
variable "node_max_size" {
57-
description = "Maximum number of worker nodes in the managed node group."
58-
type = number
59-
default = 3
60-
}
61-
62-
variable "node_disk_size" {
63-
description = "Disk size in GiB for each node in the managed node group."
64-
type = number
65-
default = 20
66-
}

0 commit comments

Comments
 (0)