Skip to content

Commit 1657664

Browse files
Benclaude
andauthored
feat: add EKS Auto Mode support (#259)
* feat: add EKS Auto Mode support Add compute_config, storage_config, and elastic_load_balancing object variables with enabled flags. Adds dynamic blocks to aws_eks_cluster, Auto Mode IAM policies (Compute, BlockStorage, LoadBalancing, Networking), sts:TagSession trust policy, and auto_mode_enabled output. Bumps AWS provider to >= 5.79.0. All defaults preserve current behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace coalesce with ternary for bootstrap_self_managed_addons coalesce treats false as empty, causing an error when both Auto Mode is disabled and the user hasn't set bootstrap_self_managed_addons_enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: filter auto mode node role from linux access entries When EKS Auto Mode is enabled, AWS automatically creates an access entry for the node role specified in compute_config. Attempting to create it again via aws_eks_access_entry.linux causes a 409 ResourceInUseException. Filter out the compute_config.node_role_arn from the linux access entries when auto mode is enabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * revert: remove submodule-level access entry filtering The ARN-based filtering caused "count depends on resource attributes" errors because the node_role_arn isn't known at plan time when the IAM role is being created in the same apply. The fix is handled at the component level instead — the component simply does not pass the auto mode node role to access_entries_for_nodes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: rename Auto Mode variables with auto_mode_ prefix Rename compute_config -> auto_mode_compute_config, storage_config -> auto_mode_storage_config, elastic_load_balancing -> auto_mode_elastic_load_balancing for clarity. Also add EKS Auto Mode section to README. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add EKS Capabilities support (Argo CD, ACK, KRO) - Add `capabilities` map variable for independently-enableable managed platform features - Create capabilities.tf with aws_eks_capability resources and auto-created IAM roles per capability - Add capabilities and capability_role_arns outputs - Bump AWS provider to >= 6.25.0 for aws_eks_capability resource - Support ARGOCD configuration (IDC, RBAC, network access) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove .terraform.lock.hcl from repo * feat: update examples/complete with Auto Mode support Add auto_mode_enabled variable, Auto Mode node IAM role, and pass auto_mode_compute_config/storage_config/elastic_load_balancing to the module. Disable node group when Auto Mode is enabled. Incorporates example patterns from PR #253 using our variable naming. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use static key sets for capabilities for_each to fix plan-time error OpenTofu/Terraform requires for_each keys to be known at plan time. Changed from map-based for_each to toset of keys derived from var.capabilities, ensuring keys are always static. Resource attributes now reference var.capabilities[each.value] instead of each.value.X. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add create_iam_role field to capabilities for plan-time stability The for_each on capability IAM resources was failing because role_arn == null is unknown at plan time when the calling module passes a resource ARN. Added create_iam_role boolean (default true) that callers set to false when they provide their own roles, ensuring for_each keys are always deterministic at plan time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: make aws_idc required for Argo CD capability configuration The AWS provider requires the aws_idc block to always be present when configuring an Argo CD capability. Changed from dynamic block (optional) to static block (required) and updated the variable type accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: make aws_idc optional for Argo CD -- skip argo_cd block when absent The AWS provider requires aws_idc when argo_cd configuration is rendered, but users may not have an IDC instance set up initially. Changed aws_idc back to optional and only render the argo_cd configuration block when aws_idc is provided. The capability is still created, just without the argo_cd configuration block (can be configured later). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove unused enabled_capabilities local TFLint flagged enabled_capabilities as unused after switching to key-based sets for for_each. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: require aws_idc for ARGOCD capabilities, skip empty config block The AWS API requires configuration.argo_cd.aws_idc for ARGOCD capabilities. Previously, when aws_idc was null, the argo_cd block was skipped but the configuration block still rendered empty, causing an API error. Now: - Skip entire configuration block when aws_idc is not provided - Add validation to give a clear error if aws_idc is missing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: remove .terraform.lock.hcl from version control Lock files should not be committed in reusable modules as they constrain consumers' provider versions unnecessarily. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add EKS Auto Mode section to README.yaml Port the EKS Auto Mode documentation from README.md back to README.yaml so it persists through readme generation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use aws_partition for policy ARNs in examples, rename capabilities to auto mode managed add-ons - Add data.aws_partition.current to examples/complete for GovCloud/China partition support instead of hardcoded "arn:aws:" prefixes - Rename "Capabilities" section to "Auto Mode Managed Add-ons" in docs to avoid confusion with EKS Capabilities (Argo CD, ACK, KRO) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * update test * -> local.enabled --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2049cc8 commit 1657664

11 files changed

Lines changed: 538 additions & 9 deletions

File tree

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,71 @@ Module usage with two unmanaged worker groups:
327327
> you're using. This practice ensures the stability of your infrastructure. Additionally, we recommend implementing a systematic
328328
> approach for updating versions to avoid unexpected changes.
329329
330+
## EKS Auto Mode
330331

332+
This module supports [EKS Auto Mode](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) (GA December 2024),
333+
which delegates compute, networking, and storage management to AWS. Enable it using the `auto_mode_compute_config`,
334+
`auto_mode_storage_config`, and `auto_mode_elastic_load_balancing` variables.
335+
336+
### Enabling Auto Mode
337+
338+
```hcl
339+
module "eks_cluster" {
340+
source = "cloudposse/eks-cluster/aws"
341+
# version = "..."
342+
343+
auto_mode_compute_config = {
344+
enabled = true
345+
node_pools = ["general-purpose", "system"]
346+
node_role_arn = aws_iam_role.auto_mode_node.arn
347+
}
348+
349+
auto_mode_storage_config = {
350+
block_storage = {
351+
enabled = true
352+
}
353+
}
354+
355+
auto_mode_elastic_load_balancing = {
356+
enabled = true
357+
}
358+
359+
# ... other configuration
360+
}
361+
```
362+
363+
When Auto Mode is enabled, this module automatically:
364+
- Sets `bootstrap_self_managed_addons = false` (unless explicitly overridden)
365+
- Adds `sts:TagSession` to the cluster IAM role trust policy
366+
- Attaches 4 additional IAM policies to the cluster role: `AmazonEKSComputePolicy`, `AmazonEKSBlockStoragePolicy`,
367+
`AmazonEKSLoadBalancingPolicy`, and `AmazonEKSNetworkingPolicy`
368+
369+
### Auto Mode Managed Add-ons
370+
371+
When Auto Mode is enabled, AWS manages the following add-ons automatically:
372+
373+
| Add-on | Variable | What AWS Manages |
374+
|--------|----------|-----------------|
375+
| **Compute** | `auto_mode_compute_config` | Node provisioning via managed Karpenter |
376+
| **Storage** | `auto_mode_storage_config` | EBS volumes via `ebs.csi.eks.amazonaws.com` |
377+
| **Networking** | `auto_mode_elastic_load_balancing` | ALB/NLB for Services and Ingress |
378+
379+
### Important Notes
380+
381+
- Requires AWS provider `>= 5.79.0` and Kubernetes `>= 1.29`
382+
- Auto Mode manages `vpc-cni`, `kube-proxy`, `coredns`, and `aws-ebs-csi-driver` add-ons automatically.
383+
Do not include these in the `addons` variable when Auto Mode is enabled.
384+
- Auto Mode nodes are Bottlerocket-only, immutable, with no SSH/IMDS access
385+
- Nodes have a 21-day maximum lifetime and are automatically rotated
386+
- The `node_role_arn` in `auto_mode_compute_config` must be an IAM role with
387+
`AmazonEKSWorkerNodeMinimalPolicy` and `AmazonEC2ContainerRegistryPullOnly` attached
388+
389+
### Cluster Version Upgrades
390+
391+
With Auto Mode, Kubernetes version upgrades are simplified:
392+
1. Bump `kubernetes_version` and apply -- control plane upgrades in place
393+
2. Managed Karpenter detects version drift and automatically replaces nodes
394+
3. Auto Mode-managed add-ons are automatically upgraded to compatible versions
331395

332396

333397

README.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,5 +328,71 @@ usage: |-
328328
> many issues you may read about that had affected prior versions. See the version 2 README and release notes
329329
> for more information on the challenges and workarounds that were required prior to v3.
330330
331+
## EKS Auto Mode
332+
333+
This module supports [EKS Auto Mode](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) (GA December 2024),
334+
which delegates compute, networking, and storage management to AWS. Enable it using the `auto_mode_compute_config`,
335+
`auto_mode_storage_config`, and `auto_mode_elastic_load_balancing` variables.
336+
337+
### Enabling Auto Mode
338+
339+
```hcl
340+
module "eks_cluster" {
341+
source = "cloudposse/eks-cluster/aws"
342+
# version = "..."
343+
344+
auto_mode_compute_config = {
345+
enabled = true
346+
node_pools = ["general-purpose", "system"]
347+
node_role_arn = aws_iam_role.auto_mode_node.arn
348+
}
349+
350+
auto_mode_storage_config = {
351+
block_storage = {
352+
enabled = true
353+
}
354+
}
355+
356+
auto_mode_elastic_load_balancing = {
357+
enabled = true
358+
}
359+
360+
# ... other configuration
361+
}
362+
```
363+
364+
When Auto Mode is enabled, this module automatically:
365+
- Sets `bootstrap_self_managed_addons = false` (unless explicitly overridden)
366+
- Adds `sts:TagSession` to the cluster IAM role trust policy
367+
- Attaches 4 additional IAM policies to the cluster role: `AmazonEKSComputePolicy`, `AmazonEKSBlockStoragePolicy`,
368+
`AmazonEKSLoadBalancingPolicy`, and `AmazonEKSNetworkingPolicy`
369+
370+
### Auto Mode Managed Add-ons
371+
372+
When Auto Mode is enabled, AWS manages the following add-ons automatically:
373+
374+
| Add-on | Variable | What AWS Manages |
375+
|--------|----------|-----------------|
376+
| **Compute** | `auto_mode_compute_config` | Node provisioning via managed Karpenter |
377+
| **Storage** | `auto_mode_storage_config` | EBS volumes via `ebs.csi.eks.amazonaws.com` |
378+
| **Networking** | `auto_mode_elastic_load_balancing` | ALB/NLB for Services and Ingress |
379+
380+
### Important Notes
381+
382+
- Requires AWS provider `>= 5.79.0` and Kubernetes `>= 1.29`
383+
- Auto Mode manages `vpc-cni`, `kube-proxy`, `coredns`, and `aws-ebs-csi-driver` add-ons automatically.
384+
Do not include these in the `addons` variable when Auto Mode is enabled.
385+
- Auto Mode nodes are Bottlerocket-only, immutable, with no SSH/IMDS access
386+
- Nodes have a 21-day maximum lifetime and are automatically rotated
387+
- The `node_role_arn` in `auto_mode_compute_config` must be an IAM role with
388+
`AmazonEKSWorkerNodeMinimalPolicy` and `AmazonEC2ContainerRegistryPullOnly` attached
389+
390+
### Cluster Version Upgrades
391+
392+
With Auto Mode, Kubernetes version upgrades are simplified:
393+
1. Bump `kubernetes_version` and apply -- control plane upgrades in place
394+
2. Managed Karpenter detects version drift and automatically replaces nodes
395+
3. Auto Mode-managed add-ons are automatically upgraded to compatible versions
396+
331397
include: []
332398
contributors: []

capabilities.tf

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# EKS Capabilities: Argo CD, ACK, KRO
2+
# https://docs.aws.amazon.com/eks/latest/userguide/capabilities.html
3+
4+
locals {
5+
# Use toset of keys to ensure for_each keys are always known at plan time.
6+
# The map keys come from var.capabilities which is a static configuration.
7+
enabled_capability_keys = toset([
8+
for k, v in var.capabilities : k if local.enabled && v.enabled
9+
])
10+
11+
# Keys of capabilities that need auto-created IAM roles.
12+
# Uses create_iam_role (a static bool) instead of role_arn == null
13+
# to ensure for_each keys are always known at plan time.
14+
capability_keys_needing_roles = toset([
15+
for k, v in var.capabilities : k if local.enabled && v.enabled && v.create_iam_role
16+
])
17+
18+
# Final role ARN map: auto-created or user-provided
19+
capability_role_arns = {
20+
for k in local.enabled_capability_keys : k => (
21+
var.capabilities[k].create_iam_role ? aws_iam_role.capability[k].arn : var.capabilities[k].role_arn
22+
)
23+
}
24+
}
25+
26+
# IAM roles for capabilities that don't provide their own
27+
module "capability_label" {
28+
for_each = local.capability_keys_needing_roles
29+
30+
source = "cloudposse/label/null"
31+
version = "0.25.0"
32+
33+
attributes = ["capability", each.key]
34+
context = module.this.context
35+
}
36+
37+
data "aws_iam_policy_document" "capability_assume_role" {
38+
count = length(local.capability_keys_needing_roles) > 0 ? 1 : 0
39+
40+
statement {
41+
effect = "Allow"
42+
actions = ["sts:AssumeRole", "sts:TagSession"]
43+
44+
principals {
45+
type = "Service"
46+
identifiers = ["capabilities.eks.amazonaws.com"]
47+
}
48+
}
49+
}
50+
51+
resource "aws_iam_role" "capability" {
52+
for_each = local.capability_keys_needing_roles
53+
54+
name = module.capability_label[each.key].id
55+
assume_role_policy = one(data.aws_iam_policy_document.capability_assume_role[*].json)
56+
tags = module.capability_label[each.key].tags
57+
permissions_boundary = var.permissions_boundary
58+
}
59+
60+
resource "aws_eks_capability" "default" {
61+
for_each = local.enabled_capability_keys
62+
63+
cluster_name = local.eks_cluster_id
64+
capability_name = each.value
65+
type = var.capabilities[each.value].type
66+
role_arn = local.capability_role_arns[each.value]
67+
delete_propagation_policy = var.capabilities[each.value].delete_propagation_policy
68+
tags = module.label.tags
69+
70+
dynamic "configuration" {
71+
# The AWS API requires configuration with argo_cd and aws_idc for ARGOCD capabilities.
72+
# Skip the entire configuration block if aws_idc is not provided -- the capability
73+
# cannot be created without it. Provide aws_idc in your stack config to enable.
74+
for_each = (
75+
var.capabilities[each.value].type == "ARGOCD" &&
76+
var.capabilities[each.value].configuration != null &&
77+
try(var.capabilities[each.value].configuration.argo_cd.aws_idc, null) != null
78+
) ? [var.capabilities[each.value].configuration] : []
79+
content {
80+
dynamic "argo_cd" {
81+
for_each = configuration.value.argo_cd != null ? [configuration.value.argo_cd] : []
82+
content {
83+
namespace = argo_cd.value.namespace
84+
85+
aws_idc {
86+
idc_instance_arn = argo_cd.value.aws_idc.idc_instance_arn
87+
idc_region = argo_cd.value.aws_idc.idc_region
88+
}
89+
90+
dynamic "network_access" {
91+
for_each = argo_cd.value.network_access != null ? [argo_cd.value.network_access] : []
92+
content {
93+
vpce_ids = network_access.value.vpce_ids
94+
}
95+
}
96+
97+
dynamic "rbac_role_mapping" {
98+
for_each = argo_cd.value.rbac_role_mapping
99+
content {
100+
role = rbac_role_mapping.value.role
101+
102+
dynamic "identity" {
103+
for_each = rbac_role_mapping.value.identity
104+
content {
105+
id = identity.value.id
106+
type = identity.value.type
107+
}
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}
114+
}
115+
116+
timeouts {
117+
create = var.capabilities[each.value].create_timeout
118+
update = var.capabilities[each.value].update_timeout
119+
delete = var.capabilities[each.value].delete_timeout
120+
}
121+
122+
depends_on = [
123+
aws_eks_cluster.default,
124+
aws_iam_role.capability,
125+
]
126+
}

examples/complete/main.tf

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ provider "aws" {
22
region = var.region
33
}
44

5+
data "aws_partition" "current" {}
6+
57
module "label" {
68
source = "cloudposse/label/null"
79
version = "0.25.0"
@@ -111,11 +113,28 @@ module "eks_cluster" {
111113
cluster_encryption_config_resources = var.cluster_encryption_config_resources
112114

113115
addons = local.addons
114-
addons_depends_on = [module.eks_node_group]
116+
addons_depends_on = var.auto_mode_enabled ? null : [module.eks_node_group]
115117
bootstrap_self_managed_addons_enabled = var.bootstrap_self_managed_addons_enabled
116118
upgrade_policy = var.upgrade_policy
117119
zonal_shift_config = var.zonal_shift_config
118120

121+
# EKS Auto Mode
122+
auto_mode_compute_config = {
123+
enabled = var.auto_mode_enabled
124+
node_pools = var.auto_mode_enabled ? ["general-purpose", "system"] : []
125+
node_role_arn = var.auto_mode_enabled ? one(aws_iam_role.auto_mode_node[*].arn) : null
126+
}
127+
128+
auto_mode_storage_config = {
129+
block_storage = {
130+
enabled = var.auto_mode_enabled
131+
}
132+
}
133+
134+
auto_mode_elastic_load_balancing = {
135+
enabled = var.auto_mode_enabled
136+
}
137+
119138
access_entry_map = local.access_entry_map
120139
access_config = {
121140
authentication_mode = "API"
@@ -136,10 +155,49 @@ module "eks_cluster" {
136155
cluster_depends_on = [module.subnets]
137156
}
138157

158+
# Auto Mode node role (only when auto_mode_enabled = true)
159+
data "aws_iam_policy_document" "auto_mode_node_assume_role" {
160+
count = local.enabled && var.auto_mode_enabled ? 1 : 0
161+
162+
statement {
163+
effect = "Allow"
164+
actions = ["sts:AssumeRole"]
165+
166+
principals {
167+
type = "Service"
168+
identifiers = ["ec2.amazonaws.com"]
169+
}
170+
}
171+
}
172+
173+
resource "aws_iam_role" "auto_mode_node" {
174+
count = local.enabled && var.auto_mode_enabled ? 1 : 0
175+
176+
name = "${module.label.id}-auto-mode-node"
177+
assume_role_policy = one(data.aws_iam_policy_document.auto_mode_node_assume_role[*].json)
178+
tags = module.label.tags
179+
}
180+
181+
resource "aws_iam_role_policy_attachment" "auto_mode_node_minimal" {
182+
count = local.enabled && var.auto_mode_enabled ? 1 : 0
183+
184+
role = one(aws_iam_role.auto_mode_node[*].name)
185+
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy"
186+
}
187+
188+
resource "aws_iam_role_policy_attachment" "auto_mode_node_ecr" {
189+
count = local.enabled && var.auto_mode_enabled ? 1 : 0
190+
191+
role = one(aws_iam_role.auto_mode_node[*].name)
192+
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly"
193+
}
194+
139195
module "eks_node_group" {
140196
source = "cloudposse/eks-node-group/aws"
141197
version = "3.2.0"
142198

199+
enabled = local.enabled && !var.auto_mode_enabled
200+
143201
# node group <= 3.2 requires a non-empty list of subnet_ids, even when disabled
144202
subnet_ids = local.enabled ? module.subnets.public_subnet_ids : ["filler_string_for_enabled_is_false"]
145203
cluster_name = module.eks_cluster.eks_cluster_id

examples/complete/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ variable "private_ipv6_enabled" {
142142
description = "Whether to use IPv6 addresses for the pods in the node group"
143143
}
144144

145+
variable "auto_mode_enabled" {
146+
type = bool
147+
default = false
148+
description = "Set to true to enable EKS Auto Mode"
149+
}
150+
145151
variable "remote_network_config" {
146152
description = "Configuration block for the cluster remote network configuration"
147153
type = object({

examples/complete/vpc-cni.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ resource "aws_iam_role_policy_attachment" "vpc_cni" {
4242
count = local.vpc_cni_sa_needed ? 1 : 0
4343

4444
role = module.vpc_cni_eks_iam_role.service_account_role_name
45-
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
45+
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
4646
}
4747

4848
module "vpc_cni_eks_iam_role" {

0 commit comments

Comments
 (0)