Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
53dbe60
feat: add EKS Auto Mode support
Benbentwo Mar 25, 2026
61b3dea
fix: replace coalesce with ternary for bootstrap_self_managed_addons
Benbentwo Mar 25, 2026
edcf6a7
fix: filter auto mode node role from linux access entries
Benbentwo Mar 25, 2026
7f4d7b3
revert: remove submodule-level access entry filtering
Benbentwo Mar 25, 2026
eda8a0a
refactor: rename Auto Mode variables with auto_mode_ prefix
Benbentwo Mar 26, 2026
f8651ab
feat: add EKS Capabilities support (Argo CD, ACK, KRO)
Benbentwo Mar 26, 2026
8518b52
chore: remove .terraform.lock.hcl from repo
Benbentwo Mar 26, 2026
7a3c39d
feat: update examples/complete with Auto Mode support
Benbentwo Mar 26, 2026
985b221
fix: use static key sets for capabilities for_each to fix plan-time e…
Benbentwo Mar 26, 2026
9e59751
fix: add create_iam_role field to capabilities for plan-time stability
Benbentwo Mar 26, 2026
70ba794
fix: make aws_idc required for Argo CD capability configuration
Benbentwo Mar 26, 2026
abbc924
fix: make aws_idc optional for Argo CD -- skip argo_cd block when absent
Benbentwo Mar 26, 2026
8df2a9b
fix: remove unused enabled_capabilities local
Benbentwo Mar 26, 2026
4c111a5
fix: require aws_idc for ARGOCD capabilities, skip empty config block
Benbentwo Mar 26, 2026
a2e337d
chore: remove .terraform.lock.hcl from version control
Benbentwo Mar 27, 2026
a35c839
docs: add EKS Auto Mode section to README.yaml
Benbentwo Mar 27, 2026
d922e7c
fix: use aws_partition for policy ARNs in examples, rename capabiliti…
Benbentwo Mar 27, 2026
aeb880d
update test
Benbentwo Mar 27, 2026
a07061d
-> local.enabled
Benbentwo Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,71 @@ Module usage with two unmanaged worker groups:
> you're using. This practice ensures the stability of your infrastructure. Additionally, we recommend implementing a systematic
> approach for updating versions to avoid unexpected changes.

## EKS Auto Mode

This module supports [EKS Auto Mode](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) (GA December 2024),
which delegates compute, networking, and storage management to AWS. Enable it using the `auto_mode_compute_config`,
`auto_mode_storage_config`, and `auto_mode_elastic_load_balancing` variables.

### Enabling Auto Mode

```hcl
module "eks_cluster" {
source = "cloudposse/eks-cluster/aws"
# version = "..."

auto_mode_compute_config = {
enabled = true
node_pools = ["general-purpose", "system"]
node_role_arn = aws_iam_role.auto_mode_node.arn
}

auto_mode_storage_config = {
block_storage = {
enabled = true
}
}

auto_mode_elastic_load_balancing = {
enabled = true
}

# ... other configuration
}
```

When Auto Mode is enabled, this module automatically:
- Sets `bootstrap_self_managed_addons = false` (unless explicitly overridden)
- Adds `sts:TagSession` to the cluster IAM role trust policy
- Attaches 4 additional IAM policies to the cluster role: `AmazonEKSComputePolicy`, `AmazonEKSBlockStoragePolicy`,
`AmazonEKSLoadBalancingPolicy`, and `AmazonEKSNetworkingPolicy`

### Auto Mode Managed Add-ons

When Auto Mode is enabled, AWS manages the following add-ons automatically:

| Add-on | Variable | What AWS Manages |
|--------|----------|-----------------|
| **Compute** | `auto_mode_compute_config` | Node provisioning via managed Karpenter |
| **Storage** | `auto_mode_storage_config` | EBS volumes via `ebs.csi.eks.amazonaws.com` |
| **Networking** | `auto_mode_elastic_load_balancing` | ALB/NLB for Services and Ingress |

### Important Notes

- Requires AWS provider `>= 5.79.0` and Kubernetes `>= 1.29`
- Auto Mode manages `vpc-cni`, `kube-proxy`, `coredns`, and `aws-ebs-csi-driver` add-ons automatically.
Do not include these in the `addons` variable when Auto Mode is enabled.
- Auto Mode nodes are Bottlerocket-only, immutable, with no SSH/IMDS access
- Nodes have a 21-day maximum lifetime and are automatically rotated
- The `node_role_arn` in `auto_mode_compute_config` must be an IAM role with
`AmazonEKSWorkerNodeMinimalPolicy` and `AmazonEC2ContainerRegistryPullOnly` attached

### Cluster Version Upgrades

With Auto Mode, Kubernetes version upgrades are simplified:
1. Bump `kubernetes_version` and apply -- control plane upgrades in place
2. Managed Karpenter detects version drift and automatically replaces nodes
3. Auto Mode-managed add-ons are automatically upgraded to compatible versions



Expand Down
66 changes: 66 additions & 0 deletions README.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -328,5 +328,71 @@ usage: |-
> many issues you may read about that had affected prior versions. See the version 2 README and release notes
> for more information on the challenges and workarounds that were required prior to v3.

