Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
256 changes: 256 additions & 0 deletions MIGRATION-2.0.md

Large diffs are not rendered by default.

172 changes: 169 additions & 3 deletions README.md

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ data "aws_iam_policy_document" "cluster_assume_role" {
identifiers = ["eks.amazonaws.com"]
}

actions = ["sts:AssumeRole"]
actions = local.cluster_assume_role_actions
}
}

Expand Down Expand Up @@ -41,3 +41,19 @@ data "aws_iam_policy_document" "capability_assume_role" {
]
}
}

data "aws_iam_policy_document" "pod_identity_assume_role" {
statement {
effect = "Allow"

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

actions = [
"sts:AssumeRole",
"sts:TagSession"
]
}
}
48 changes: 48 additions & 0 deletions examples/auto-mode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# EKS Auto Mode Example

This example creates an EKS cluster that uses Auto Mode for compute, load balancing, and block storage. It enables the EKS built-in `general-purpose` and `system` node pools and deliberately sets both `node_groups` and `addons` to empty maps.

Because an Auto Mode object is configured, the nullable `bootstrap_self_managed_addons` input resolves to `false` when this new cluster is created. EKS treats that setting as creation-time only.

The Kubernetes API endpoint is public and private. The example defaults `public_access_cidrs` to `0.0.0.0/0` for ease of evaluation; production callers should replace that value with the narrowest CIDRs that need API access.

Provide at least two existing subnet IDs in different Availability Zones before planning:

```bash
terraform init
terraform plan \
-var='subnet_ids=["subnet-0123456789abcdef0","subnet-0fedcba9876543210"]' \
-var='public_access_cidrs=["203.0.113.10/32"]'
```

After applying the plan, configure `kubectl` with the rendered command:

```bash
terraform output -raw update_kubeconfig_command
```

The module can also render custom Auto Mode `NodeClass`, `NodePool`, `StorageClass`, and load-balancer `Service` YAML through `auto_mode_node_classes`, `auto_mode_node_pools`, `auto_mode_storage_classes`, and `auto_mode_load_balancer_services`. It returns the YAML as outputs but does not apply it, because the module does not configure a Kubernetes provider.

Set `access_entries` to grant principals access through the EKS API authentication mode. Set `pod_identity_associations` when workloads need EKS Pod Identity; the module can create a role for an association or use an existing role. Auto Mode nodes include the Pod Identity agent, so this pure Auto Mode example does not need the `eks-pod-identity-agent` add-on. Hybrid clusters must retain or add that add-on for Pod Identity workloads running on standard managed nodes. A fresh hybrid configuration must also set `addons["vpc-cni"].before_compute = true`; moving an existing add-on to that phase requires the state move documented in [`MIGRATION-2.0.md`](../../MIGRATION-2.0.md).

EKS creates the access entry for the node role used by the built-in node pools. The module does not create a duplicate. A custom `NodeClass` using a separate IAM role should set `node_role_arn`; for a plan-known ARN, the module automatically creates an `EC2` access entry and `AmazonEKSAutoNodePolicy` association keyed by the NodeClass name. Set `create_access_entry = true` explicitly when the ARN comes from a resource created in the same plan. Give NodeClasses sharing one role the same stable `access_entry_key` to create one shared entry; the output uses that key. Set `create_access_entry = false` when reusing the built-in compute role or an entry managed elsewhere.

After Auto Mode is enabled with built-in node pools, any planned change to its effective compute node role ARN forces EKS cluster replacement. Keep the role ARN stable for the cluster's lifetime.

AWS-managed Auto Mode policies do not grant permission to propagate arbitrary user-defined tags to provisioned AWS resources. Use a plan-safe map when the policy is created alongside the cluster:

```hcl
auto_mode = {
cluster_iam_policy_arn_map = {
custom_tags = aws_iam_policy.eks_auto_custom_tags.arn
}
}
```

Use `cluster_iam_policy_arns` only for ARNs already known during planning. The same map/set distinction applies to `auto_mode.node_iam_policy_arn_map`/`node_iam_policy_arns` and to `iam_policy_arn_map`/`iam_policy_arns` in Pod Identity and capability entries.

Custom worker security groups outside the EKS-created `eks-cluster-sg-*` naming pattern can require additional cluster-role permissions for Auto Mode to add load-balancer ingress rules. Supply a least-privilege policy through `cluster_iam_policy_arn_map` and review AWS's [Auto Mode networking guidance](https://docs.aws.amazon.com/eks/latest/userguide/auto-networking.html).

Choose the network family only when creating the cluster. The provider marks `ip_family` and `service_ipv4_cidr` `ForceNew`, so any planned difference forces replacement of the EKS cluster. Auto Mode supports IPv6, but `service_ipv4_cidr` cannot be set with `ip_family = "ipv6"`; omit it and let EKS assign the IPv6 service range.

Disabling Auto Mode requires two applies and destroys Auto Mode-managed compute and load balancers. Review AWS's [disable procedure](https://docs.aws.amazon.com/eks/latest/userguide/auto-disable.html) before changing an enabled cluster to `auto_mode = null`, and keep `bootstrap_self_managed_addons = false` permanently for this Auto-created cluster.
39 changes: 39 additions & 0 deletions examples/auto-mode/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
terraform {
required_version = ">= 1.5.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.59.0"
}
}
}