## EKS Auto Mode

This module supports [EKS Auto Mode](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) (GA December 2024),
which delegates compute, networking, and storage management to AWS. Enable it using the `auto_mode_compute_config`,
`auto_mode_storage_config`, and `auto_mode_elastic_load_balancing` variables.

### Enabling Auto Mode

```hcl
module "eks_cluster" {
source = "cloudposse/eks-cluster/aws"
# version = "..."

auto_mode_compute_config = {
enabled = true
node_pools = ["general-purpose", "system"]
node_role_arn = aws_iam_role.auto_mode_node.arn
}

auto_mode_storage_config = {
block_storage = {
enabled = true
}
}

auto_mode_elastic_load_balancing = {
enabled = true
}

# ... other configuration
}
```

When Auto Mode is enabled, this module automatically:
- Sets `bootstrap_self_managed_addons = false` (unless explicitly overridden)
- Adds `sts:TagSession` to the cluster IAM role trust policy
- Attaches 4 additional IAM policies to the cluster role: `AmazonEKSComputePolicy`, `AmazonEKSBlockStoragePolicy`,
`AmazonEKSLoadBalancingPolicy`, and `AmazonEKSNetworkingPolicy`

### Auto Mode Managed Add-ons

When Auto Mode is enabled, AWS manages the following add-ons automatically:

| Add-on | Variable | What AWS Manages |
|--------|----------|-----------------|
| **Compute** | `auto_mode_compute_config` | Node provisioning via managed Karpenter |
| **Storage** | `auto_mode_storage_config` | EBS volumes via `ebs.csi.eks.amazonaws.com` |
| **Networking** | `auto_mode_elastic_load_balancing` | ALB/NLB for Services and Ingress |

### Important Notes

- Requires AWS provider `>= 5.79.0` and Kubernetes `>= 1.29`
- Auto Mode manages `vpc-cni`, `kube-proxy`, `coredns`, and `aws-ebs-csi-driver` add-ons automatically.
Do not include these in the `addons` variable when Auto Mode is enabled.
- Auto Mode nodes are Bottlerocket-only, immutable, with no SSH/IMDS access
- Nodes have a 21-day maximum lifetime and are automatically rotated
- The `node_role_arn` in `auto_mode_compute_config` must be an IAM role with
`AmazonEKSWorkerNodeMinimalPolicy` and `AmazonEC2ContainerRegistryPullOnly` attached

### Cluster Version Upgrades

With Auto Mode, Kubernetes version upgrades are simplified:
1. Bump `kubernetes_version` and apply -- control plane upgrades in place
2. Managed Karpenter detects version drift and automatically replaces nodes
3. Auto Mode-managed add-ons are automatically upgraded to compatible versions

include: []
contributors: []
126 changes: 126 additions & 0 deletions capabilities.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# EKS Capabilities: Argo CD, ACK, KRO
# https://docs.aws.amazon.com/eks/latest/userguide/capabilities.html

locals {
# Use toset of keys to ensure for_each keys are always known at plan time.
# The map keys come from var.capabilities which is a static configuration.
enabled_capability_keys = toset([
for k, v in var.capabilities : k if local.enabled && v.enabled
])

# Keys of capabilities that need auto-created IAM roles.
# Uses create_iam_role (a static bool) instead of role_arn == null
# to ensure for_each keys are always known at plan time.
capability_keys_needing_roles = toset([
for k, v in var.capabilities : k if local.enabled && v.enabled && v.create_iam_role
])

# Final role ARN map: auto-created or user-provided
capability_role_arns = {
for k in local.enabled_capability_keys : k => (
var.capabilities[k].create_iam_role ? aws_iam_role.capability[k].arn : var.capabilities[k].role_arn
)
}
}

# IAM roles for capabilities that don't provide their own
module "capability_label" {
for_each = local.capability_keys_needing_roles

source = "cloudposse/label/null"
version = "0.25.0"

attributes = ["capability", each.key]
context = module.this.context
}

data "aws_iam_policy_document" "capability_assume_role" {
count = length(local.capability_keys_needing_roles) > 0 ? 1 : 0

statement {
effect = "Allow"
actions = ["sts:AssumeRole", "sts:TagSession"]

principals {
type = "Service"
identifiers = ["capabilities.eks.amazonaws.com"]
}
}
}

resource "aws_iam_role" "capability" {
for_each = local.capability_keys_needing_roles

name = module.capability_label[each.key].id
assume_role_policy = one(data.aws_iam_policy_document.capability_assume_role[*].json)
tags = module.capability_label[each.key].tags
permissions_boundary = var.permissions_boundary
}

resource "aws_eks_capability" "default" {
for_each = local.enabled_capability_keys

cluster_name = local.eks_cluster_id
capability_name = each.value
type = var.capabilities[each.value].type
role_arn = local.capability_role_arns[each.value]
delete_propagation_policy = var.capabilities[each.value].delete_propagation_policy
tags = module.label.tags

dynamic "configuration" {
# The AWS API requires configuration with argo_cd and aws_idc for ARGOCD capabilities.
# Skip the entire configuration block if aws_idc is not provided -- the capability
# cannot be created without it. Provide aws_idc in your stack config to enable.
for_each = (
var.capabilities[each.value].type == "ARGOCD" &&
var.capabilities[each.value].configuration != null &&
try(var.capabilities[each.value].configuration.argo_cd.aws_idc, null) != null
) ? [var.capabilities[each.value].configuration] : []
content {
dynamic "argo_cd" {
for_each = configuration.value.argo_cd != null ? [configuration.value.argo_cd] : []
content {
namespace = argo_cd.value.namespace

aws_idc {
idc_instance_arn = argo_cd.value.aws_idc.idc_instance_arn
idc_region = argo_cd.value.aws_idc.idc_region
}

dynamic "network_access" {
for_each = argo_cd.value.network_access != null ? [argo_cd.value.network_access] : []
content {
vpce_ids = network_access.value.vpce_ids
}
}

dynamic "rbac_role_mapping" {
for_each = argo_cd.value.rbac_role_mapping
content {
role = rbac_role_mapping.value.role

dynamic "identity" {
for_each = rbac_role_mapping.value.identity
content {
id = identity.value.id
type = identity.value.type
}
}
}
}
}
}
}
}

timeouts {
create = var.capabilities[each.value].create_timeout
update = var.capabilities[each.value].update_timeout
delete = var.capabilities[each.value].delete_timeout
}

depends_on = [
aws_eks_cluster.default,
aws_iam_role.capability,
]
}
60 changes: 59 additions & 1 deletion examples/complete/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ provider "aws" {
region = var.region
}

data "aws_partition" "current" {}

module "label" {
source = "cloudposse/label/null"
version = "0.25.0"
Expand Down Expand Up @@ -111,11 +113,28 @@ module "eks_cluster" {
cluster_encryption_config_resources = var.cluster_encryption_config_resources

addons = local.addons
addons_depends_on = [module.eks_node_group]
addons_depends_on = var.auto_mode_enabled ? null : [module.eks_node_group]
bootstrap_self_managed_addons_enabled = var.bootstrap_self_managed_addons_enabled
upgrade_policy = var.upgrade_policy
zonal_shift_config = var.zonal_shift_config

# EKS Auto Mode
auto_mode_compute_config = {
enabled = var.auto_mode_enabled
node_pools = var.auto_mode_enabled ? ["general-purpose", "system"] : []
node_role_arn = var.auto_mode_enabled ? one(aws_iam_role.auto_mode_node[*].arn) : null
}

auto_mode_storage_config = {
block_storage = {
enabled = var.auto_mode_enabled
}
}

auto_mode_elastic_load_balancing = {
enabled = var.auto_mode_enabled
}

access_entry_map = local.access_entry_map
access_config = {
authentication_mode = "API"
Expand All @@ -136,10 +155,49 @@ module "eks_cluster" {
cluster_depends_on = [module.subnets]
}

# Auto Mode node role (only when auto_mode_enabled = true)
data "aws_iam_policy_document" "auto_mode_node_assume_role" {
count = local.enabled && var.auto_mode_enabled ? 1 : 0

statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

resource "aws_iam_role" "auto_mode_node" {
count = local.enabled && var.auto_mode_enabled ? 1 : 0

name = "${module.label.id}-auto-mode-node"
assume_role_policy = one(data.aws_iam_policy_document.auto_mode_node_assume_role[*].json)
tags = module.label.tags
}

resource "aws_iam_role_policy_attachment" "auto_mode_node_minimal" {
count = local.enabled && var.auto_mode_enabled ? 1 : 0

role = one(aws_iam_role.auto_mode_node[*].name)
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKSWorkerNodeMinimalPolicy"
}

resource "aws_iam_role_policy_attachment" "auto_mode_node_ecr" {
count = local.enabled && var.auto_mode_enabled ? 1 : 0

role = one(aws_iam_role.auto_mode_node[*].name)
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEC2ContainerRegistryPullOnly"
}

module "eks_node_group" {
source = "cloudposse/eks-node-group/aws"
version = "3.2.0"

enabled = !var.auto_mode_enabled

# node group <= 3.2 requires a non-empty list of subnet_ids, even when disabled
subnet_ids = local.enabled ? module.subnets.public_subnet_ids : ["filler_string_for_enabled_is_false"]
cluster_name = module.eks_cluster.eks_cluster_id
Expand Down
6 changes: 6 additions & 0 deletions examples/complete/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ variable "private_ipv6_enabled" {
description = "Whether to use IPv6 addresses for the pods in the node group"
}

variable "auto_mode_enabled" {
type = bool
default = false
description = "Set to true to enable EKS Auto Mode"
}

variable "remote_network_config" {
description = "Configuration block for the cluster remote network configuration"
type = object({
Expand Down
2 changes: 1 addition & 1 deletion examples/complete/vpc-cni.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ resource "aws_iam_role_policy_attachment" "vpc_cni" {
count = local.vpc_cni_sa_needed ? 1 : 0

role = module.vpc_cni_eks_iam_role.service_account_role_name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
}

module "vpc_cni_eks_iam_role" {
Expand Down
Loading
Loading