provider "aws" {
region = var.region
}

module "eks" {
source = "../.."

name = var.name
cluster_name = var.cluster_name
kubernetes_version = var.kubernetes_version
subnet_ids = var.subnet_ids

# An empty object enables Auto Mode with the EKS built-in
# general-purpose and system node pools.
auto_mode = {}

# This is a pure Auto Mode cluster: do not create managed node groups or
# install the traditional EKS add-on resources.
node_groups = {}
addons = {}

# Version 2.0 keeps the module's public-and-private endpoint behavior.
endpoint_private_access = true
endpoint_public_access = true
public_access_cidrs = var.public_access_cidrs

tags = var.tags
}
19 changes: 19 additions & 0 deletions examples/auto-mode/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
output "cluster_name" {
description = "EKS cluster name."
value = module.eks.cluster_name
}

output "cluster_endpoint" {
description = "Kubernetes API server endpoint."
value = module.eks.cluster_endpoint
}

output "auto_mode_node_iam_role_arn" {
description = "IAM role ARN used by EKS Auto Mode managed compute."
value = module.eks.auto_mode_node_iam_role_arn
}

output "update_kubeconfig_command" {
description = "AWS CLI command to configure kubectl for this cluster."
value = module.eks.update_kubeconfig_command
}
43 changes: 43 additions & 0 deletions examples/auto-mode/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
variable "region" {
description = "AWS region to deploy into."
type = string
default = "eu-west-2"
}

variable "name" {
description = "Name prefix for module-created resources."
type = string
default = "example-auto-mode"
}

variable "cluster_name" {
description = "Optional EKS cluster name. When null, name is used as the cluster name."
type = string
default = null
}

variable "kubernetes_version" {
description = "Kubernetes version for the EKS cluster. Leave null to use the current AWS default."
type = string
default = null
}

variable "subnet_ids" {
description = "Existing subnet IDs for the EKS control plane and Auto Mode compute. Prefer private subnets with NAT egress."
type = list(string)
}

variable "public_access_cidrs" {
description = "CIDR blocks allowed to reach the public Kubernetes API endpoint. Restrict this in production."
type = list(string)
default = ["0.0.0.0/0"]
}

variable "tags" {
description = "Tags applied to resources."
type = map(string)
default = {
Environment = "example"
Example = "auto-mode"
}
}
63 changes: 63 additions & 0 deletions iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ resource "aws_iam_role_policy_attachment" "cluster" {
policy_arn = each.value
}

resource "aws_iam_role_policy_attachment" "auto_mode_cluster_additional" {
for_each = local.auto_mode_cluster_policy_arn_map

role = aws_iam_role.cluster.name
policy_arn = each.value
}

resource "aws_iam_role" "node" {
name = local.node_role_name
assume_role_policy = data.aws_iam_policy_document.node_assume_role.json
Expand All @@ -24,6 +31,54 @@ resource "aws_iam_role_policy_attachment" "node" {
policy_arn = each.value
}

resource "aws_iam_role" "auto_mode_node" {
count = local.auto_mode_create_node_iam_role ? 1 : 0

name = local.auto_mode_node_role_name
assume_role_policy = data.aws_iam_policy_document.node_assume_role.json
tags = local.common_tags
}

resource "aws_iam_role_policy_attachment" "auto_mode_node" {
for_each = local.auto_mode_node_policy_arns

role = aws_iam_role.auto_mode_node[0].name
policy_arn = each.value
}

resource "aws_iam_role_policy_attachment" "auto_mode_node_additional" {
for_each = local.auto_mode_node_policy_arn_map

role = aws_iam_role.auto_mode_node[0].name
policy_arn = each.value
}

resource "aws_iam_role" "pod_identity" {
for_each = local.pod_identity_create_iam_roles

name = each.value.iam_role_name
assume_role_policy = data.aws_iam_policy_document.pod_identity_assume_role.json
tags = merge(local.common_tags, each.value.tags)
}

resource "aws_iam_role_policy_attachment" "pod_identity" {
for_each = local.pod_identity_policy_attachments

role = aws_iam_role.pod_identity[each.value.association_key].name
policy_arn = each.value.policy_arn
}

resource "aws_iam_role_policy" "pod_identity" {
for_each = {
for key, association in local.pod_identity_create_iam_roles : key => association
if association.inline_policy_json != null
}

name = substr("${each.value.iam_role_name}-policy", 0, 128)
role = aws_iam_role.pod_identity[each.key].id
policy = each.value.inline_policy_json
}

resource "aws_iam_role" "eks_capability" {
for_each = local.eks_capability_create_iam_roles

Expand All @@ -50,6 +105,14 @@ resource "aws_iam_role_policy" "eks_capability" {
policy = each.value.inline_policy_json
}

resource "aws_iam_role_policy" "eks_capability_preset" {
for_each = local.eks_capability_preset_policies

name = substr("${local.eks_capability_configs[each.value.capability_key].iam_role_name}-${each.value.preset_name}", 0, 128)
role = aws_iam_role.eks_capability[each.value.capability_key].id
policy = jsonencode(local.eks_capability_iam_policy_preset_documents[each.value.preset_name])
}

resource "aws_iam_role" "karpenter_node" {
count = local.karpenter_create_node_iam_role ? 1 : 0

Expand Down
Loading