From ab09420bf73886fd3e232bea9a4ee1fef80265b8 Mon Sep 17 00:00:00 2001 From: John Ajera <37360952+jajera@users.noreply.github.com> Date: Sat, 4 Apr 2026 20:51:03 +1300 Subject: [PATCH] update argocd code connection module improves code handle chore: update argocd code connection module improves code handle docs: update examples restructures and reorganizes for eacy understanding terraform-docs: automated action feat: multi-cluster shared VPC SQS and Fargate IRSA - ACK capability: attach AmazonSQSFullAccess for SQS-managed resources - Per-cluster SQS queue ARNs (celery jobs + DLQ); enable_sqs_access on all three clusters - Fargate: sqs_identity_type irsa, workload_sqs profile, eks-pod-identity-agent in addons - Classic/Auto Mode: sqs_identity_type pod_identity - Output fargate_sqs_role_arns; README for ALB/KEDA/celery IRSA and kube-infra feat: configure eks-hub-spoke hub and spoke configuration feat: add support for cluster autoscaler sets iam permission via irsa or identity pod feat: configure support for karpenter adds support to use karpenter --- .cursor/rules/markdown.mdc | 42 ++ .cursor/rules/project-conventions.mdc | 100 +++++ .cursor/rules/terraform-eks.mdc | 82 ++++ .markdownlint.yaml | 9 + README.md | 158 ++++++- cluster-autoscaler-iam.tf | 129 ++++++ .../README.md | 0 .../main.tf | 0 .../outputs.tf | 0 .../terraform.tfvars.example | 0 .../variables.tf | 0 .../README.md | 2 +- .../main.tf | 2 +- .../outputs.tf | 0 .../terraform.tfvars.example | 0 .../variables.tf | 0 .../.terraform.lock.hcl | 20 - .../{eks-auto-mode => auto-mode}/README.md | 41 +- examples/auto-mode/main.tf | 173 ++++++++ examples/auto-mode/outputs.tf | 54 +++ .../terraform.tfvars.example | 0 examples/auto-mode/variables.tf | 93 ++++ examples/basic/.terraform.lock.hcl | 85 ++-- examples/basic/README.md | 9 +- examples/basic/main.tf | 181 ++++++-- examples/basic/outputs.tf | 32 +- examples/basic/terraform.tfvars.example | 55 +++ examples/basic/variables.tf | 54 ++- .../README.md | 0 .../main.tf | 0 .../outputs.tf | 0 .../terraform.tfvars.example | 0 .../variables.tf | 0 .../README.md | 0 .../main.tf | 0 .../outputs.tf | 0 .../terraform.tfvars.example | 0 .../variables.tf | 0 .../manifests/scale-test.yaml | 31 -- examples/eks-auto-mode/main.tf | 80 ---- .../eks-auto-mode/manifests/scale-test.yaml | 31 -- examples/eks-auto-mode/variables.tf | 45 -- .../.terraform.lock.hcl | 105 ----- examples/eks-capabilities/.terraform.lock.hcl | 104 ----- examples/eks-fargate/main.tf | 85 ---- examples/eks-fargate/variables.tf | 51 --- .../.terraform.lock.hcl | 28 +- examples/{eks-fargate => fargate}/README.md | 3 +- examples/fargate/main.tf | 198 +++++++++ examples/{eks-fargate => fargate}/outputs.tf | 17 +- examples/fargate/terraform.tfvars.example | 31 ++ examples/fargate/variables.tf | 101 +++++ .../.terraform.lock.hcl | 32 +- examples/hub-spoke-argocd/README.md | 113 +++++ examples/hub-spoke-argocd/main.tf | 226 ++++++++++ examples/hub-spoke-argocd/outputs.tf | 96 +++++ .../hub-spoke-argocd/terraform.tfvars.example | 80 ++++ examples/hub-spoke-argocd/variables.tf | 151 +++++++ examples/minimal/README.md | 39 ++ examples/minimal/main.tf | 40 ++ .../{eks-auto-mode => minimal}/outputs.tf | 20 - examples/minimal/terraform.tfvars.example | 15 + examples/minimal/variables.tf | 36 ++ .../.terraform.lock.hcl | 52 +-- examples/multi-cluster-shared-vpc/README.md | 72 ++++ examples/multi-cluster-shared-vpc/main.tf | 403 ++++++++++++++++++ examples/multi-cluster-shared-vpc/outputs.tf | 83 ++++ .../terraform.tfvars.example | 67 +++ .../multi-cluster-shared-vpc/variables.tf | 153 +++++++ examples/pod-identity/.terraform.lock.hcl | 104 ----- examples/pod-identity/loki-logs-bucket.tf | 7 +- examples/pod-identity/outputs.tf | 2 +- examples/private-endpoint/.terraform.lock.hcl | 123 ------ examples/private-endpoint/README.md | 1 + .../private-endpoint/terraform.tfvars.example | 23 + fargate.tf | 11 +- karpenter.tf | 64 +++ main.tf | 19 +- modules/argocd-codeconnections/README.md | 6 +- modules/argocd-codeconnections/main.tf | 19 +- modules/argocd-codeconnections/outputs.tf | 14 +- modules/argocd-codeconnections/variables.tf | 15 +- outputs.tf | 29 +- variables.tf | 64 +++ 84 files changed, 3367 insertions(+), 1043 deletions(-) create mode 100644 .cursor/rules/markdown.mdc create mode 100644 .cursor/rules/project-conventions.mdc create mode 100644 .cursor/rules/terraform-eks.mdc create mode 100644 .markdownlint.yaml create mode 100644 cluster-autoscaler-iam.tf rename examples/{eks-auto-mode-keda-workload => auto-mode-keda-workload}/README.md (100%) rename examples/{eks-auto-mode-keda-workload => auto-mode-keda-workload}/main.tf (100%) rename examples/{eks-auto-mode-keda-workload => auto-mode-keda-workload}/outputs.tf (100%) rename examples/{eks-auto-mode-keda-workload => auto-mode-keda-workload}/terraform.tfvars.example (100%) rename examples/{eks-auto-mode-keda-workload => auto-mode-keda-workload}/variables.tf (100%) rename examples/{eks-auto-mode-private => auto-mode-private}/README.md (96%) rename examples/{eks-auto-mode-private => auto-mode-private}/main.tf (95%) rename examples/{eks-auto-mode-private => auto-mode-private}/outputs.tf (100%) rename examples/{eks-auto-mode-private => auto-mode-private}/terraform.tfvars.example (100%) rename examples/{eks-auto-mode-private => auto-mode-private}/variables.tf (100%) rename examples/{eks-auto-mode => auto-mode}/.terraform.lock.hcl (81%) rename examples/{eks-auto-mode => auto-mode}/README.md (57%) create mode 100644 examples/auto-mode/main.tf create mode 100644 examples/auto-mode/outputs.tf rename examples/{eks-auto-mode => auto-mode}/terraform.tfvars.example (100%) create mode 100644 examples/auto-mode/variables.tf create mode 100644 examples/basic/terraform.tfvars.example rename examples/{eks-capabilities-private => capabilities-private}/README.md (100%) rename examples/{eks-capabilities-private => capabilities-private}/main.tf (100%) rename examples/{eks-capabilities-private => capabilities-private}/outputs.tf (100%) rename examples/{eks-capabilities-private => capabilities-private}/terraform.tfvars.example (100%) rename examples/{eks-capabilities-private => capabilities-private}/variables.tf (100%) rename examples/{eks-capabilities => capabilities}/README.md (100%) rename examples/{eks-capabilities => capabilities}/main.tf (100%) rename examples/{eks-capabilities => capabilities}/outputs.tf (100%) rename examples/{eks-capabilities => capabilities}/terraform.tfvars.example (100%) rename examples/{eks-capabilities => capabilities}/variables.tf (100%) delete mode 100644 examples/eks-auto-mode-private/manifests/scale-test.yaml delete mode 100644 examples/eks-auto-mode/main.tf delete mode 100644 examples/eks-auto-mode/manifests/scale-test.yaml delete mode 100644 examples/eks-auto-mode/variables.tf delete mode 100644 examples/eks-capabilities-private/.terraform.lock.hcl delete mode 100644 examples/eks-capabilities/.terraform.lock.hcl delete mode 100644 examples/eks-fargate/main.tf delete mode 100644 examples/eks-fargate/variables.tf rename examples/{eks-fargate => fargate}/.terraform.lock.hcl (79%) rename examples/{eks-fargate => fargate}/README.md (80%) create mode 100644 examples/fargate/main.tf rename examples/{eks-fargate => fargate}/outputs.tf (60%) create mode 100644 examples/fargate/terraform.tfvars.example create mode 100644 examples/fargate/variables.tf rename examples/{eks-auto-mode-private => hub-spoke-argocd}/.terraform.lock.hcl (75%) create mode 100644 examples/hub-spoke-argocd/README.md create mode 100644 examples/hub-spoke-argocd/main.tf create mode 100644 examples/hub-spoke-argocd/outputs.tf create mode 100644 examples/hub-spoke-argocd/terraform.tfvars.example create mode 100644 examples/hub-spoke-argocd/variables.tf create mode 100644 examples/minimal/README.md create mode 100644 examples/minimal/main.tf rename examples/{eks-auto-mode => minimal}/outputs.tf (52%) create mode 100644 examples/minimal/terraform.tfvars.example create mode 100644 examples/minimal/variables.tf rename examples/{eks-auto-mode-keda-workload => multi-cluster-shared-vpc}/.terraform.lock.hcl (61%) create mode 100644 examples/multi-cluster-shared-vpc/README.md create mode 100644 examples/multi-cluster-shared-vpc/main.tf create mode 100644 examples/multi-cluster-shared-vpc/outputs.tf create mode 100644 examples/multi-cluster-shared-vpc/terraform.tfvars.example create mode 100644 examples/multi-cluster-shared-vpc/variables.tf delete mode 100644 examples/pod-identity/.terraform.lock.hcl delete mode 100644 examples/private-endpoint/.terraform.lock.hcl create mode 100644 examples/private-endpoint/terraform.tfvars.example create mode 100644 karpenter.tf diff --git a/.cursor/rules/markdown.mdc b/.cursor/rules/markdown.mdc new file mode 100644 index 0000000..eaf263d --- /dev/null +++ b/.cursor/rules/markdown.mdc @@ -0,0 +1,42 @@ +--- +description: Markdown and markdownlint (MD060) conventions for this repo. +globs: + - "**/*.md" +alwaysApply: false +--- + +# Markdown + +Repo **`.markdownlint.yaml`** disables **MD013** (line length); do not re-wrap markdown to an arbitrary column limit unless the user asks. + +## Root `README.md` — terraform-docs (do not edit) + +The block between **``** and **``** is **generated by terraform-docs**. **Never** edit, reformat, or fix lint inside those markers. + +- To change inputs/outputs/docs in that section: edit `variables.tf`, `outputs.tf`, or other Terraform in the root module, then regenerate (e.g. `terraform-docs markdown table --output-file README.md --output-mode inject .` from the repo root, per your local workflow). +- **Do not remove** the `` / `` pair that wraps that block (they sit **outside** the BEGIN/END markers). They suppress MD060 for terraform-docs’ default table separators. + +## MD060 — table column style (`compact`) + +For **hand-written** markdown (outside the generated README block), markdownlint **MD060** uses **compact** style: + +1. **Every cell** has one space after the opening `|` and one space before the closing `|`. +2. **Separator rows** use `| --- | --- |`, not `|---------|---------|`. + +**Correct:** + +```markdown +| Column A | Column B | +| --- | --- | +| foo | bar | +``` + +**Wrong (triggers MD060):** + +```markdown +| Column A | Column B | +|----------|----------| +| foo | bar | +``` + +Apply this pattern to **all** manually authored tables in `*.md` (example READMEs, module docs, etc.). diff --git a/.cursor/rules/project-conventions.mdc b/.cursor/rules/project-conventions.mdc new file mode 100644 index 0000000..2b14a44 --- /dev/null +++ b/.cursor/rules/project-conventions.mdc @@ -0,0 +1,100 @@ +--- +description: Project-wide conventions for terraform-aws-eks-basic. Always apply when working in this repo. +globs: +alwaysApply: true +--- + +# Project Conventions + +## Repo overview + +This is a Terraform module (`terraform-aws-eks-basic`) for creating EKS clusters. + +- Root module: the reusable module itself (do not treat as an example) +- `examples/`: standalone Terraform configurations that call the root module for testing/reference +- `modules/argocd-codeconnections/`: optional submodule for GitHub CodeConnections + IAM +- Related GitOps repo: `/home/johna/workspace/k8sforge/kube-infra` (Argo CD manifests) + +## Terraform workflow + +Always run Terraform commands from within the example directory, not the root: +```bash +cd examples/auto-mode +terraform init +terraform plan +terraform apply +``` + +## Examples + +The three primary full-featured examples are: +- `examples/auto-mode` — EKS Auto Mode (primary example; used for personal testing) +- `examples/fargate` — Pure Fargate +- `examples/basic` — Classic EC2 managed node groups + +These all share the same variable/output structure for consistency. + +Minimal example (`examples/minimal`) is BYO VPC, no capabilities, ~30 lines. + +### Example conventions +- No `kubernetes` or `helm` provider blocks unless the example actually uses them +- Variables always include `endpoint_public_access`, `public_access_cidrs`, `private_access_cidrs`, `argocd_idc_instance_arn`, `argocd_rbac_role_mappings`, `argocd_vpce_ids`, `access_entries`, `tags` +- Outputs always include `aws_region`, `cluster_name`, `cluster_endpoint`, `oidc_provider_arn`, and any Argo CD CodeConnections outputs when applicable +- Comments use `# ── Section Title ──────` header style with closing `# ──` line + +## Argo CD capability + +- Requires AWS IAM Identity Center (`argocd_idc_instance_arn`) +- Use `merge({ack={}, kro={}}, var.argocd_idc_instance_arn != null ? { argocd = {...} } : {})` pattern +- Always include `network_access = { vpce_ids = var.argocd_vpce_ids }` even when empty +- Always include `rbac_role_mapping = var.argocd_rbac_role_mappings` + +### CodeConnections + +- Always use `modules/argocd-codeconnections` submodule — do not wire inline +- `argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"]` +- Connection `name` should follow pattern `"github-${var.cluster_name}"` +- Always expose `argocd_connection_ids`, `argocd_codeconnections_iam_role_name`, `argocd_codeconnections_iam_policy_name` as outputs +- After apply, go to AWS Console → CodeConnections to complete OAuth; connections start in PENDING state +- Argo CD Application repoURLs must use the UUID from `connection_ids` output — other UUIDs cause AccessDeniedException + +## Known bugs + +### Argo CD UI public ↔ private switching + +**Symptom:** Changing `argocd_vpce_ids` between empty and a VPCE ID does not apply in-place. + +**Workaround:** +1. Remove `argocd` from `capabilities` block +2. Run `terraform apply` (destroys the Argo CD capability) +3. Re-add `argocd` with the correct `argocd_vpce_ids` +4. Run `terraform apply` again + +This applies only to the Argo CD UI endpoint — not the Kubernetes API endpoint access. + +## Pod Identity vs IRSA + +| Context | Use | +| --- | --- | +| Auto Mode | Pod Identity (`"pod_identity"`) — agent is built in | +| EC2 nodes | Pod Identity (`"pod_identity"`) — requires `eks-pod-identity-agent` addon | +| Fargate | IRSA (`"irsa"`) — Pod Identity agent cannot run on Fargate | + +## kube-infra repo + +GitOps manifests live at `/home/johna/workspace/k8sforge/kube-infra`. +Argo CD Application `repoURL` must use the CodeConnections UUID from Terraform output `argocd_connection_ids`. +Bootstrap secret commands are documented in `bootstrap/README.md` in that repo. + +Cursor rules for GitOps layout, hub-spoke ApplicationSets, spoke `argocd` namespace / RBAC reconcile, and EBS CSI vs Auto Mode are in **kube-infra** `.cursor/rules/infrastructure-conventions.mdc` — read that when editing `clusters/`, `bootstrap/`, or `infrastructure/aws-ebs-csi-driver/`. + +### Hub-spoke example (`examples/hub-spoke-argocd`) + +- Argo CD capability lives on the **hub** only; spokes get **`access_entries`** merging `argocd_hub` so the hub capability IAM role has cluster-admin (or scoped policy) on each spoke. +- Cluster registration in kube-infra uses **EKS cluster ARN** as `server` in the cluster Secret when using managed Argo CD `awsAuthConfig` (not the public API URL). +- Terraform does **not** create the empty `argocd` namespace on spokes; GitOps **`infrastructure/argocd-destination-namespace/`** in kube-infra does (see kube-infra rules). Do not spend cycles on “wrong EBS CSI” until spoke RBAC sync errors (`namespaces "argocd" not found`) are ruled out. + +## Markdown + +- **`README.md` terraform-docs block:** Do **not** edit anything between `` and `` — it is auto-generated. Regenerate with terraform-docs after changing root-module Terraform. Do **not** remove the `markdownlint-disable MD060` / `markdownlint-enable MD060` HTML comments that wrap that block (they live outside the markers). +- **All other `*.md`:** Use **compact** tables for markdownlint **MD060** (`| --- | --- |` separators). **MD013** (line length) is off repo-wide. See `.markdownlint.yaml` and `.cursor/rules/markdown.mdc`. diff --git a/.cursor/rules/terraform-eks.mdc b/.cursor/rules/terraform-eks.mdc new file mode 100644 index 0000000..969e739 --- /dev/null +++ b/.cursor/rules/terraform-eks.mdc @@ -0,0 +1,82 @@ +--- +description: Terraform coding conventions for terraform-aws-eks-basic. Apply to all .tf files. +globs: + - "**/*.tf" +alwaysApply: false +--- + +# Terraform EKS Module Conventions + +## Variable standards + +- Use `default = null` for optional string variables that gate a feature (e.g. `argocd_idc_instance_arn`) +- Use `default = []` for optional list variables (e.g. `argocd_vpce_ids`) +- Required variables have no `default` +- Boolean feature flags default to `false` (e.g. `enable_ebs_csi_driver = false`) +- Identity type variables always default to `"irsa"` (e.g. `ebs_csi_driver_identity_type`) +- Always group related variables with a `# ── Section Title ──` comment + +## IAM role references + +- Always use role **names** (not ARNs) when referencing capability roles for inline policies +- Use `module.eks.cluster_capability_role_names["argocd"]` not `cluster_capability_role_arns` +- The `argocd-codeconnections` submodule expects `argocd_capability_role_name` (string), not ARN + +## Comment style for optional feature blocks + +Use this style for all optional blocks in examples — makes it easy to scan what to enable: + +```hcl +# ── Feature Name ──────────────────────────────────────────────────────────── +# Short explanation of when/why to enable this. +# Mutually exclusive with: X +# NOT applicable on: Y +# ──────────────────────────────────────────────────────────────────────────── +enable_feature = true +``` + +Do not add comments that just repeat what the code does — only explain non-obvious intent, trade-offs, or constraints. + +## Auto Mode specifics + +- Set `addons = {}` — built-ins cover CoreDNS, vpc-cni, kube-proxy, Pod Identity Agent +- Set `enable_automode = true` and `automode_node_pools = ["system", "general-purpose"]` +- Do NOT set `eks_managed_node_groups` (mutually exclusive) +- Use private subnets only for `subnet_ids` on Auto Mode + +## Fargate specifics + +- Pass `concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids)` to `subnet_ids` +- Always include a `kube-system` Fargate profile so CoreDNS can schedule +- Set `computeType = "fargate"` in the CoreDNS addon `configuration_values` +- Only vpc-cni and coredns addons needed (no kube-proxy, no Pod Identity Agent) +- Secrets Manager and EBS CSI must use `"irsa"` identity type — Pod Identity not supported on Fargate +- EBS CSI is not applicable on Fargate — remove it entirely + +## Capabilities block pattern + +Always use `merge()` with a conditional for Argo CD: + +```hcl +capabilities = merge( + { ack = {}, kro = {} }, + var.argocd_idc_instance_arn != null ? { argocd = { ... } } : {} +) +``` + +Always include `network_access = { vpce_ids = var.argocd_vpce_ids }` in the Argo CD block. + +## Module source paths + +- Examples reference the root module as `../../` +- Examples reference the CodeConnections submodule as `../../modules/argocd-codeconnections` + +## Provider blocks in examples + +Only declare providers that the example actually uses: +- `aws` — always +- `tls` — always (used by root module for OIDC certificate) +- `kubernetes` — only if the example creates Kubernetes resources directly +- `helm` — only if the example deploys Helm charts directly + +Do not add `kubernetes` or `helm` providers just because the root module exists — the root module handles them internally when needed. diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..bdbf124 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,9 @@ +# markdownlint: https://github.com/DavidAnson/markdownlint +# MD060 (table-column-style): "compact" for hand-written tables (| --- | separators). +# Root README disables MD060 between markdownlint-disable/enable comments wrapping +# the terraform-docs output (). +default: true +# MD013 (line-length): disabled — long lines are common in README tables and terraform-docs output. +MD013: false +MD060: + style: "compact" diff --git a/README.md b/README.md index 4dd3821..99e1393 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A Terraform module for creating and managing Amazon EKS (Elastic Kubernetes Serv - **EC2 Managed Node Groups**: Full support with customizable launch templates and auto-scaling - **EKS Auto Mode**: Optional; compute is auto-provisioned by AWS (no managed node groups; built-in node pools). Mutually exclusive with `eks_managed_node_groups`. -- **Fargate profiles**: Optional via `fargate_profiles`; shared pod execution role (create or bring-your-own ARN), profiles, and optional API access entry (`create_fargate_access_entry`, `fargate_access_entry_type`). IAM options: `fargate_pod_execution_role_name`, `path`, `permissions_boundary`. See `examples/eks-fargate`. +- **Fargate profiles**: Optional via `fargate_profiles`; shared pod execution role (create or bring-your-own ARN), profiles, and optional API access entry (`create_fargate_access_entry`, `fargate_access_entry_type`). IAM options: `fargate_pod_execution_role_name`, `path`, `permissions_boundary`. See `examples/fargate`. - **Dual-Stack Support**: IPv4 and IPv6 cluster support (IPv6 service CIDR auto-assigned by AWS) - **Modern EKS Access Entries**: Native EKS authentication via access entries (no aws-auth ConfigMap) - **IRSA and Pod Identity**: OIDC provider for IAM Roles for Service Accounts (IRSA); optional EKS Pod Identity per component (ALB controller, External DNS, addons, Secrets Manager). Choose per component via `*_identity_type` variables (`"irsa"` or `"pod_identity"`). When using Pod Identity, enable the **eks-pod-identity-agent** addon. @@ -19,7 +19,7 @@ A Terraform module for creating and managing Amazon EKS (Elastic Kubernetes Serv ## Prerequisites | Name | Version | -| ---- | ------- | +| --- | --- | | terraform | >= 1.6.0 | | aws | >= 6.0 | | kubernetes | ~> 2.30 | @@ -171,15 +171,121 @@ EKS automatically creates an access entry for each capability role with default ## Examples -- **[examples/basic](examples/basic/)** - Basic EKS cluster with EC2 node groups -- **[examples/pod-identity](examples/pod-identity/)** - EKS cluster using Pod Identity for ALB controller, External DNS, EBS CSI addon, and Secrets Manager -- **[examples/eks-capabilities](examples/eks-capabilities/)** - Platform engineering example with EKS capabilities (ACK, KRO, Argo CD) -- **[examples/eks-capabilities-private](examples/eks-capabilities-private/)** - Private-only EKS and Argo CD (VPC endpoints; access from within VPC) -- **[examples/eks-auto-mode](examples/eks-auto-mode/)** - EKS cluster with Auto Mode (compute auto-provisioned; no managed node groups) -- **[examples/eks-fargate](examples/eks-fargate/)** - EKS with Fargate only (no EC2 nodes); CoreDNS on Fargate; IRSA for workload AWS credentials -- **[examples/eks-auto-mode-keda-workload](examples/eks-auto-mode-keda-workload/)** - Auto Mode + workload IAM wiring example (SQS; suitable for KEDA-managed workers) -- **[examples/private-endpoint](examples/private-endpoint/)** - EKS with private API endpoint +| Example | Compute mode | Capabilities | Argo CD | Notes | +| --- | --- | --- | --- | --- | +| [minimal](examples/minimal/) | Auto Mode | None | No | BYO VPC/subnets; smallest possible usage | +| [basic](examples/basic/) | EC2 (managed node groups) | ACK, KRO, optional Argo CD | Optional | Full feature set for classic EC2 | +| [auto-mode](examples/auto-mode/) | Auto Mode | ACK, KRO, optional Argo CD | Optional | Full feature set for Auto Mode | +| [fargate](examples/fargate/) | Fargate only | ACK, KRO, optional Argo CD | Optional | No EC2 nodes; IRSA for workload IAM | +| [auto-mode-private](examples/auto-mode-private/) | Auto Mode | ACK, KRO, Argo CD | Private (VPCE) | BYO VPC; private API + private Argo CD UI | +| [capabilities](examples/capabilities/) | Auto Mode | ACK, KRO, Argo CD | Public | Platform engineering reference | +| [capabilities-private](examples/capabilities-private/) | Auto Mode | ACK, KRO, Argo CD | Private (VPCE) | Private API + private Argo CD UI | +| [pod-identity](examples/pod-identity/) | EC2 | None | No | Pod Identity for ALB, External DNS, EBS CSI, Secrets Manager | +| [auto-mode-keda-workload](examples/auto-mode-keda-workload/) | Auto Mode | None | No | SQS IAM for KEDA-managed workers | +| [private-endpoint](examples/private-endpoint/) | EC2 | None | No | Private API endpoint only | +| [multi-cluster-shared-vpc](examples/multi-cluster-shared-vpc/) | EC2 + Fargate + Auto Mode | ACK, KRO, optional Argo CD (per cluster) | Optional | One VPC; three clusters; separate Argo CD and CodeConnections each | +| [hub-spoke-argocd](examples/hub-spoke-argocd/) | Auto Mode (×3) | ACK, KRO, Argo CD on hub only | Required | Hub-and-spoke GitOps: Argo CD on hub deploys to two spoke clusters via access entries | + +## Feature Reference + +### Compute mode + +| Feature | Auto Mode | Fargate | EC2 (managed node groups) | +| --- | --- | --- | --- | +| `enable_automode` | `true` | — | — | +| `eks_managed_node_groups` | Not set (mutually exclusive) | — | Set map of node groups | +| `fargate_profiles` | — | Required | — | +| Pod Identity Agent | Built-in (no addon needed) | Not supported | Install `eks-pod-identity-agent` addon | +| EBS CSI Driver | Pod Identity supported | Not applicable (no EBS on Fargate) | Pod Identity or IRSA | +| Secrets Manager IAM | Pod Identity (`"pod_identity"`) | IRSA only (`"irsa"`) | Pod Identity or IRSA | +| CoreDNS | Built-in | Set `computeType = "fargate"` in addon | Standard addon | +| kube-proxy / vpc-cni | Built-in | Only vpc-cni addon needed | Standard addons | + +### Argo CD capability + +To enable Argo CD, set `argocd_idc_instance_arn` (AWS IAM Identity Center required): +```hcl +capabilities = merge( + { ack = {}, kro = {} }, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [{ + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + }] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { idc_instance_arn = var.argocd_idc_instance_arn, idc_region = var.aws_region } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { vpce_ids = var.argocd_vpce_ids } + } + } + } + } : {} +) +``` + +| Variable | Required | Description | +| --- | --- | --- | +| `argocd_idc_instance_arn` | Yes | IAM Identity Center instance ARN. When `null`, Argo CD is not created. | +| `argocd_rbac_role_mappings` | No | IdC users/groups → Argo CD roles (ADMIN/EDITOR/VIEWER). Required to avoid "No users assigned" errors. | +| `argocd_vpce_ids` | No | VPC interface endpoint IDs for the Argo CD UI. Leave `[]` for public access. | + +> **KNOWN BUG — Argo CD UI public ↔ private switching:** Changing `argocd_vpce_ids` between empty and a VPCE ID does not apply in-place. Workaround: remove `argocd` from `capabilities`, apply (destroys the capability), then re-add with the correct `argocd_vpce_ids` and apply again. This affects the Argo CD UI endpoint only — not the Kubernetes API endpoint. + +### Argo CD CodeConnections (GitHub/GitLab/Bitbucket) + +Use the [modules/argocd-codeconnections](modules/argocd-codeconnections/) submodule to create CodeStar Connections and attach `codeconnections:UseConnection` + `codeconnections:GetConnection` to the Argo CD capability role: + +```hcl +module "argocd_connections" { + source = "../../modules/argocd-codeconnections" + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] + connections = [{ name = "github-${var.cluster_name}", provider_type = "GitHub" }] + tags = var.tags +} +``` + +**Important:** + +- The IAM policy allows only the connections listed here. Argo CD Application `repoURL`s must use the UUID from `output.argocd_connection_ids[""]`. +- Using any other connection UUID will cause `AccessDeniedException` in Argo CD. +- New connections are in `PENDING` state; complete the GitHub OAuth handshake in the AWS Console before Argo CD can sync. + +## API Endpoint Access + +The EKS API server endpoint access is controlled by three variables: + +| Variable | Default | Description | +| --- | --- | --- | +| `endpoint_public_access` | `true` | Whether the API is reachable over the internet | +| `public_access_cidrs` | `["0.0.0.0/0"]` | CIDRs allowed to reach the public endpoint | +| `private_access_cidrs` | `[]` | CIDRs allowed to reach the private endpoint (within VPC) | + +**Configurations:** + +```hcl +# Public only (default) — tighten public_access_cidrs in production +endpoint_public_access = true +public_access_cidrs = ["1.2.3.4/32"] # your egress IP + +# Private only — Terraform runner must be inside the VPC or connected via VPN +endpoint_public_access = false +private_access_cidrs = ["10.0.0.0/8"] + +# Both (recommended for production) +endpoint_public_access = true +public_access_cidrs = ["1.2.3.4/32"] +private_access_cidrs = ["10.0.0.0/8"] +``` + +> **Note:** `argocd_vpce_ids` controls the Argo CD UI endpoint only — it is separate from the Kubernetes API endpoint access above. + + ## Requirements @@ -221,6 +327,7 @@ No modules. | [aws_eks_node_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group) | resource | | [aws_eks_pod_identity_association.addon](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | | [aws_eks_pod_identity_association.aws_lb_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | +| [aws_eks_pod_identity_association.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | | [aws_eks_pod_identity_association.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | | [aws_eks_pod_identity_association.ebs_csi_driver](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | | [aws_eks_pod_identity_association.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_pod_identity_association) | resource | @@ -235,6 +342,7 @@ No modules. | [aws_iam_role.addon](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.aws_lb_controller](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.capability](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.ebs_csi_driver](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.eks_automode_nodes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | @@ -247,6 +355,7 @@ No modules. | [aws_iam_role.sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy.argocd_codeconnections](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_iam_role_policy.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_iam_role_policy.external_dns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | | [aws_iam_role_policy.kinesis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | @@ -286,6 +395,9 @@ No modules. | [aws_iam_policy_document.aws_lb_controller_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.aws_lb_controller_assume_role_pod_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.capability_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cluster_autoscaler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cluster_autoscaler_assume_role_irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.cluster_autoscaler_assume_role_pod_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.dynamodb_assume_role_irsa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.dynamodb_assume_role_pod_identity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -335,6 +447,9 @@ No modules. | [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events in the CloudWatch log group | `number` | `14` | no | | [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | Additional tags to apply to the CloudWatch log group | `map(string)` | `{}` | no | | [cluster\_authentication\_mode](#input\_cluster\_authentication\_mode) | Authentication mode for the EKS cluster. Valid values: CONFIG\_MAP, API, API\_AND\_CONFIG\_MAP. Defaults to API\_AND\_CONFIG\_MAP when capabilities are enabled, otherwise CONFIG\_MAP. | `string` | `"API_AND_CONFIG_MAP"` | no | +| [cluster\_autoscaler\_identity\_type](#input\_cluster\_autoscaler\_identity\_type) | Identity type for Cluster Autoscaler. Use 'pod\_identity' to create Pod Identity association; requires eks-pod-identity-agent addon. | `string` | `"irsa"` | no | +| [cluster\_autoscaler\_namespace](#input\_cluster\_autoscaler\_namespace) | Kubernetes namespace for Cluster Autoscaler service account. Used for IRSA OIDC condition and when cluster\_autoscaler\_identity\_type = 'pod\_identity'. | `string` | `"kube-system"` | no | +| [cluster\_autoscaler\_service\_account](#input\_cluster\_autoscaler\_service\_account) | Kubernetes service account name for Cluster Autoscaler. Used for IRSA OIDC condition and when cluster\_autoscaler\_identity\_type = 'pod\_identity'. | `string` | `"cluster-autoscaler"` | no | | [cluster\_encryption\_config\_key\_arn](#input\_cluster\_encryption\_config\_key\_arn) | ARN of the KMS key to use for encrypting Kubernetes secrets | `string` | `null` | no | | [cluster\_encryption\_config\_resources](#input\_cluster\_encryption\_config\_resources) | List of strings with resources to be encrypted. Valid values: secrets | `list(string)` |
[
"secrets"
]
| no | | [cluster\_ip\_family](#input\_cluster\_ip\_family) | IP family for the EKS cluster. Valid values: ipv4, ipv6 | `string` | `"ipv4"` | no | @@ -350,6 +465,7 @@ No modules. | [eks\_managed\_node\_groups](#input\_eks\_managed\_node\_groups) | Map of EKS managed node group configurations |
map(object({
name = optional(string)
ami_type = optional(string, "AL2023_x86_64_STANDARD")
instance_types = optional(list(string), ["t3.medium"])
min_size = optional(number, 1)
max_size = optional(number, 3)
desired_size = optional(number, 2)
disk_size = optional(number, 20)
subnet_ids = optional(list(string))
enable_bootstrap_user_data = optional(bool, true)
metadata_options = optional(object({
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}))
labels = optional(map(string), {})
tags = optional(map(string), {})
}))
| `{}` | no | | [enable\_automode](#input\_enable\_automode) | Enable EKS Auto Mode. Mutually exclusive with eks\_managed\_node\_groups. | `bool` | `false` | no | | [enable\_aws\_load\_balancer\_controller](#input\_enable\_aws\_load\_balancer\_controller) | Whether to create IAM role for AWS Load Balancer Controller (IRSA) | `bool` | `false` | no | +| [enable\_cluster\_autoscaler\_iam](#input\_enable\_cluster\_autoscaler\_iam) | Whether to create IAM role for Cluster Autoscaler (IRSA or Pod Identity per cluster\_autoscaler\_identity\_type). For EC2 managed node groups only; not supported with enable\_automode. When true, adds k8s.io/cluster-autoscaler/* tags to managed node groups for ASG autodiscovery. | `bool` | `false` | no | | [enable\_cluster\_creator\_admin\_permissions](#input\_enable\_cluster\_creator\_admin\_permissions) | Indicates whether or not to add the cluster creator (the identity used by Terraform) as an administrator via access entry | `bool` | `false` | no | | [enable\_dynamodb\_access](#input\_enable\_dynamodb\_access) | Whether to create IAM roles for DynamoDB access (IRSA or Pod Identity per dynamodb\_identity\_type). One role per dynamodb\_access entry. | `bool` | `false` | no | | [enable\_ebs\_csi\_driver](#input\_enable\_ebs\_csi\_driver) | Whether to create IAM role for EBS CSI driver (IRSA or Pod Identity per ebs\_csi\_driver\_identity\_type) | `bool` | `false` | no | @@ -401,9 +517,11 @@ No modules. | [cluster\_addons](#output\_cluster\_addons) | Map of attribute maps for all EKS cluster addons enabled | | [cluster\_arn](#output\_cluster\_arn) | The Amazon Resource Name (ARN) of the cluster | | [cluster\_auth\_token](#output\_cluster\_auth\_token) | Token to authenticate with the EKS cluster | +| [cluster\_autoscaler\_role\_arn](#output\_cluster\_autoscaler\_role\_arn) | IAM role ARN for Cluster Autoscaler (when enable\_cluster\_autoscaler\_iam). For IRSA, annotate the cluster-autoscaler ServiceAccount with this ARN. | | [cluster\_ca\_certificate](#output\_cluster\_ca\_certificate) | Decoded certificate data required to communicate with the cluster | | [cluster\_capabilities](#output\_cluster\_capabilities) | Map of EKS capability resources (ACK, KRO, Argo CD) keyed by capability name | | [cluster\_capability\_role\_arns](#output\_cluster\_capability\_role\_arns) | Map of IAM role ARNs for EKS capabilities created by the module (keyed by capability name). Use for ACK controller config or external reference. | +| [cluster\_capability\_role\_names](#output\_cluster\_capability\_role\_names) | Map of IAM role names for EKS capabilities created by the module (same keys as cluster\_capability\_role\_arns). Use for aws\_iam\_role\_policy.role and similar APIs that expect a role name. | | [cluster\_certificate\_authority\_data](#output\_cluster\_certificate\_authority\_data) | Base64 encoded certificate data required to communicate with the cluster | | [cluster\_endpoint](#output\_cluster\_endpoint) | Endpoint for your Kubernetes API server | | [cluster\_iam\_role\_arn](#output\_cluster\_iam\_role\_arn) | Cluster IAM role ARN | @@ -412,7 +530,7 @@ No modules. | [cluster\_name](#output\_cluster\_name) | The name of the EKS cluster | | [cluster\_oidc\_issuer\_url](#output\_cluster\_oidc\_issuer\_url) | The URL on the EKS cluster for the OpenID Connect identity provider | | [cluster\_platform\_version](#output\_cluster\_platform\_version) | Platform version for the cluster | -| [cluster\_pod\_identity\_associations](#output\_cluster\_pod\_identity\_associations) | Map of EKS Pod Identity associations (addon, ALB controller, External DNS, EBS CSI driver, Secrets Manager, S3) when using Pod Identity | +| [cluster\_pod\_identity\_associations](#output\_cluster\_pod\_identity\_associations) | Map of EKS Pod Identity associations (addon, ALB controller, External DNS, EBS CSI driver, Cluster Autoscaler, Secrets Manager, S3) when using Pod Identity | | [cluster\_primary\_security\_group\_id](#output\_cluster\_primary\_security\_group\_id) | Cluster security group that was created by Amazon EKS for the cluster | | [cluster\_security\_group\_id](#output\_cluster\_security\_group\_id) | ID of the cluster security group | | [cluster\_service\_cidr](#output\_cluster\_service\_cidr) | The IPv4 CIDR block where Kubernetes pod and service IP addresses are assigned from | @@ -438,6 +556,7 @@ No modules. | [secrets\_manager\_role\_arn](#output\_secrets\_manager\_role\_arn) | IAM role ARN for Secrets Manager (when enabled) | | [sqs\_role\_arns](#output\_sqs\_role\_arns) | Map of IAM role ARNs for SQS access (when enable\_sqs\_access), keyed by namespace/service\_account | + ## Connecting to the Cluster @@ -480,13 +599,18 @@ terraform-aws-eks-basic/ ├── modules/ │ └── argocd-codeconnections/ # Optional: CodeStar Connections + IAM for Argo CD repo access └── examples/ - ├── basic/ # Basic usage example - ├── eks-capabilities/ # Platform engineering with capabilities - ├── eks-capabilities-private/ # Private-only EKS and Argo CD - ├── eks-auto-mode/ # EKS Auto Mode (no managed node groups) - ├── eks-fargate/ # Fargate (kube-system + app) + small MNG for Pod Identity agent + ├── minimal/ # Smallest possible usage (BYO VPC, Auto Mode) + ├── basic/ # Classic EC2 node groups with full feature set + ├── auto-mode/ # EKS Auto Mode with full feature set + ├── auto-mode-private/ # Auto Mode, private API + private Argo CD UI (VPCE) + ├── auto-mode-keda-workload/ # Auto Mode + SQS IAM for KEDA workers + ├── fargate/ # Fargate only (kube-system + app profiles) + ├── capabilities/ # Platform engineering with capabilities + ├── capabilities-private/ # Private-only EKS and Argo CD ├── pod-identity/ # Pod Identity for ALB, External DNS, EBS CSI, Secrets Manager - └── private-endpoint/ # EKS with private API endpoint + ├── private-endpoint/ # EKS with private API endpoint + ├── multi-cluster-shared-vpc/ # One VPC: classic EC2, Fargate, Auto Mode + optional Argo each + └── hub-spoke-argocd/ # Hub-and-spoke GitOps: Argo CD on hub, spoke clusters via access entries ``` ## License diff --git a/cluster-autoscaler-iam.tf b/cluster-autoscaler-iam.tf new file mode 100644 index 0000000..47eebc0 --- /dev/null +++ b/cluster-autoscaler-iam.tf @@ -0,0 +1,129 @@ + +################################################################################ +# Cluster Autoscaler IAM (IRSA or EKS Pod Identity) +# For EC2 managed node groups only — not compatible with enable_automode or +# Fargate-only clusters. Requires eks-pod-identity-agent addon when using pod_identity. +################################################################################ + +# IAM assume role policy document for Cluster Autoscaler (IRSA) +data "aws_iam_policy_document" "cluster_autoscaler_assume_role_irsa" { + count = var.enable_cluster_autoscaler_iam ? 1 : 0 + + statement { + effect = "Allow" + + principals { + type = "Federated" + identifiers = [aws_iam_openid_connect_provider.oidc_provider[0].arn] + } + + actions = ["sts:AssumeRoleWithWebIdentity"] + + condition { + test = "StringEquals" + variable = "${replace(aws_eks_cluster.this.identity[0].oidc[0].issuer, "https://", "")}:sub" + values = ["system:serviceaccount:${var.cluster_autoscaler_namespace}:${var.cluster_autoscaler_service_account}"] + } + + condition { + test = "StringEquals" + variable = "${replace(aws_eks_cluster.this.identity[0].oidc[0].issuer, "https://", "")}:aud" + values = ["sts.amazonaws.com"] + } + } +} + +# IAM assume role policy document for Cluster Autoscaler (EKS Pod Identity) +data "aws_iam_policy_document" "cluster_autoscaler_assume_role_pod_identity" { + count = var.enable_cluster_autoscaler_iam ? 1 : 0 + + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["pods.eks.amazonaws.com"] + } + + actions = [ + "sts:AssumeRole", + "sts:TagSession" + ] + } +} + +resource "aws_iam_role" "cluster_autoscaler" { + count = var.enable_cluster_autoscaler_iam ? 1 : 0 + + name = "${var.name}-cluster-autoscaler-role" + assume_role_policy = var.cluster_autoscaler_identity_type == "pod_identity" ? data.aws_iam_policy_document.cluster_autoscaler_assume_role_pod_identity[0].json : data.aws_iam_policy_document.cluster_autoscaler_assume_role_irsa[0].json + + tags = var.tags + + depends_on = [ + aws_iam_openid_connect_provider.oidc_provider + ] + + lifecycle { + precondition { + condition = !var.enable_cluster_autoscaler_iam || (!var.enable_automode && length(var.eks_managed_node_groups) > 0) + error_message = "enable_cluster_autoscaler_iam requires at least one eks_managed_node_groups entry and is not supported when enable_automode is true." + } + } +} + +# Permissions per kubernetes/autoscaler AWS provider README (autodiscovery + launch templates). +# Mutating autoscaling calls are restricted to ASGs tagged with eks:cluster-name matching this cluster. +data "aws_iam_policy_document" "cluster_autoscaler" { + count = var.enable_cluster_autoscaler_iam ? 1 : 0 + + statement { + sid = "AutoscalingEc2EksDescribe" + effect = "Allow" + actions = [ + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeScalingActivities", + "ec2:DescribeImages", + "ec2:DescribeInstanceTypes", + "ec2:DescribeLaunchTemplateVersions", + "ec2:GetInstanceTypesFromInstanceRequirements", + "eks:DescribeNodegroup", + ] + resources = ["*"] + } + + statement { + sid = "AutoscalingMutateClusterScoped" + effect = "Allow" + actions = [ + "autoscaling:SetDesiredCapacity", + "autoscaling:TerminateInstanceInAutoScalingGroup", + ] + resources = ["*"] + + condition { + test = "StringEquals" + variable = "aws:ResourceTag/eks:cluster-name" + values = [aws_eks_cluster.this.name] + } + } +} + +resource "aws_iam_role_policy" "cluster_autoscaler" { + count = var.enable_cluster_autoscaler_iam ? 1 : 0 + + name = "${var.name}-cluster-autoscaler" + role = aws_iam_role.cluster_autoscaler[0].id + policy = data.aws_iam_policy_document.cluster_autoscaler[0].json +} + +resource "aws_eks_pod_identity_association" "cluster_autoscaler" { + count = var.enable_cluster_autoscaler_iam && var.cluster_autoscaler_identity_type == "pod_identity" ? 1 : 0 + + cluster_name = aws_eks_cluster.this.name + namespace = var.cluster_autoscaler_namespace + service_account = var.cluster_autoscaler_service_account + role_arn = aws_iam_role.cluster_autoscaler[0].arn +} diff --git a/examples/eks-auto-mode-keda-workload/README.md b/examples/auto-mode-keda-workload/README.md similarity index 100% rename from examples/eks-auto-mode-keda-workload/README.md rename to examples/auto-mode-keda-workload/README.md diff --git a/examples/eks-auto-mode-keda-workload/main.tf b/examples/auto-mode-keda-workload/main.tf similarity index 100% rename from examples/eks-auto-mode-keda-workload/main.tf rename to examples/auto-mode-keda-workload/main.tf diff --git a/examples/eks-auto-mode-keda-workload/outputs.tf b/examples/auto-mode-keda-workload/outputs.tf similarity index 100% rename from examples/eks-auto-mode-keda-workload/outputs.tf rename to examples/auto-mode-keda-workload/outputs.tf diff --git a/examples/eks-auto-mode-keda-workload/terraform.tfvars.example b/examples/auto-mode-keda-workload/terraform.tfvars.example similarity index 100% rename from examples/eks-auto-mode-keda-workload/terraform.tfvars.example rename to examples/auto-mode-keda-workload/terraform.tfvars.example diff --git a/examples/eks-auto-mode-keda-workload/variables.tf b/examples/auto-mode-keda-workload/variables.tf similarity index 100% rename from examples/eks-auto-mode-keda-workload/variables.tf rename to examples/auto-mode-keda-workload/variables.tf diff --git a/examples/eks-auto-mode-private/README.md b/examples/auto-mode-private/README.md similarity index 96% rename from examples/eks-auto-mode-private/README.md rename to examples/auto-mode-private/README.md index 8de9c91..47be277 100644 --- a/examples/eks-auto-mode-private/README.md +++ b/examples/auto-mode-private/README.md @@ -39,7 +39,7 @@ The API is private-only. Use kubectl from **inside the VPC** (e.g. a jumphost, E aws eks update-kubeconfig --name $(terraform output -raw cluster_name) --region $(terraform output -raw aws_region) ``` -Use `manifests/scale-test.yaml` to trigger Auto Mode scaling (deploy a workload and watch nodes appear). +Deploy workloads from your GitOps repo or with `kubectl`/Helm to trigger Auto Mode scaling; this example does not include Kubernetes manifests. ## Argo CD and CodeConnections diff --git a/examples/eks-auto-mode-private/main.tf b/examples/auto-mode-private/main.tf similarity index 95% rename from examples/eks-auto-mode-private/main.tf rename to examples/auto-mode-private/main.tf index f4cebed..e5f04bc 100644 --- a/examples/eks-auto-mode-private/main.tf +++ b/examples/auto-mode-private/main.tf @@ -74,7 +74,7 @@ module "argocd_connections" { count = var.argocd_idc_instance_arn != null ? 1 : 0 - argocd_capability_role_arn = module.eks.cluster_capability_role_arns["argocd"] + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] connections = [ { name = "github", provider_type = "GitHub" } diff --git a/examples/eks-auto-mode-private/outputs.tf b/examples/auto-mode-private/outputs.tf similarity index 100% rename from examples/eks-auto-mode-private/outputs.tf rename to examples/auto-mode-private/outputs.tf diff --git a/examples/eks-auto-mode-private/terraform.tfvars.example b/examples/auto-mode-private/terraform.tfvars.example similarity index 100% rename from examples/eks-auto-mode-private/terraform.tfvars.example rename to examples/auto-mode-private/terraform.tfvars.example diff --git a/examples/eks-auto-mode-private/variables.tf b/examples/auto-mode-private/variables.tf similarity index 100% rename from examples/eks-auto-mode-private/variables.tf rename to examples/auto-mode-private/variables.tf diff --git a/examples/eks-auto-mode/.terraform.lock.hcl b/examples/auto-mode/.terraform.lock.hcl similarity index 81% rename from examples/eks-auto-mode/.terraform.lock.hcl rename to examples/auto-mode/.terraform.lock.hcl index 5741225..70f4507 100644 --- a/examples/eks-auto-mode/.terraform.lock.hcl +++ b/examples/auto-mode/.terraform.lock.hcl @@ -44,26 +44,6 @@ provider "registry.terraform.io/hashicorp/helm" { ] } -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = "~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - provider "registry.terraform.io/hashicorp/time" { version = "0.13.1" constraints = ">= 0.9.0" diff --git a/examples/eks-auto-mode/README.md b/examples/auto-mode/README.md similarity index 57% rename from examples/eks-auto-mode/README.md rename to examples/auto-mode/README.md index 9d51673..fe2bde1 100644 --- a/examples/eks-auto-mode/README.md +++ b/examples/auto-mode/README.md @@ -29,41 +29,18 @@ This example creates an EKS cluster with **EKS Auto Mode** enabled. Compute is m - No `eks_managed_node_groups` (mutually exclusive with Auto Mode) - No addons (CoreDNS, vpc-cni, kube-proxy, and **Pod Identity Agent** are built into Auto Mode; you do not install them). -## Testing Auto Mode scaling +## Verifying Auto Mode scaling -To confirm that Auto Mode provisions nodes when you schedule workloads: +This repo only contains Terraform. After `apply`, use your own workloads (e.g. GitOps in a separate repository) or a quick ad hoc deployment to confirm nodes appear when pods are scheduled: -1. Configure kubectl (run from the example dir): +```bash +aws eks update-kubeconfig --name $(terraform output -raw cluster_name) --region $(terraform output -raw aws_region) +kubectl get nodes -w +# In another terminal, deploy something (your manifests, Helm chart, or e.g.): +# kubectl create deployment demo --image=nginx --replicas=5 +``` - ```bash - aws eks update-kubeconfig --name $(terraform output -raw cluster_name) --region $(terraform output -raw aws_region) - ``` - -2. Deploy a minimal workload (triggers node provisioning): - - ```bash - kubectl apply -f manifests/scale-test.yaml - ``` - -3. In one terminal, watch nodes (Auto Mode will add nodes as pods are scheduled): - - ```bash - kubectl get nodes -w - ``` - -4. In another, watch pods: - - ```bash - kubectl get pods -A -w - ``` - -5. Scale up to see more nodes come in: - - ```bash - kubectl scale deployment scale-test -n default --replicas=10 - ``` - -You should see new nodes appear within a few minutes as pending pods get scheduled. When done, scale to 0 or delete the deployment to allow nodes to scale down. +You should see nodes join as pods become schedulable. This example does not ship Kubernetes YAML. ## Notes diff --git a/examples/auto-mode/main.tf b/examples/auto-mode/main.tf new file mode 100644 index 0000000..b076ed4 --- /dev/null +++ b/examples/auto-mode/main.tf @@ -0,0 +1,173 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +module "vpc" { + source = "cloudbuildlab/vpc/aws" + + vpc_name = var.cluster_name + vpc_cidr = "10.0.0.0/16" + availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] + + public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] + private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] + + create_igw = true + nat_gateway_type = "single" + + enable_eks_tags = true + eks_cluster_name = var.cluster_name + tags = var.tags +} + +# EKS Auto Mode — compute is auto-provisioned by AWS; no managed node groups. +# Mutually exclusive with eks_managed_node_groups. +# Built-in: CoreDNS, vpc-cni, kube-proxy, Pod Identity Agent, block storage, load balancing. +module "eks" { + source = "../../" + + name = var.cluster_name + kubernetes_version = var.cluster_version + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnet_ids + + # ── API Endpoint Access ────────────────────────────────────────────────────── + # Public (default): API reachable over the internet. + # endpoint_public_access = true + # public_access_cidrs = ["0.0.0.0/0"] # tighten to your egress IP in production + # + # Private-only: API only reachable from within the VPC or via VPN. + # endpoint_public_access = false + # private_access_cidrs = ["10.0.0.0/8"] + # (Terraform runner must be inside the VPC or connected via VPN) + # + # Both (recommended for production): + # endpoint_public_access = true + # public_access_cidrs = ["1.2.3.4/32"] # your egress IP only + # private_access_cidrs = ["10.0.0.0/8"] + # ──────────────────────────────────────────────────────────────────────────── + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + + cloudwatch_log_group_force_destroy = true + + # Auto Mode has built-in addons; do not install CoreDNS, vpc-cni, kube-proxy, or Pod Identity Agent here. + addons = {} + + # ── EKS Capabilities (ACK, KRO, Argo CD) ──────────────────────────────────── + # ACK and KRO are always enabled. + # Argo CD is enabled only when argocd_idc_instance_arn is set + # (AWS IAM Identity Center is required by the EKS Argo CD capability). + # + # To enable Argo CD, set in terraform.tfvars: + # argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxx" + # argocd_rbac_role_mappings = [{ role = "ADMIN", identity = [{ type = "SSO_USER", id = "..." }] }] + # + # Argo CD UI endpoint (argocd_vpce_ids): + # Leave empty [] for a publicly reachable Argo CD UI. + # Set a VPC interface endpoint ID to restrict access to within the VPC. + # + # KNOWN BUG — Argo CD public ↔ private UI switching does not apply in-place. + # Workaround: remove argocd from capabilities, apply (destroys it), then re-add + # with the correct argocd_vpce_ids and apply again. + # Note: argocd_vpce_ids controls the Argo CD UI only — NOT the Kubernetes API endpoint. + # ──────────────────────────────────────────────────────────────────────────── + capabilities = merge( + { + ack = {} + kro = {} + }, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [ + { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + ] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { + idc_instance_arn = var.argocd_idc_instance_arn + idc_region = var.aws_region + } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { + vpce_ids = var.argocd_vpce_ids + } + } + } + } + } : {} + ) + + # ── EBS CSI Driver ─────────────────────────────────────────────────────────── + # Pod Identity is supported on Auto Mode (agent is built in). + # ──────────────────────────────────────────────────────────────────────────── + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + # ── Secrets Manager (Pod Identity) ────────────────────────────────────────── + # Grants named service accounts access to Secrets Manager via Pod Identity. + # Supported on Auto Mode; NOT supported on Fargate (use IRSA there instead). + # Remove this block if not using Secrets Store CSI Driver. + # ──────────────────────────────────────────────────────────────────────────── + enable_secrets_manager = true + secrets_manager_identity_type = "pod_identity" + secrets_manager_associations = [ + { namespace = "sm-operator-system", service_account = "awssm-sync" }, + { namespace = "atlantis-1", service_account = "awssm-sync" } + ] + secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] + + tags = var.tags +} + +# ── Argo CD CodeConnections (GitHub) ───────────────────────────────────────── +# Creates a GitHub CodeConnection and grants UseConnection + GetConnection to +# the Argo CD capability IAM role. Active only when argocd_idc_instance_arn is set. +# +# IMPORTANT: Only connections listed here are permitted in the IAM policy. +# Argo CD Application repoURLs must use the UUID from output connection_ids[""]. +# Using any other connection UUID will cause AccessDeniedException in Argo CD. +# +# After apply: complete the GitHub OAuth handshake in the AWS Console — new +# connections are in PENDING state until authorised. +# ───────────────────────────────────────────────────────────────────────────── +module "argocd_connections" { + source = "../../modules/argocd-codeconnections" + + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] + + connections = [ + { name = "github-${var.cluster_name}", provider_type = "GitHub" } + ] + + tags = var.tags +} diff --git a/examples/auto-mode/outputs.tf b/examples/auto-mode/outputs.tf new file mode 100644 index 0000000..46119e4 --- /dev/null +++ b/examples/auto-mode/outputs.tf @@ -0,0 +1,54 @@ +output "aws_region" { + description = "AWS region (for aws eks update-kubeconfig)" + value = var.aws_region +} + +output "vpc_id" { + description = "ID of the VPC" + value = module.vpc.vpc_id +} + +output "public_subnet_ids" { + description = "IDs of the public subnets" + value = module.vpc.public_subnet_ids +} + +output "private_subnet_ids" { + description = "IDs of the private subnets" + value = module.vpc.private_subnet_ids +} + +output "cluster_name" { + description = "Name of the EKS cluster" + value = module.eks.cluster_name +} + +output "cluster_endpoint" { + description = "Endpoint for EKS control plane" + value = module.eks.cluster_endpoint +} + +output "cluster_version" { + description = "Kubernetes version of the EKS cluster" + value = module.eks.cluster_version +} + +output "oidc_provider_arn" { + description = "ARN of the EKS OIDC provider" + value = module.eks.oidc_provider_arn +} + +output "argocd_connection_ids" { + description = "Map of CodeConnection UUIDs keyed by connection name. Use these UUIDs in Argo CD Application repoURLs." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].connection_ids : null +} + +output "argocd_codeconnections_iam_role_name" { + description = "IAM role name that has CodeConnections UseConnection/GetConnection attached (Argo CD capability role)." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_role_name : null +} + +output "argocd_codeconnections_iam_policy_name" { + description = "Inline policy name on the Argo CD role for CodeConnections." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_policy_name : null +} diff --git a/examples/eks-auto-mode/terraform.tfvars.example b/examples/auto-mode/terraform.tfvars.example similarity index 100% rename from examples/eks-auto-mode/terraform.tfvars.example rename to examples/auto-mode/terraform.tfvars.example diff --git a/examples/auto-mode/variables.tf b/examples/auto-mode/variables.tf new file mode 100644 index 0000000..b35b57f --- /dev/null +++ b/examples/auto-mode/variables.tf @@ -0,0 +1,93 @@ +variable "aws_region" { + description = "AWS region for resources" + type = string + default = "ap-southeast-2" +} + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "eks-automode" +} + +variable "cluster_version" { + description = "Kubernetes version for the EKS cluster (Auto Mode requires 1.29+)" + type = string + default = "1.35" +} + +# ── API Endpoint Access ────────────────────────────────────────────────────── + +variable "endpoint_public_access" { + description = "Whether the EKS API server endpoint is publicly accessible" + type = bool + default = true +} + +variable "public_access_cidrs" { + description = "CIDR blocks allowed to reach the public EKS API endpoint. Defaults to unrestricted; tighten for production." + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "private_access_cidrs" { + description = "CIDR blocks allowed to reach the private EKS API endpoint (within the VPC). Only relevant when endpoint_public_access = false or both modes are active." + type = list(string) + default = [] +} + +# ── Argo CD ────────────────────────────────────────────────────────────────── + +variable "argocd_idc_instance_arn" { + description = "ARN of the AWS IAM Identity Center instance for Argo CD capability. When null, the Argo CD capability and CodeConnections submodule are not created." + type = string + default = null +} + +variable "argocd_rbac_role_mappings" { + description = "Argo CD RBAC role mappings: IdC users/groups to Argo CD roles (ADMIN, EDITOR, VIEWER). Fixes 'No users or groups assigned' and Unauthorized when loading applications." + type = list(object({ + role = string # ADMIN | EDITOR | VIEWER + identity = list(object({ + type = string # SSO_USER or SSO_GROUP + id = string # IAM Identity Center user or group ID + })) + })) + default = [] +} + +variable "argocd_vpce_ids" { + description = "VPC interface endpoint ID(s) for the Argo CD UI (com.amazonaws..eks-capabilities). Leave empty for a public UI. See KNOWN BUG in main.tf regarding in-place switching." + type = list(string) + default = [] +} + +# ── Access ─────────────────────────────────────────────────────────────────── + +variable "access_entries" { + description = "Map of IAM principals to grant cluster access to" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "tags" { + description = "Map of tags to apply to all resources" + type = map(string) + default = { + Environment = "dev" + ManagedBy = "terraform" + } +} diff --git a/examples/basic/.terraform.lock.hcl b/examples/basic/.terraform.lock.hcl index 1685c23..70f4507 100644 --- a/examples/basic/.terraform.lock.hcl +++ b/examples/basic/.terraform.lock.hcl @@ -2,70 +2,51 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "6.31.0" + version = "6.39.0" constraints = ">= 6.0.0" hashes = [ - "h1:yrswE/HFLKpPvs2622EyUPWv87ir0OuixxhrESWTt4g=", - "zh:0184b83f61dfb2f90f051d6a10e85d554809eb7dec13c49000bc884cfd1e956d", - "zh:16f76019ad67f0dfafea2c65b17bd1aa289cb5c275521df71337e23b08af6fec", - "zh:296ebaa261729b78159694e3ca709735c5c67913d6107c7e1abd4d1e9b05fc6b", - "zh:6b4c37bd7e8abca1b428903212de731b04695dcc59e2ba2acefc3d936b36c4dc", - "zh:6f49e2f7464dbb9d6911dd32951637f589d21a5c3c9a7c5056837701977ec803", - "zh:6fc4095e59286dd83e9528346390b0b07b3bffa1d46b50027c9e080352207626", - "zh:98816c0c5d1b956b564c2d2feb423fdf4eb3e476a6c5202668a285ff3b2d6910", + "h1:jweey4Iefm/DuuBg84saQ8vz5IO3vC6hDFTU/eGdmBI=", + "zh:00c3e3c38063ff629d6fdbce04e9ac2e241566e0f5ad5399c335f0abdefd7bff", + "zh:148f95b62791080537d926b9d2f5d8457cca45921d9b1019d03ceb3ab93bf9db", + "zh:203da629ed5191dd5d7aa3427a5d1d1a83eed5c1b0114166897206973f0d0fd0", + "zh:21923eedbc60b4f68c8d717b951d16b0b1bbf31d66330c7be228869bec18f7ce", + "zh:26226f02e3661b3d071c01601b654a308b29d21758b75692bec66f70c6f6b945", + "zh:271c7c6fadcd8ac7ed37c11e61c0f374773eaaa5293703499f8a0f75830060e0", + "zh:46e319a8888dc50ed8d26a1cbee9637f529112a88f5d44decc8f1d10ef968ffe", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:a70b34fb8d5a7d3b3823046938f3c9b0527afa93d02086b3d87ffa668c9a350e", - "zh:c24c0a58a8301d13cb4c27738840c8e7e0f29563ccf8d6b5ca1c87fcf21bdf89", - "zh:c95d44b2baea56b03198acaaf50f9196504d0207118e1afca7d70b5840315dc4", - "zh:db5a7692e2bde721a37b83f89eb9886715dbd17eb45858b1b58b8f7903ce5144", - "zh:db706b23a652e06c6c3f5de1da70e55a20a4fc2f73c01c24f7b9cd39a9a35f56", - "zh:fb781119fa98d8b0318ffb26ef013d5e674637e8f6a36b4b9c2742f24c022538", - "zh:fc459573b260a5a295d5fed442bf4f44fe034a0702ca22a65d320bd3b3e70eb5", + "zh:a3c3ca09cdbf3b9a3f892a23c000ff04772bdf19f626959ea83d0803c8fd2350", + "zh:a5fa6515ffc3c815e0d2204d67e838f5bad8635009dab85211d166c7ae729d2c", + "zh:c0807566b4ddde8390f50c5475464103f066bc7f511a6c0be762d75cb6d1a078", + "zh:da754a529fd0e06ac372f62d88566f85a8c4bcec7ee9a231b65e0a0148165e63", + "zh:dcb768e48363a9f4dffaf2dc7d01f1877285528925ec50de6335286298e37e1d", + "zh:eac9de9d123c679ea3035199fb9c588a08cda281cbabf948dc696e2a1a1b9063", + "zh:fef276b6331c663ca0e60dc7f637b2b8244825b8c9bc721481957e58f74ffb4f", ] } provider "registry.terraform.io/hashicorp/helm" { - version = "2.17.0" - constraints = "~> 2.13" + version = "3.1.1" + constraints = ">= 2.13.0" hashes = [ - "h1:K5FEjxvDnxb1JF1kG1xr8J3pNGxoaR3Z0IBG9Csm/Is=", - "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", - "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", - "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", - "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", - "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", - "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", - "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", - "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", - "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", - "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", - "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = "~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" + version = "0.13.1" + constraints = ">= 0.9.0" hashes = [ "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", @@ -85,7 +66,7 @@ provider "registry.terraform.io/hashicorp/time" { provider "registry.terraform.io/hashicorp/tls" { version = "4.2.1" - constraints = "~> 4.0" + constraints = ">= 4.0.0, ~> 4.0" hashes = [ "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", diff --git a/examples/basic/README.md b/examples/basic/README.md index ff350d1..4157a89 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -6,12 +6,7 @@ This example demonstrates how to create a basic EKS cluster with EC2 managed nod 1. **Configure variables:** - Edit `terraform.tfvars` to set your cluster name and access entries: - - ```hcl - cluster_name = "my-eks-cluster" - aws_region = "ap-southeast-2" - ``` + Edit `terraform.tfvars` (gitignored) or copy from `terraform.tfvars.example`. Cluster creator admin is enabled for the IAM identity that runs Terraform, so `access_entries` can stay `{}` at first. For additional principals, use real IAM ARNs in `access_entries` (12-digit account ID in the ARN; placeholder text like `ACCOUNT_ID` is rejected by AWS). 2. **Initialize and apply:** @@ -25,7 +20,7 @@ This example demonstrates how to create a basic EKS cluster with EC2 managed nod ### Variables -- `cluster_name`: Name of the EKS cluster (default: `cltest`) +- `cluster_name`: Name of the EKS cluster (default: `eks-basic`) - `aws_region`: AWS region for resources (default: `ap-southeast-2`) - `cluster_version`: Kubernetes version (default: `1.35`) - `access_entries`: Map of IAM users/roles with cluster access (default: `{}`) diff --git a/examples/basic/main.tf b/examples/basic/main.tf index d189a80..3c32052 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -6,14 +6,6 @@ terraform { source = "hashicorp/aws" version = ">= 6.0" } - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.30" - } - helm = { - source = "hashicorp/helm" - version = "~> 2.13" - } tls = { source = "hashicorp/tls" version = "~> 4.0" @@ -25,47 +17,29 @@ provider "aws" { region = var.aws_region } -provider "kubernetes" { - host = module.eks.cluster_endpoint - cluster_ca_certificate = module.eks.cluster_ca_certificate - token = module.eks.cluster_auth_token -} - -provider "helm" { - kubernetes { - host = module.eks.cluster_endpoint - cluster_ca_certificate = module.eks.cluster_ca_certificate - token = module.eks.cluster_auth_token - } -} - -locals { - base_name = var.cluster_name - name = var.cluster_name - tags = var.tags -} - module "vpc" { source = "cloudbuildlab/vpc/aws" - vpc_name = local.base_name + vpc_name = var.cluster_name vpc_cidr = "10.0.0.0/16" availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] - # Enable Internet Gateway & NAT Gateway create_igw = true nat_gateway_type = "single" enable_eks_tags = true - eks_cluster_name = local.name - - tags = local.tags + eks_cluster_name = var.cluster_name + tags = var.tags } -# Example: Basic EKS cluster with EC2 node groups +# Classic EKS — EC2 managed node groups. Use when Auto Mode is not available +# or when you need full control over node configuration (instance types, AMIs, etc.). +# +# Pod Identity is supported on EC2 nodes (Pod Identity agent addon required). +# Mutually exclusive with enable_automode. module "eks" { source = "../../" @@ -74,10 +48,31 @@ module "eks" { vpc_id = module.vpc.vpc_id subnet_ids = concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids) - endpoint_public_access = true + # ── API Endpoint Access ────────────────────────────────────────────────────── + # Public (default): API reachable over the internet. + # endpoint_public_access = true + # public_access_cidrs = ["0.0.0.0/0"] # tighten to your egress IP in production + # + # Private-only: API only reachable from within the VPC or via VPN. + # endpoint_public_access = false + # private_access_cidrs = ["10.0.0.0/8"] + # (Terraform runner must be inside the VPC or connected via VPN) + # + # Both (recommended for production): + # endpoint_public_access = true + # public_access_cidrs = ["1.2.3.4/32"] # your egress IP only + # private_access_cidrs = ["10.0.0.0/8"] + # ──────────────────────────────────────────────────────────────────────────── + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs access_entries = var.access_entries + enable_cluster_creator_admin_permissions = true + + cloudwatch_log_group_force_destroy = true + addons = { coredns = { addon_version = "v1.13.2-eksbuild.1" @@ -107,9 +102,9 @@ module "eks" { ami_type = "AL2023_x86_64_STANDARD" instance_types = ["t3a.large"] - min_size = 3 - max_size = 3 - desired_size = 3 + min_size = 2 + max_size = 4 + desired_size = 2 metadata_options = { http_endpoint = "enabled" @@ -118,4 +113,114 @@ module "eks" { } } } + + # ── EKS Capabilities (ACK, KRO, Argo CD) ──────────────────────────────────── + # ACK and KRO are always enabled. + # Argo CD is enabled only when argocd_idc_instance_arn is set + # (AWS IAM Identity Center is required by the EKS Argo CD capability). + # + # To enable Argo CD, set in terraform.tfvars: + # argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxx" + # argocd_rbac_role_mappings = [{ role = "ADMIN", identity = [{ type = "SSO_USER", id = "..." }] }] + # + # Argo CD UI endpoint (argocd_vpce_ids): + # Leave empty [] for a publicly reachable Argo CD UI. + # Set a VPC interface endpoint ID to restrict access to within the VPC. + # + # KNOWN BUG — Argo CD public ↔ private UI switching does not apply in-place. + # Workaround: remove argocd from capabilities, apply (destroys it), then re-add + # with the correct argocd_vpce_ids and apply again. + # Note: argocd_vpce_ids controls the Argo CD UI only — NOT the Kubernetes API endpoint. + # ──────────────────────────────────────────────────────────────────────────── + capabilities = merge( + { + ack = {} + kro = {} + }, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [ + { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + ] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { + idc_instance_arn = var.argocd_idc_instance_arn + idc_region = var.aws_region + } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { + vpce_ids = var.argocd_vpce_ids + } + } + } + } + } : {} + ) + + # ── EBS CSI Driver (Pod Identity) ─────────────────────────────────────────── + # Pod Identity is supported on EC2 nodes (eks-pod-identity-agent addon above is required). + # ──────────────────────────────────────────────────────────────────────────── + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + # ── Karpenter (Pod Identity) ──────────────────────────────────────────────── + # Node autoscaling via Karpenter; requires eks-pod-identity-agent addon above. + # Tags private subnets for karpenter.sh/discovery (see karpenter_discovery_subnet_ids). + # ──────────────────────────────────────────────────────────────────────────── + enable_karpenter = true + karpenter_identity_type = "pod_identity" + karpenter_discovery_subnet_ids = module.vpc.private_subnet_ids + + # ── Cluster Autoscaler (Pod Identity) ─────────────────────────────────────── + # IAM for in-cluster Cluster Autoscaler (GitOps manifest). EC2 managed node + # groups only — not for Auto Mode or Fargate. Requires eks-pod-identity-agent. + # ──────────────────────────────────────────────────────────────────────────── + # enable_cluster_autoscaler_iam = true + # cluster_autoscaler_identity_type = "pod_identity" + + # ── Secrets Manager (Pod Identity) ────────────────────────────────────────── + # Grants named service accounts access to Secrets Manager via Pod Identity. + # Pod Identity is supported on EC2 nodes; NOT supported on Fargate (use IRSA there). + # Remove this block if not using Secrets Store CSI Driver. + # ──────────────────────────────────────────────────────────────────────────── + enable_secrets_manager = true + secrets_manager_identity_type = "pod_identity" + secrets_manager_associations = [ + { namespace = "sm-operator-system", service_account = "awssm-sync" }, + ] + secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] + + tags = var.tags +} + +# ── Argo CD CodeConnections (GitHub) ───────────────────────────────────────── +# Creates a GitHub CodeConnection and grants UseConnection + GetConnection to +# the Argo CD capability IAM role. Active only when argocd_idc_instance_arn is set. +# +# IMPORTANT: Only connections listed here are permitted in the IAM policy. +# Argo CD Application repoURLs must use the UUID from output connection_ids[""]. +# Using any other connection UUID will cause AccessDeniedException in Argo CD. +# +# After apply: complete the GitHub OAuth handshake in the AWS Console — new +# connections are in PENDING state until authorised. +# ───────────────────────────────────────────────────────────────────────────── +module "argocd_connections" { + source = "../../modules/argocd-codeconnections" + + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] + + connections = [ + { name = "github-${var.cluster_name}", provider_type = "GitHub" } + ] + + tags = var.tags } diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index 2ad2fb9..d563d1b 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -1,4 +1,8 @@ -# VPC Outputs +output "aws_region" { + description = "AWS region (for aws eks update-kubeconfig)" + value = var.aws_region +} + output "vpc_id" { description = "ID of the VPC" value = module.vpc.vpc_id @@ -14,7 +18,6 @@ output "private_subnet_ids" { value = module.vpc.private_subnet_ids } -# EKS Cluster Outputs output "cluster_name" { description = "Name of the EKS cluster" value = module.eks.cluster_name @@ -30,7 +33,32 @@ output "cluster_version" { value = module.eks.cluster_version } +output "karpenter_node_role_name" { + description = "IAM role name for Karpenter-provisioned nodes (must match EC2NodeClass spec.role in kube-infra)" + value = module.eks.karpenter_node_role_name +} + +output "karpenter_interruption_queue_name" { + description = "SQS queue name for Karpenter settings.interruptionQueue" + value = module.eks.karpenter_interruption_queue_name +} + output "oidc_provider_arn" { description = "ARN of the EKS OIDC provider" value = module.eks.oidc_provider_arn } + +output "argocd_connection_ids" { + description = "Map of CodeConnection UUIDs keyed by connection name. Use these UUIDs in Argo CD Application repoURLs." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].connection_ids : null +} + +output "argocd_codeconnections_iam_role_name" { + description = "IAM role name that has CodeConnections UseConnection/GetConnection attached (Argo CD capability role)." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_role_name : null +} + +output "argocd_codeconnections_iam_policy_name" { + description = "Inline policy name on the Argo CD role for CodeConnections." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_policy_name : null +} diff --git a/examples/basic/terraform.tfvars.example b/examples/basic/terraform.tfvars.example new file mode 100644 index 0000000..a557eff --- /dev/null +++ b/examples/basic/terraform.tfvars.example @@ -0,0 +1,55 @@ +# Copy to terraform.tfvars and set your values. +# Repo .gitignore ignores *.tfvars. +# +# Do not use literal ACCOUNT_ID in principal_arn — AWS requires a 12-digit account ID in the ARN. +# This example sets enable_cluster_creator_admin_permissions = true in main.tf so the Terraform +# caller has admin until you add access_entries for other IAM principals. + +aws_region = "ap-southeast-2" +cluster_name = "eks-basic" +cluster_version = "1.35" + +tags = { + Environment = "dev" + ManagedBy = "terraform" +} + +access_entries = {} + +# Uncomment and replace 123456789012 / role name with your account and IAM role: +# access_entries = { +# admin = { +# principal_arn = "arn:aws:iam::123456789012:role/MyAdminRole" +# type = "STANDARD" +# policy_associations = { +# admin = { +# policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" +# access_scope = { type = "cluster" } +# } +# } +# } +# +# # Optional: read-only +# # viewer = { +# # principal_arn = "arn:aws:iam::123456789012:role/ViewerRole" +# # type = "STANDARD" +# # policy_associations = { +# # view = { +# # policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy" +# # access_scope = { type = "cluster" } +# # } +# # } +# # } +# } + +# Optional: API endpoint (defaults: public, 0.0.0.0/0) +# endpoint_public_access = false +# private_access_cidrs = ["10.0.0.0/8"] +# public_access_cidrs = ["203.0.113.10/32"] + +# Optional: Argo CD capability (requires IAM Identity Center) +# argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxx" +# argocd_rbac_role_mappings = [ +# { role = "ADMIN", identity = [{ type = "SSO_USER", id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }] } +# ] +# argocd_vpce_ids = [] diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index 26e2a56..24bdff4 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -7,7 +7,7 @@ variable "aws_region" { variable "cluster_name" { description = "Name of the EKS cluster" type = string - default = "cltest" + default = "eks-basic" } variable "cluster_version" { @@ -16,8 +16,56 @@ variable "cluster_version" { default = "1.35" } +# ── API Endpoint Access ────────────────────────────────────────────────────── + +variable "endpoint_public_access" { + description = "Whether the EKS API server endpoint is publicly accessible" + type = bool + default = true +} + +variable "public_access_cidrs" { + description = "CIDR blocks allowed to reach the public EKS API endpoint. Defaults to unrestricted; tighten for production." + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "private_access_cidrs" { + description = "CIDR blocks allowed to reach the private EKS API endpoint (within the VPC)." + type = list(string) + default = [] +} + +# ── Argo CD ────────────────────────────────────────────────────────────────── + +variable "argocd_idc_instance_arn" { + description = "ARN of the AWS IAM Identity Center instance for Argo CD capability. When null, the Argo CD capability and CodeConnections submodule are not created." + type = string + default = null +} + +variable "argocd_rbac_role_mappings" { + description = "Argo CD RBAC role mappings: IdC users/groups to Argo CD roles (ADMIN, EDITOR, VIEWER). Fixes 'No users or groups assigned' and Unauthorized when loading applications." + type = list(object({ + role = string # ADMIN | EDITOR | VIEWER + identity = list(object({ + type = string # SSO_USER or SSO_GROUP + id = string # IAM Identity Center user or group ID + })) + })) + default = [] +} + +variable "argocd_vpce_ids" { + description = "VPC interface endpoint ID(s) for the Argo CD UI (com.amazonaws..eks-capabilities). Leave empty for a public UI. See KNOWN BUG in main.tf regarding in-place switching." + type = list(string) + default = [] +} + +# ── Access ─────────────────────────────────────────────────────────────────── + variable "access_entries" { - description = "Map of access entries to add to the cluster" + description = "Map of IAM principals to grant cluster access to" type = map(object({ kubernetes_groups = optional(list(string)) principal_arn = string @@ -39,7 +87,7 @@ variable "tags" { description = "Map of tags to apply to all resources" type = map(string) default = { - Environment = "example" + Environment = "dev" ManagedBy = "terraform" } } diff --git a/examples/eks-capabilities-private/README.md b/examples/capabilities-private/README.md similarity index 100% rename from examples/eks-capabilities-private/README.md rename to examples/capabilities-private/README.md diff --git a/examples/eks-capabilities-private/main.tf b/examples/capabilities-private/main.tf similarity index 100% rename from examples/eks-capabilities-private/main.tf rename to examples/capabilities-private/main.tf diff --git a/examples/eks-capabilities-private/outputs.tf b/examples/capabilities-private/outputs.tf similarity index 100% rename from examples/eks-capabilities-private/outputs.tf rename to examples/capabilities-private/outputs.tf diff --git a/examples/eks-capabilities-private/terraform.tfvars.example b/examples/capabilities-private/terraform.tfvars.example similarity index 100% rename from examples/eks-capabilities-private/terraform.tfvars.example rename to examples/capabilities-private/terraform.tfvars.example diff --git a/examples/eks-capabilities-private/variables.tf b/examples/capabilities-private/variables.tf similarity index 100% rename from examples/eks-capabilities-private/variables.tf rename to examples/capabilities-private/variables.tf diff --git a/examples/eks-capabilities/README.md b/examples/capabilities/README.md similarity index 100% rename from examples/eks-capabilities/README.md rename to examples/capabilities/README.md diff --git a/examples/eks-capabilities/main.tf b/examples/capabilities/main.tf similarity index 100% rename from examples/eks-capabilities/main.tf rename to examples/capabilities/main.tf diff --git a/examples/eks-capabilities/outputs.tf b/examples/capabilities/outputs.tf similarity index 100% rename from examples/eks-capabilities/outputs.tf rename to examples/capabilities/outputs.tf diff --git a/examples/eks-capabilities/terraform.tfvars.example b/examples/capabilities/terraform.tfvars.example similarity index 100% rename from examples/eks-capabilities/terraform.tfvars.example rename to examples/capabilities/terraform.tfvars.example diff --git a/examples/eks-capabilities/variables.tf b/examples/capabilities/variables.tf similarity index 100% rename from examples/eks-capabilities/variables.tf rename to examples/capabilities/variables.tf diff --git a/examples/eks-auto-mode-private/manifests/scale-test.yaml b/examples/eks-auto-mode-private/manifests/scale-test.yaml deleted file mode 100644 index c74ed76..0000000 --- a/examples/eks-auto-mode-private/manifests/scale-test.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Minimal workload to trigger Auto Mode scaling. -# Apply with: kubectl apply -f scale-test.yaml -# Watch nodes: kubectl get nodes -w -# Watch pods: kubectl get pods -A -w -# Scale up: kubectl scale deployment scale-test --replicas=10 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scale-test - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: scale-test - template: - metadata: - labels: - app: scale-test - spec: - containers: - - name: nginx - image: nginx:alpine - resources: - requests: - cpu: "100m" - memory: "64Mi" - limits: - cpu: "500m" - memory: "128Mi" diff --git a/examples/eks-auto-mode/main.tf b/examples/eks-auto-mode/main.tf deleted file mode 100644 index 1a38aed..0000000 --- a/examples/eks-auto-mode/main.tf +++ /dev/null @@ -1,80 +0,0 @@ -terraform { - required_version = ">= 1.6.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 6.0" - } - kubernetes = { - source = "hashicorp/kubernetes" - version = "~> 2.30" - } - tls = { - source = "hashicorp/tls" - version = "~> 4.0" - } - } -} - -provider "aws" { - region = var.aws_region -} - -module "vpc" { - source = "cloudbuildlab/vpc/aws" - - vpc_name = var.cluster_name - vpc_cidr = "10.0.0.0/16" - availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] - - public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] - private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] - - create_igw = true - nat_gateway_type = "single" - - enable_eks_tags = true - eks_cluster_name = var.cluster_name - tags = var.tags -} - -# EKS cluster with Auto Mode (no managed node groups; compute is auto-provisioned) -module "eks" { - source = "../../" - - name = var.cluster_name - kubernetes_version = var.cluster_version - vpc_id = module.vpc.vpc_id - - subnet_ids = module.vpc.private_subnet_ids - - endpoint_public_access = true - access_entries = var.access_entries - - enable_automode = true - automode_node_pools = ["system", "general-purpose"] - # eks_managed_node_groups not set (mutually exclusive with enable_automode) - - cloudwatch_log_group_force_destroy = true - - # CoreDNS, vpc-cni, kube-proxy, and Pod Identity Agent are built into Auto Mode; do not install as addons. - addons = {} - - # Pod Identity works out of the box on Auto Mode (agent is included). E.g. EBS CSI driver: - ebs_csi_driver_identity_type = "pod_identity" - enable_ebs_csi_driver = true - - # Pod Identity for Secrets Manager (for app pods mounting secrets via Secrets Store CSI Driver) - # sm-operator: awssm-sync SA in sm-operator-system fetches Bitwarden token from AWS Secrets Manager - # atlantis-1: awssm-sync SA in atlantis-1 fetches Bitwarden token from bitwarden/sm-operator/atlantis-1/* - enable_secrets_manager = true - secrets_manager_identity_type = "pod_identity" - secrets_manager_associations = [ - { namespace = "sm-operator-system", service_account = "awssm-sync" }, - { namespace = "atlantis-1", service_account = "awssm-sync" } - ] - secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] - - tags = var.tags -} diff --git a/examples/eks-auto-mode/manifests/scale-test.yaml b/examples/eks-auto-mode/manifests/scale-test.yaml deleted file mode 100644 index c74ed76..0000000 --- a/examples/eks-auto-mode/manifests/scale-test.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Minimal workload to trigger Auto Mode scaling. -# Apply with: kubectl apply -f scale-test.yaml -# Watch nodes: kubectl get nodes -w -# Watch pods: kubectl get pods -A -w -# Scale up: kubectl scale deployment scale-test --replicas=10 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: scale-test - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: scale-test - template: - metadata: - labels: - app: scale-test - spec: - containers: - - name: nginx - image: nginx:alpine - resources: - requests: - cpu: "100m" - memory: "64Mi" - limits: - cpu: "500m" - memory: "128Mi" diff --git a/examples/eks-auto-mode/variables.tf b/examples/eks-auto-mode/variables.tf deleted file mode 100644 index 0ea9221..0000000 --- a/examples/eks-auto-mode/variables.tf +++ /dev/null @@ -1,45 +0,0 @@ -variable "aws_region" { - description = "AWS region for resources" - type = string - default = "ap-southeast-2" -} - -variable "cluster_name" { - description = "Name of the EKS cluster" - type = string - default = "eks-automode" -} - -variable "cluster_version" { - description = "Kubernetes version for the EKS cluster (Auto Mode requires 1.29+)" - type = string - default = "1.35" -} - -variable "access_entries" { - description = "Map of access entries to add to the cluster" - type = map(object({ - kubernetes_groups = optional(list(string)) - principal_arn = string - type = optional(string, "STANDARD") - user_name = optional(string) - tags = optional(map(string), {}) - policy_associations = optional(map(object({ - policy_arn = string - access_scope = object({ - namespaces = optional(list(string)) - type = string - }) - })), {}) - })) - default = {} -} - -variable "tags" { - description = "Map of tags to apply to all resources" - type = map(string) - default = { - Environment = "dev" - ManagedBy = "terraform" - } -} diff --git a/examples/eks-capabilities-private/.terraform.lock.hcl b/examples/eks-capabilities-private/.terraform.lock.hcl deleted file mode 100644 index 375fb01..0000000 --- a/examples/eks-capabilities-private/.terraform.lock.hcl +++ /dev/null @@ -1,105 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "6.36.0" - constraints = ">= 6.0.0" - hashes = [ - "h1:r9icn1WEZVvEXiy6ZKexLzAPnXkkt+22jJ9WQYPfKB0=", - "zh:0eb4481315564aaeec4905a804fd0df22c40f509ad2af63615eeaa90abacf81c", - "zh:12c3cddc461a8dbaa04387fe83420b64c4c05cb5479d181674168ca7daefcc38", - "zh:1b55a09661e80acf6826faa38dd8fbff24c2ef620d2a0a16918491a222c55370", - "zh:269cb1a406d0cac762bce82119247395a0bbf0d4ad2492fb2ea5653b4f44bc05", - "zh:3bfb78e3345f0c3846e76578952a09fb5dda05d2d73e19473fb0af0000469a66", - "zh:3ead4f4388c7dd78ed198082a981746324da0d7a51460c9b455fd884d86fc82c", - "zh:44906654199991b3f1a21c6a984bc5f9f556ff4baa4e5f77e168968e941c2725", - "zh:4803d050d581b05b0fd0ae5cce95ec1784d66e2bc9da4b1f7663df0ce7914609", - "zh:4cf9fe8fae58b62e83c0672a9c66e0963b7289aaf768a250e9bc44570d82cbd5", - "zh:5bfd7a1fb3116164b411777115dd4b272a68984fa949c687e41a3041318c82f1", - "zh:77cbcf2db512617f10b81e11c20d40fa534ef07163171cbe35214fa8f74b4e85", - "zh:8201cabed01f1434bf9ea7fbcf2a95612a87a0398b870b2643bd1a5119793d2d", - "zh:9aaded4cf36ec2abbe35086733a4510e08819698180b21a9387ba4112aee02e0", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:f594ef2683a0d23d3a6f0ad6c84a55ed79368c158ee08c2f3b7c41ec446a701f", - ] -} - -provider "registry.terraform.io/hashicorp/helm" { - version = "3.1.1" - constraints = ">= 2.13.0" - hashes = [ - "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", - "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", - "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", - "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", - "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", - "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", - "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", - "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", - "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", - "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", - "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", - "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = ">= 2.30.0, ~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - constraints = ">= 0.9.0" - hashes = [ - "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.2.1" - constraints = ">= 4.0.0, ~> 4.0" - hashes = [ - "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", - "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", - "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", - "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", - "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", - "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", - "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", - "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", - "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", - "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", - "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", - "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/examples/eks-capabilities/.terraform.lock.hcl b/examples/eks-capabilities/.terraform.lock.hcl deleted file mode 100644 index 7dd68a6..0000000 --- a/examples/eks-capabilities/.terraform.lock.hcl +++ /dev/null @@ -1,104 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "6.35.1" - constraints = ">= 6.0.0" - hashes = [ - "h1:0/uXxSpL98lpRqjRhjAvvWZVnJZnbOehfAlTrcPXURI=", - "zh:0a16d1b0ba9379e5c5295e6b3caa42f0b8ba6b9f0a7cc9dbe58c232cf995db2d", - "zh:4b2e69907a1a2c557e45ef590f9fd6187ab5bf90378346ba9f723535e49ce908", - "zh:56bdafda0d629e15dc3dd9275b54f1fb953e2e09a3bc1a34e027da9d03ea4893", - "zh:5b84e933989150249036f84faad221dce0daa9d3043ff24401547e18f00b121e", - "zh:70bac98c27a14cb2cedabd741a1f7f1bab074c127efdcf02b54dbcf0d03db3cc", - "zh:7184f48bd077eaf68e184fd44f97e2d971cb77c59a68aedb95a0f8dc01b134fe", - "zh:7367589ae8b584bfcd83c973f5003e15010a453349c017a0d2cca8772d4fcfd9", - "zh:7ec9699dee49dd31bbc2d0e50fa1fff451eee5c1d9fd59bca7412acb49ce6594", - "zh:92dd139b96977a64af0e976cd06e84921033678ab97550f1b687c0ea54a8e82c", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f2df575a5b010db60068668c48806595a3d617a2c0305035283fe8b72f07b19", - "zh:a4602b7602c75c8f726bdc7e706dc5c26736e47cc8381be01386aa8d8d998403", - "zh:bc25fefeeee10425df7aebfc21dc6532d19acdf03fa97b9e6d8c113adffd0a1d", - "zh:f445592040b5fc368a12e6edeffc951b2eb41e86413c4074638a13376e25a9cc", - "zh:ff43962a48bd8f85e17188736bbd3c145b6a1320bd8303221f6b4f9ec861e1e6", - ] -} - -provider "registry.terraform.io/hashicorp/helm" { - version = "3.1.1" - constraints = ">= 2.13.0" - hashes = [ - "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", - "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", - "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", - "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", - "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", - "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", - "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", - "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", - "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", - "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", - "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", - "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = ">= 2.30.0, ~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - hashes = [ - "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.2.1" - constraints = ">= 4.0.0, ~> 4.0" - hashes = [ - "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", - "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", - "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", - "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", - "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", - "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", - "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", - "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", - "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", - "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", - "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", - "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/examples/eks-fargate/main.tf b/examples/eks-fargate/main.tf deleted file mode 100644 index 420cc3e..0000000 --- a/examples/eks-fargate/main.tf +++ /dev/null @@ -1,85 +0,0 @@ -terraform { - required_version = ">= 1.6.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 6.0" - } - } -} - -provider "aws" { - region = var.aws_region -} - -module "vpc" { - source = "cloudbuildlab/vpc/aws" - - vpc_name = var.cluster_name - vpc_cidr = "10.0.0.0/16" - availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] - - public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] - private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] - - create_igw = true - nat_gateway_type = "single" - - enable_eks_tags = true - eks_cluster_name = var.cluster_name - tags = var.tags -} - -# Pure Fargate EKS cluster — no EC2 nodes. All pods run on AWS Fargate. -# -# AWS credential delivery for Fargate pods: -# - Use IRSA (IAM Roles for Service Accounts) — annotate the service account with -# an IAM role ARN; the pod exchanges its OIDC token with STS directly. -# - EKS Pod Identity is NOT supported on Fargate. The Pod Identity agent is a -# hostNetwork DaemonSet that only runs on EC2 nodes and cannot reach Fargate pods. -# See: https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html -# -# CoreDNS note: the addon is configured with computeType = "fargate" so EKS schedules -# it on Fargate. A kube-system Fargate profile is required for this to work. -module "eks" { - source = "../../" - - name = var.cluster_name - kubernetes_version = var.cluster_version - vpc_id = module.vpc.vpc_id - subnet_ids = concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids) - - endpoint_public_access = true - access_entries = var.access_entries - - cloudwatch_log_group_force_destroy = true - - addons = { - coredns = { - addon_version = "v1.13.2-eksbuild.4" - configuration_values = jsonencode({ - computeType = "fargate" - }) - } - vpc-cni = { - before_compute = true - addon_version = "v1.21.1-eksbuild.3" - } - } - - fargate_profiles = { - # Required so CoreDNS (and other kube-system pods) can schedule on Fargate. - kube-system = { - selectors = [{ namespace = "kube-system" }] - subnet_ids = module.vpc.private_subnet_ids - } - # Application namespace — add more selectors or profiles as needed. - default = { - selectors = [{ namespace = var.fargate_namespace }] - subnet_ids = module.vpc.private_subnet_ids - } - } - - tags = var.tags -} diff --git a/examples/eks-fargate/variables.tf b/examples/eks-fargate/variables.tf deleted file mode 100644 index 90244bd..0000000 --- a/examples/eks-fargate/variables.tf +++ /dev/null @@ -1,51 +0,0 @@ -variable "aws_region" { - description = "AWS region for resources" - type = string - default = "ap-southeast-2" -} - -variable "cluster_name" { - description = "Name of the EKS cluster" - type = string - default = "eks-fargate" -} - -variable "cluster_version" { - description = "Kubernetes version for the EKS cluster" - type = string - default = "1.35" -} - -variable "fargate_namespace" { - description = "Kubernetes namespace selector for the Fargate profile — pods in this namespace are scheduled on Fargate" - type = string - default = "app" -} - -variable "access_entries" { - description = "Map of access entries to add to the cluster" - type = map(object({ - kubernetes_groups = optional(list(string)) - principal_arn = string - type = optional(string, "STANDARD") - user_name = optional(string) - tags = optional(map(string), {}) - policy_associations = optional(map(object({ - policy_arn = string - access_scope = object({ - namespaces = optional(list(string)) - type = string - }) - })), {}) - })) - default = {} -} - -variable "tags" { - description = "Map of tags to apply to all resources" - type = map(string) - default = { - Environment = "dev" - ManagedBy = "terraform" - } -} diff --git a/examples/eks-fargate/.terraform.lock.hcl b/examples/fargate/.terraform.lock.hcl similarity index 79% rename from examples/eks-fargate/.terraform.lock.hcl rename to examples/fargate/.terraform.lock.hcl index 4dde3cb..70f4507 100644 --- a/examples/eks-fargate/.terraform.lock.hcl +++ b/examples/fargate/.terraform.lock.hcl @@ -25,21 +25,21 @@ provider "registry.terraform.io/hashicorp/aws" { } provider "registry.terraform.io/hashicorp/helm" { - version = "2.17.0" - constraints = ">= 2.13.0, ~> 2.13" + version = "3.1.1" + constraints = ">= 2.13.0" hashes = [ - "h1:K5FEjxvDnxb1JF1kG1xr8J3pNGxoaR3Z0IBG9Csm/Is=", - "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", - "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", - "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", - "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", - "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", - "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", - "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", - "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", - "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", - "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", - "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", + "h1:5b2ojWKT0noujHiweCds37ZreRFRQLNaErdJLusJN88=", + "zh:1a6d5ce931708aec29d1f3d9e360c2a0c35ba5a54d03eeaff0ce3ca597cd0275", + "zh:3411919ba2a5941801e677f0fea08bdd0ae22ba3c9ce3309f55554699e06524a", + "zh:81b36138b8f2320dc7f877b50f9e38f4bc614affe68de885d322629dd0d16a29", + "zh:95a2a0a497a6082ee06f95b38bd0f0d6924a65722892a856cfd914c0d117f104", + "zh:9d3e78c2d1bb46508b972210ad706dd8c8b106f8b206ecf096cd211c54f46990", + "zh:a79139abf687387a6efdbbb04289a0a8e7eaca2bd91cdc0ce68ea4f3286c2c34", + "zh:aaa8784be125fbd50c48d84d6e171d3fb6ef84a221dbc5165c067ce05faab4c8", + "zh:afecd301f469975c9d8f350cc482fe656e082b6ab0f677d1a816c3c615837cc1", + "zh:c54c22b18d48ff9053d899d178d9ffef7d9d19785d9bf310a07d648b7aac075b", + "zh:db2eefd55aea48e73384a555c72bac3f7d428e24147bedb64e1a039398e5b903", + "zh:ee61666a233533fd2be971091cecc01650561f1585783c381b6f6e8a390198a4", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } diff --git a/examples/eks-fargate/README.md b/examples/fargate/README.md similarity index 80% rename from examples/eks-fargate/README.md rename to examples/fargate/README.md index 7ccdfa9..399001f 100644 --- a/examples/eks-fargate/README.md +++ b/examples/fargate/README.md @@ -10,7 +10,7 @@ This example creates an EKS cluster with **Fargate profiles only** (no EC2 manag ## Usage 1. Configure AWS credentials. -2. Edit `terraform.tfvars` and set `access_entries` (required for kubectl access). +2. Copy `terraform.tfvars.example` to `terraform.tfvars` and set `access_entries` (required for kubectl access). 3. Run: ```bash @@ -35,6 +35,7 @@ This example creates an EKS cluster with **Fargate profiles only** (no EC2 manag - `fargate_namespace`: Namespace whose pods are scheduled on Fargate via the `default` profile (default: `app`) - `access_entries`: Map of IAM principals for cluster access - `tags`: Tags for resources +- Optional Argo CD + CodeConnections: set `argocd_idc_instance_arn` (IAM Identity Center instance ARN). The root module then creates the `argocd` capability so `cluster_capability_role_names["argocd"]` exists for `modules/argocd-codeconnections`. Use `argocd_rbac_role_mappings` / `argocd_vpce_ids` if needed (see `examples/eks-auto-mode-private`). ## Connecting to the cluster diff --git a/examples/fargate/main.tf b/examples/fargate/main.tf new file mode 100644 index 0000000..8564ed0 --- /dev/null +++ b/examples/fargate/main.tf @@ -0,0 +1,198 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +module "vpc" { + source = "cloudbuildlab/vpc/aws" + + vpc_name = var.cluster_name + vpc_cidr = "10.0.0.0/16" + availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] + + public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] + private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] + + create_igw = true + nat_gateway_type = "single" + + enable_eks_tags = true + eks_cluster_name = var.cluster_name + tags = var.tags +} + +# Pure Fargate EKS cluster — no EC2 nodes. All pods run on AWS Fargate. +# +# Credential delivery for Fargate pods: +# Use IRSA (IAM Roles for Service Accounts): annotate the service account with +# an IAM role ARN and the pod exchanges its OIDC token with STS directly. +# +# EKS Pod Identity is NOT supported on Fargate. The Pod Identity agent is a +# hostNetwork DaemonSet that only runs on EC2 nodes. +# See: https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html +# +# EBS CSI Driver is NOT applicable on Fargate. Fargate pods cannot use EBS volumes +# (EBS is EC2-specific). Use EFS or S3 for persistent storage on Fargate. +# +# CoreDNS: configured with computeType=fargate so EKS schedules it on Fargate. +# The kube-system Fargate profile below is required for this. +module "eks" { + source = "../../" + + name = var.cluster_name + kubernetes_version = var.cluster_version + vpc_id = module.vpc.vpc_id + subnet_ids = concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids) + + # ── API Endpoint Access ────────────────────────────────────────────────────── + # Public (default): API reachable over the internet. + # endpoint_public_access = true + # public_access_cidrs = ["0.0.0.0/0"] # tighten to your egress IP in production + # + # Private-only: API only reachable from within the VPC or via VPN. + # endpoint_public_access = false + # private_access_cidrs = ["10.0.0.0/8"] + # (Terraform runner must be inside the VPC or connected via VPN) + # ──────────────────────────────────────────────────────────────────────────── + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries + + cloudwatch_log_group_force_destroy = true + + addons = { + coredns = { + addon_version = "v1.13.2-eksbuild.4" + configuration_values = jsonencode({ + computeType = "fargate" + }) + } + vpc-cni = { + before_compute = true + addon_version = "v1.21.1-eksbuild.7" + } + } + + fargate_profiles = { + # Required so CoreDNS (and other kube-system pods) can schedule on Fargate. + kube-system = { + selectors = [{ namespace = "kube-system" }] + subnet_ids = module.vpc.private_subnet_ids + } + # Gatekeeper (Argo / Helm) installs into gatekeeper-system — needs its own profile on Fargate-only clusters. + gatekeeper-system = { + selectors = [{ namespace = "gatekeeper-system" }] + subnet_ids = module.vpc.private_subnet_ids + } + # Application namespace — add more selectors or profiles as needed. + default = { + selectors = [{ namespace = var.fargate_namespace }] + subnet_ids = module.vpc.private_subnet_ids + } + } + + # ── EKS Capabilities (ACK, KRO, Argo CD) ──────────────────────────────────── + # ACK and KRO are always enabled. + # Argo CD is enabled only when argocd_idc_instance_arn is set + # (AWS IAM Identity Center is required by the EKS Argo CD capability). + # + # To enable Argo CD, set in terraform.tfvars: + # argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxx" + # argocd_rbac_role_mappings = [{ role = "ADMIN", identity = [{ type = "SSO_USER", id = "..." }] }] + # + # Argo CD UI endpoint (argocd_vpce_ids): + # Leave empty [] for a publicly reachable Argo CD UI. + # Set a VPC interface endpoint ID to restrict access to within the VPC. + # + # KNOWN BUG — Argo CD public ↔ private UI switching does not apply in-place. + # Workaround: remove argocd from capabilities, apply (destroys it), then re-add + # with the correct argocd_vpce_ids and apply again. + # Note: argocd_vpce_ids controls the Argo CD UI only — NOT the Kubernetes API endpoint. + # ──────────────────────────────────────────────────────────────────────────── + capabilities = merge( + { + ack = {} + kro = {} + }, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [ + { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + ] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { + idc_instance_arn = var.argocd_idc_instance_arn + idc_region = var.aws_region + } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { + vpce_ids = var.argocd_vpce_ids + } + } + } + } + } : {} + ) + + # ── Secrets Manager (IRSA) ─────────────────────────────────────────────────── + # On Fargate, use IRSA — Pod Identity is NOT supported. + # Uncomment and configure to grant service accounts Secrets Manager access. + # + # enable_secrets_manager = true + # secrets_manager_identity_type = "irsa" + # secrets_manager_associations = [ + # { namespace = "sm-operator-system", service_account = "awssm-sync" }, + # ] + # secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] + # ──────────────────────────────────────────────────────────────────────────── + + tags = var.tags +} + +# ── Argo CD CodeConnections (GitHub) ───────────────────────────────────────── +# Creates a GitHub CodeConnection and grants UseConnection + GetConnection to +# the Argo CD capability IAM role. Active only when argocd_idc_instance_arn is set. +# +# IMPORTANT: Only connections listed here are permitted in the IAM policy. +# Argo CD Application repoURLs must use the UUID from output connection_ids[""]. +# Using any other connection UUID will cause AccessDeniedException in Argo CD. +# +# After apply: complete the GitHub OAuth handshake in the AWS Console — new +# connections are in PENDING state until authorised. +# ───────────────────────────────────────────────────────────────────────────── +module "argocd_connections" { + source = "../../modules/argocd-codeconnections" + + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] + + connections = [ + { name = "github-${var.cluster_name}", provider_type = "GitHub" } + ] + + tags = var.tags +} diff --git a/examples/eks-fargate/outputs.tf b/examples/fargate/outputs.tf similarity index 60% rename from examples/eks-fargate/outputs.tf rename to examples/fargate/outputs.tf index 025cc85..4a2a69e 100644 --- a/examples/eks-fargate/outputs.tf +++ b/examples/fargate/outputs.tf @@ -1,5 +1,5 @@ output "aws_region" { - description = "AWS region (for update-kubeconfig)" + description = "AWS region (for aws eks update-kubeconfig)" value = var.aws_region } @@ -52,3 +52,18 @@ output "fargate_access_entry_arn" { description = "ARN of the module-managed Fargate access entry (when created)" value = module.eks.fargate_access_entry_arn } + +output "argocd_connection_ids" { + description = "Map of CodeConnection UUIDs keyed by connection name. Use these UUIDs in Argo CD Application repoURLs." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].connection_ids : null +} + +output "argocd_codeconnections_iam_role_name" { + description = "IAM role name that has CodeConnections UseConnection/GetConnection attached (Argo CD capability role)." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_role_name : null +} + +output "argocd_codeconnections_iam_policy_name" { + description = "Inline policy name on the Argo CD role for CodeConnections." + value = var.argocd_idc_instance_arn != null ? module.argocd_connections[0].codeconnections_iam_policy_name : null +} diff --git a/examples/fargate/terraform.tfvars.example b/examples/fargate/terraform.tfvars.example new file mode 100644 index 0000000..b314290 --- /dev/null +++ b/examples/fargate/terraform.tfvars.example @@ -0,0 +1,31 @@ +# Copy to terraform.tfvars and set your values. +# Add terraform.tfvars to .gitignore if it contains account-specific data. + +# Cluster access (required for kubectl) +access_entries = { + admin = { + principal_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME" + type = "STANDARD" + policy_associations = { + admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + } + } + } +} + +# Optional: API endpoint (defaults: public, 0.0.0.0/0) +# endpoint_public_access = false +# private_access_cidrs = ["10.0.0.0/8"] +# public_access_cidrs = ["203.0.113.10/32"] + +# Optional: Argo CD capability (requires IAM Identity Center). Uncomment and set. +# argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxx" +# argocd_rbac_role_mappings = [ +# { role = "ADMIN", identity = [{ type = "SSO_USER", id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }] } +# ] +# argocd_vpce_ids = [] # or ["vpce-xxxxxxxx"] for private Argo CD UI only + +# Optional: +# fargate_namespace = "app" diff --git a/examples/fargate/variables.tf b/examples/fargate/variables.tf new file mode 100644 index 0000000..1be95dd --- /dev/null +++ b/examples/fargate/variables.tf @@ -0,0 +1,101 @@ +variable "aws_region" { + description = "AWS region for resources" + type = string + default = "ap-southeast-2" +} + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "eks-fargate" +} + +variable "cluster_version" { + description = "Kubernetes version for the EKS cluster" + type = string + default = "1.35" +} + +# ── API Endpoint Access ────────────────────────────────────────────────────── + +variable "endpoint_public_access" { + description = "Whether the EKS API server endpoint is publicly accessible" + type = bool + default = true +} + +variable "public_access_cidrs" { + description = "CIDR blocks allowed to reach the public EKS API endpoint. Defaults to unrestricted; tighten for production." + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "private_access_cidrs" { + description = "CIDR blocks allowed to reach the private EKS API endpoint (within the VPC)." + type = list(string) + default = [] +} + +# ── Argo CD ────────────────────────────────────────────────────────────────── + +variable "argocd_idc_instance_arn" { + description = "ARN of the AWS IAM Identity Center instance for Argo CD capability. When null, the Argo CD capability and CodeConnections submodule are not created." + type = string + default = null +} + +variable "argocd_rbac_role_mappings" { + description = "Argo CD RBAC role mappings: IdC users/groups to Argo CD roles (ADMIN, EDITOR, VIEWER). Fixes 'No users or groups assigned' and Unauthorized when loading applications." + type = list(object({ + role = string # ADMIN | EDITOR | VIEWER + identity = list(object({ + type = string # SSO_USER or SSO_GROUP + id = string # IAM Identity Center user or group ID + })) + })) + default = [] +} + +variable "argocd_vpce_ids" { + description = "VPC interface endpoint ID(s) for the Argo CD UI (com.amazonaws..eks-capabilities). Leave empty for a public UI. See KNOWN BUG in main.tf regarding in-place switching." + type = list(string) + default = [] +} + +# ── Fargate ────────────────────────────────────────────────────────────────── + +variable "fargate_namespace" { + description = "Kubernetes namespace selector for the default Fargate profile — pods in this namespace are scheduled on Fargate" + type = string + default = "app" +} + +# ── Access ─────────────────────────────────────────────────────────────────── + +variable "access_entries" { + description = "Map of IAM principals to grant cluster access to" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "tags" { + description = "Map of tags to apply to all resources" + type = map(string) + default = { + Environment = "dev" + ManagedBy = "terraform" + } +} diff --git a/examples/eks-auto-mode-private/.terraform.lock.hcl b/examples/hub-spoke-argocd/.terraform.lock.hcl similarity index 75% rename from examples/eks-auto-mode-private/.terraform.lock.hcl rename to examples/hub-spoke-argocd/.terraform.lock.hcl index a81ee35..70f4507 100644 --- a/examples/eks-auto-mode-private/.terraform.lock.hcl +++ b/examples/hub-spoke-argocd/.terraform.lock.hcl @@ -2,25 +2,25 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "6.36.0" + version = "6.39.0" constraints = ">= 6.0.0" hashes = [ - "h1:r9icn1WEZVvEXiy6ZKexLzAPnXkkt+22jJ9WQYPfKB0=", - "zh:0eb4481315564aaeec4905a804fd0df22c40f509ad2af63615eeaa90abacf81c", - "zh:12c3cddc461a8dbaa04387fe83420b64c4c05cb5479d181674168ca7daefcc38", - "zh:1b55a09661e80acf6826faa38dd8fbff24c2ef620d2a0a16918491a222c55370", - "zh:269cb1a406d0cac762bce82119247395a0bbf0d4ad2492fb2ea5653b4f44bc05", - "zh:3bfb78e3345f0c3846e76578952a09fb5dda05d2d73e19473fb0af0000469a66", - "zh:3ead4f4388c7dd78ed198082a981746324da0d7a51460c9b455fd884d86fc82c", - "zh:44906654199991b3f1a21c6a984bc5f9f556ff4baa4e5f77e168968e941c2725", - "zh:4803d050d581b05b0fd0ae5cce95ec1784d66e2bc9da4b1f7663df0ce7914609", - "zh:4cf9fe8fae58b62e83c0672a9c66e0963b7289aaf768a250e9bc44570d82cbd5", - "zh:5bfd7a1fb3116164b411777115dd4b272a68984fa949c687e41a3041318c82f1", - "zh:77cbcf2db512617f10b81e11c20d40fa534ef07163171cbe35214fa8f74b4e85", - "zh:8201cabed01f1434bf9ea7fbcf2a95612a87a0398b870b2643bd1a5119793d2d", - "zh:9aaded4cf36ec2abbe35086733a4510e08819698180b21a9387ba4112aee02e0", + "h1:jweey4Iefm/DuuBg84saQ8vz5IO3vC6hDFTU/eGdmBI=", + "zh:00c3e3c38063ff629d6fdbce04e9ac2e241566e0f5ad5399c335f0abdefd7bff", + "zh:148f95b62791080537d926b9d2f5d8457cca45921d9b1019d03ceb3ab93bf9db", + "zh:203da629ed5191dd5d7aa3427a5d1d1a83eed5c1b0114166897206973f0d0fd0", + "zh:21923eedbc60b4f68c8d717b951d16b0b1bbf31d66330c7be228869bec18f7ce", + "zh:26226f02e3661b3d071c01601b654a308b29d21758b75692bec66f70c6f6b945", + "zh:271c7c6fadcd8ac7ed37c11e61c0f374773eaaa5293703499f8a0f75830060e0", + "zh:46e319a8888dc50ed8d26a1cbee9637f529112a88f5d44decc8f1d10ef968ffe", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:f594ef2683a0d23d3a6f0ad6c84a55ed79368c158ee08c2f3b7c41ec446a701f", + "zh:a3c3ca09cdbf3b9a3f892a23c000ff04772bdf19f626959ea83d0803c8fd2350", + "zh:a5fa6515ffc3c815e0d2204d67e838f5bad8635009dab85211d166c7ae729d2c", + "zh:c0807566b4ddde8390f50c5475464103f066bc7f511a6c0be762d75cb6d1a078", + "zh:da754a529fd0e06ac372f62d88566f85a8c4bcec7ee9a231b65e0a0148165e63", + "zh:dcb768e48363a9f4dffaf2dc7d01f1877285528925ec50de6335286298e37e1d", + "zh:eac9de9d123c679ea3035199fb9c588a08cda281cbabf948dc696e2a1a1b9063", + "zh:fef276b6331c663ca0e60dc7f637b2b8244825b8c9bc721481957e58f74ffb4f", ] } diff --git a/examples/hub-spoke-argocd/README.md b/examples/hub-spoke-argocd/README.md new file mode 100644 index 0000000..d62e698 --- /dev/null +++ b/examples/hub-spoke-argocd/README.md @@ -0,0 +1,113 @@ +# EKS Argo CD Hub-and-Spoke Example + +This example implements the [AWS hub-and-spoke GitOps pattern](https://docs.aws.amazon.com/eks/latest/userguide/argocd.html) using the managed EKS Argo CD capability. + +One **hub** cluster hosts the Argo CD capability (with AWS IAM Identity Center authentication). Two **spoke** clusters (`module.eks_spoke_1` and `module.eks_spoke_2`) do not run Argo CD themselves — instead, they grant the hub's Argo CD IAM role cluster-admin access via `access_entries`, allowing Argo CD on the hub to deploy to them. + +```text +Hub EKS (Argo CD + ACK + KRO) + └─ deploys to ──► Spoke 1 — module eks_spoke_1 (ACK + KRO) + └─ deploys to ──► Spoke 2 — module eks_spoke_2 (ACK + KRO) +``` + +All three clusters share one VPC. + +## Requirements + +- Terraform >= 1.6.0 +- AWS provider >= 6.0 +- **AWS IAM Identity Center** — required for the managed Argo CD capability (`argocd_idc_instance_arn`) +- Kubernetes 1.29+ (required for managed capabilities) + +## Usage + +1. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in: + - `argocd_idc_instance_arn` (required) + - `access_entries_hub` / `access_entries_spoke_dev` / `access_entries_spoke_prod` for your IAM principals + - `argocd_rbac_role_mappings` for Argo CD UI access +2. Run: + + ```bash + cd examples/hub-spoke-argocd + terraform init + terraform plan + terraform apply + ``` + +> **Optional targeted apply:** If you want spoke clusters to be created after the hub IAM role exists (e.g. to avoid unknown-value diffs on first plan), run `terraform apply -target=module.eks_hub` first, then `terraform apply`. This is not required — Terraform resolves the dependency automatically. + +## What's created + +- A shared VPC with public and private subnets (2 AZs) +- **Hub EKS cluster** (default `cluster_names.hub` = `eks-10`) with: + - **EKS Auto Mode** (`system` + `general-purpose` node pools; no managed node groups or addon map) + - Capabilities: ACK, KRO, **Argo CD** (with IdC authentication, configurable RBAC, optional VPCE) + - Argo CD capability role granted cluster-admin on the hub cluster itself (for in-cluster deployments) +- **Spoke 1** — `module.eks_spoke_1` (default `cluster_names.spoke_dev` = `eks-11`) with: + - **EKS Auto Mode** (same node pools as hub) + - Capabilities: ACK, KRO (no Argo CD) + - Hub Argo CD role granted `AmazonEKSClusterAdminPolicy` via `access_entries` +- **Spoke 2** — `module.eks_spoke_2` (default `cluster_names.spoke_prod` = `eks-12`) — identical pattern to spoke 1 +- **CodeConnections** (GitHub) on the hub only, granting the Argo CD role `UseConnection + GetConnection` + +## Post-apply: register spoke clusters in Argo CD + +After `terraform apply` completes: + +1. Complete the GitHub OAuth handshake in the AWS Console → CodeConnections — new connections start in **PENDING** state. +2. Configure kubeconfig for the hub cluster: + + ```bash + aws eks update-kubeconfig \ + --name "$(terraform output -raw hub_cluster_name)" \ + --region "$(terraform output -raw aws_region)" + ``` + +3. Bootstrap GitOps on the hub using **kube-infra** (`bootstrap/root-eks-10.yaml` then `root-eks-11.yaml` / `root-eks-12.yaml`). All three root Applications sync to **`in-cluster`** on the hub — `clusters/eks-11` and `clusters/eks-12` are **ApplicationSets**, which must exist only on the hub (spokes lack the ApplicationSet CRD). Remote cluster Secrets still register eks-11/eks-12 for **child** apps. Managed Argo CD requires the **cluster ARN** (not the HTTPS API URL) in those Secrets’ `server` when using `awsAuthConfig`. Copy values from Terraform: + + ```bash + terraform output -raw hub_cluster_arn + terraform output -raw spoke_1_cluster_arn + terraform output -raw spoke_2_cluster_arn + terraform output -raw hub_argocd_capability_role_arn + ``` + + Pre-filled manifests live in the **kube-infra** repo under `bootstrap/`; after `git pull`, `kubectl apply` them to the hub context. See that repo’s `bootstrap/README.md` (hub-spoke section). + +4. Argo CD Application `repoURL` must use the UUID from `argocd_connection_ids`: + + ```bash + terraform output argocd_connection_ids + ``` + +## Outputs + +| Output | Description | +| --- | --- | +| `hub_cluster_name` | Hub cluster name | +| `hub_cluster_endpoint` | Hub API server endpoint | +| `hub_cluster_arn` | Hub EKS cluster ARN (Argo CD `server` for in-cluster registration) | +| `hub_argocd_capability_role_arn` | ARN of the hub Argo CD capability role (granted admin on all spokes) | +| `spoke_1_cluster_name` | First spoke cluster name (`eks_spoke_1`) | +| `spoke_1_cluster_endpoint` | First spoke API server endpoint | +| `spoke_1_cluster_arn` | First spoke EKS cluster ARN (Argo CD remote `Secret` `server`; not the HTTPS URL) | +| `spoke_2_cluster_name` | Second spoke cluster name (`eks_spoke_2`) | +| `spoke_2_cluster_endpoint` | Second spoke API server endpoint | +| `spoke_2_cluster_arn` | Second spoke EKS cluster ARN (Argo CD remote `Secret` `server`) | +| `argocd_connection_ids` | CodeConnections UUID map — use in Argo CD Application `repoURL` | +| `argocd_codeconnections_iam_role_name` | Hub IAM role with CodeConnections policy attached | + +## Notes + +- **Renaming modules in an existing workspace:** If state still has `module.eks_spoke_dev` / `module.eks_spoke_prod`, run before the next apply: + + ```bash + terraform state mv 'module.eks_spoke_dev' 'module.eks_spoke_1' + terraform state mv 'module.eks_spoke_prod' 'module.eks_spoke_2' + ``` + +- **Least privilege in production:** Replace `AmazonEKSClusterAdminPolicy` with a narrower policy (e.g. namespace-scoped `AmazonEKSEditPolicy`) once you know which namespaces Argo CD deploys to. +- **KNOWN BUG — Argo CD UI public ↔ private switching:** Changing `argocd_vpce_ids` between empty and a VPCE ID does not apply in-place. Workaround: remove `argocd` from hub capabilities, `apply`, re-add with the correct `argocd_vpce_ids`, `apply` again. +- **`argocd_idc_instance_arn` is required** for this example. The whole hub-spoke wiring depends on the Argo CD capability role existing on the hub. +- **Subnet tags:** Spoke cluster tags (`kubernetes.io/cluster/`) are merged into the VPC module's `tags` input to avoid conflicts with the `aws_subnet` resource in AWS provider v6. +- **Subnets for Auto Mode:** All clusters use **private** subnets only for `subnet_ids` (control plane still respects your public/private API settings). diff --git a/examples/hub-spoke-argocd/main.tf b/examples/hub-spoke-argocd/main.tf new file mode 100644 index 0000000..82f910f --- /dev/null +++ b/examples/hub-spoke-argocd/main.tf @@ -0,0 +1,226 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +# Spoke cluster names need kubernetes.io/cluster tags on subnets (shared VPC + Auto Mode). +# aws_ec2_tag would conflict with aws_subnet in AWS provider v6. +# +# The spoke access_entries reference module.eks_hub.cluster_capability_role_arns["argocd"] (computed). +# Terraform resolves this dependency automatically: hub IAM role is created first, +# then spoke access entries are applied. +locals { + vpc_tags = merge(var.tags, { + "kubernetes.io/cluster/${var.cluster_names.spoke_dev}" = "shared" + "kubernetes.io/cluster/${var.cluster_names.spoke_prod}" = "shared" + }) +} + +# ── Shared VPC ──────────────────────────────────────────────────────────────── +module "vpc" { + source = "cloudbuildlab/vpc/aws" + + vpc_name = var.vpc_name + vpc_cidr = "10.0.0.0/16" + availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] + + public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] + private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"] + + create_igw = true + nat_gateway_type = "single" + + # Hub cluster name drives the primary kubernetes.io/cluster/* subnet tag. + # Spoke cluster tags are merged in local.vpc_tags above. + enable_eks_tags = true + eks_cluster_name = var.cluster_names.hub + + tags = local.vpc_tags +} + +# ── Hub EKS Cluster (Argo CD) ───────────────────────────────────────────────── +# The hub runs the managed Argo CD capability and deploys to spoke clusters via +# the hub Argo CD role, which is granted cluster-admin on each spoke's access_entries. +module "eks_hub" { + source = "../../" + + name = var.cluster_names.hub + kubernetes_version = var.kubernetes_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnet_ids + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries_hub + cloudwatch_log_group_force_destroy = true + + # Auto Mode: no managed node groups; CoreDNS, vpc-cni, kube-proxy, Pod Identity Agent are built in. + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + addons = {} + + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + # ── EKS Capabilities ──────────────────────────────────────────────────────── + # Hub has ACK, KRO, and Argo CD. Spoke clusters have ACK and KRO only. + # access_entry_policy_associations gives the Argo CD role admin access to + # the hub cluster itself (in-cluster deployments or App of Apps on the hub). + # ────────────────────────────────────────────────────────────────────────── + capabilities = { + ack = {} + kro = {} + argocd = { + access_entry_policy_associations = [ + { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { + type = "cluster" + } + } + ] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { + idc_instance_arn = var.argocd_idc_instance_arn + idc_region = var.aws_region + } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { + vpce_ids = var.argocd_vpce_ids + } + } + } + } + } + + tags = var.tags +} + +# ── Spoke 1 ──────────────────────────────────────────────────────────────────── +# No Argo CD capability. Grants hub Argo CD role cluster-admin so Argo CD can +# deploy workloads here. The argocd_hub key is static; only the value is computed. +module "eks_spoke_1" { + source = "../../" + + name = var.cluster_names.spoke_dev + kubernetes_version = var.kubernetes_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnet_ids + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = merge(var.access_entries_spoke_dev, { + argocd_hub = { + principal_arn = module.eks_hub.cluster_capability_role_arns["argocd"] + policy_associations = { + admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + } + } + } + }) + + cloudwatch_log_group_force_destroy = true + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + addons = {} + + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + capabilities = { + ack = {} + kro = {} + } + + tags = var.tags +} + +# ── Spoke 2 ──────────────────────────────────────────────────────────────────── +module "eks_spoke_2" { + source = "../../" + + name = var.cluster_names.spoke_prod + kubernetes_version = var.kubernetes_version + + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnet_ids + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = merge(var.access_entries_spoke_prod, { + argocd_hub = { + principal_arn = module.eks_hub.cluster_capability_role_arns["argocd"] + policy_associations = { + admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + } + } + } + }) + + cloudwatch_log_group_force_destroy = true + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + addons = {} + + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + capabilities = { + ack = {} + kro = {} + } + + tags = var.tags +} + +# ── Argo CD CodeConnections (GitHub) ───────────────────────────────────────── +# Hub only: spoke clusters do not host Argo CD and need no CodeConnections. +# +# IMPORTANT: Only connections listed here are permitted in the IAM policy. +# Argo CD Application repoURLs must use the UUID from output argocd_connection_ids[""]. +# Using any other connection UUID will cause AccessDeniedException in Argo CD. +# +# After apply: complete the GitHub OAuth handshake in the AWS Console — new +# connections start PENDING until authorised. +# ───────────────────────────────────────────────────────────────────────────── +module "argocd_connections" { + source = "../../modules/argocd-codeconnections" + + argocd_capability_role_name = module.eks_hub.cluster_capability_role_names["argocd"] + + connections = [ + { name = "github-${var.cluster_names.hub}", provider_type = "GitHub" } + ] + + tags = var.tags +} diff --git a/examples/hub-spoke-argocd/outputs.tf b/examples/hub-spoke-argocd/outputs.tf new file mode 100644 index 0000000..95b7e0e --- /dev/null +++ b/examples/hub-spoke-argocd/outputs.tf @@ -0,0 +1,96 @@ +output "aws_region" { + description = "AWS region (for aws eks update-kubeconfig)" + value = var.aws_region +} + +output "vpc_id" { + description = "Shared VPC ID for hub and spokes; set aws-load-balancer-controller Helm vpcId in kube-infra overlays for eks-11/eks-12" + value = module.vpc.vpc_id +} + +# ── Hub ─────────────────────────────────────────────────────────────────────── + +output "hub_cluster_name" { + description = "Name of the hub EKS cluster" + value = module.eks_hub.cluster_name +} + +output "hub_cluster_endpoint" { + description = "Endpoint for the hub EKS control plane" + value = module.eks_hub.cluster_endpoint +} + +output "hub_cluster_arn" { + description = "EKS cluster ARN for eks-10 (use as server in Argo CD in-cluster Secret; same pattern as root-eks-10.yaml)" + value = module.eks_hub.cluster_arn +} + +output "hub_oidc_provider_arn" { + description = "OIDC provider ARN for the hub cluster" + value = module.eks_hub.oidc_provider_arn +} + +# Used by spoke clusters to grant Argo CD deploy access. +output "hub_argocd_capability_role_arn" { + description = "ARN of the hub Argo CD capability IAM role. This is automatically granted cluster-admin on spoke clusters via access_entries." + value = module.eks_hub.cluster_capability_role_arns["argocd"] +} + +# ── Spokes ──────────────────────────────────────────────────────────────────── + +output "spoke_1_cluster_name" { + description = "Name of the first spoke EKS cluster (module eks_spoke_1)" + value = module.eks_spoke_1.cluster_name +} + +output "spoke_1_cluster_endpoint" { + description = "Endpoint for the first spoke EKS control plane" + value = module.eks_spoke_1.cluster_endpoint +} + +output "spoke_1_cluster_arn" { + description = "EKS cluster ARN for eks-11 — required value for Argo CD remote cluster Secret server (with awsAuthConfig); do not use spoke_1_cluster_endpoint there" + value = module.eks_spoke_1.cluster_arn +} + +output "spoke_1_oidc_provider_arn" { + description = "OIDC provider ARN for the first spoke cluster" + value = module.eks_spoke_1.oidc_provider_arn +} + +output "spoke_2_cluster_name" { + description = "Name of the second spoke EKS cluster (module eks_spoke_2)" + value = module.eks_spoke_2.cluster_name +} + +output "spoke_2_cluster_endpoint" { + description = "Endpoint for the second spoke EKS control plane" + value = module.eks_spoke_2.cluster_endpoint +} + +output "spoke_2_cluster_arn" { + description = "EKS cluster ARN for eks-12 — required value for Argo CD remote cluster Secret server (with awsAuthConfig); do not use spoke_2_cluster_endpoint there" + value = module.eks_spoke_2.cluster_arn +} + +output "spoke_2_oidc_provider_arn" { + description = "OIDC provider ARN for the second spoke cluster" + value = module.eks_spoke_2.oidc_provider_arn +} + +# ── Argo CD CodeConnections ─────────────────────────────────────────────────── + +output "argocd_connection_ids" { + description = "Map of CodeConnections UUIDs keyed by connection name. Use these UUIDs in Argo CD Application repoURLs." + value = module.argocd_connections.connection_ids +} + +output "argocd_codeconnections_iam_role_name" { + description = "IAM role name that has CodeConnections UseConnection/GetConnection attached (hub Argo CD capability role)." + value = module.argocd_connections.codeconnections_iam_role_name +} + +output "argocd_codeconnections_iam_policy_name" { + description = "Inline policy name on the hub Argo CD role for CodeConnections." + value = module.argocd_connections.codeconnections_iam_policy_name +} diff --git a/examples/hub-spoke-argocd/terraform.tfvars.example b/examples/hub-spoke-argocd/terraform.tfvars.example new file mode 100644 index 0000000..0a73db1 --- /dev/null +++ b/examples/hub-spoke-argocd/terraform.tfvars.example @@ -0,0 +1,80 @@ +aws_region = "ap-southeast-2" + +vpc_name = "vpc-eks-hub-spoke" + +tags = { + Environment = "dev" + ManagedBy = "terraform" + Project = "hub-spoke-argocd" +} + +cluster_names = { + hub = "eks-10" + spoke_dev = "eks-11" + spoke_prod = "eks-12" +} + +kubernetes_version = "1.35" + +# ── API access ──────────────────────────────────────────────────────────────── +# Restrict public_access_cidrs to your egress IP for production. +endpoint_public_access = true +public_access_cidrs = ["0.0.0.0/0"] +private_access_cidrs = [] + +# ── EKS access entries ──────────────────────────────────────────────────────── +# Grant your IAM principals (users, roles) kubectl access to each cluster. +# Spoke clusters automatically receive an argocd_hub entry for the hub Argo CD role. + +access_entries_hub = { + # admin = { + # principal_arn = "arn:aws:iam::111122223333:role/my-dev-role" + # policy_associations = { + # admin = { + # policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + # access_scope = { type = "cluster" } + # } + # } + # } +} + +access_entries_spoke_dev = { + # admin = { + # principal_arn = "arn:aws:iam::111122223333:role/my-dev-role" + # policy_associations = { + # admin = { + # policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + # access_scope = { type = "cluster" } + # } + # } + # } +} + +access_entries_spoke_prod = { + # admin = { + # principal_arn = "arn:aws:iam::111122223333:role/my-prod-role" + # policy_associations = { + # admin = { + # policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + # access_scope = { type = "cluster" } + # } + # } + # } +} + +# ── Argo CD (required) ──────────────────────────────────────────────────────── +# argocd_idc_instance_arn is required: the hub needs AWS IAM Identity Center +# for the managed Argo CD capability. +argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxx" + +argocd_rbac_role_mappings = [ + # { + # role = "ADMIN" + # identity = [{ type = "SSO_USER", id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }] + # } +] + +# Leave empty for a publicly reachable Argo CD UI. +# Set a VPC interface endpoint ID to restrict to within the VPC. +# See KNOWN BUG in README before changing this after first apply. +argocd_vpce_ids = [] diff --git a/examples/hub-spoke-argocd/variables.tf b/examples/hub-spoke-argocd/variables.tf new file mode 100644 index 0000000..63fb031 --- /dev/null +++ b/examples/hub-spoke-argocd/variables.tf @@ -0,0 +1,151 @@ +variable "aws_region" { + description = "AWS region for all resources" + type = string + default = "ap-southeast-2" +} + +variable "vpc_name" { + description = "Name tag for the shared VPC" + type = string + default = "vpc-eks-hub-spoke" +} + +variable "tags" { + description = "Common tags for VPC and all clusters" + type = map(string) + default = { + Environment = "dev" + ManagedBy = "terraform" + } +} + +# ── Cluster identity ────────────────────────────────────────────────────────── + +variable "cluster_names" { + description = "EKS cluster names. Hub name drives the VPC module's primary kubernetes.io/cluster/* subnet tag." + type = object({ + hub = string + spoke_dev = string + spoke_prod = string + }) + default = { + hub = "eks-10" + spoke_dev = "eks-11" + spoke_prod = "eks-12" + } +} + +variable "kubernetes_version" { + description = "Kubernetes version for all clusters (Auto Mode requires 1.29+)" + type = string + default = "1.35" +} + +# ── API access (shared for all clusters) ───────────────────────────────────── + +variable "endpoint_public_access" { + description = "Whether each cluster's Kubernetes API is publicly reachable" + type = bool + default = true +} + +variable "public_access_cidrs" { + description = "CIDR blocks that can reach the public API endpoint" + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "private_access_cidrs" { + description = "CIDR blocks that can reach the private API endpoint" + type = list(string) + default = [] +} + +# ── EKS access entries (one map per cluster) ────────────────────────────────── +# Note: spoke clusters automatically receive an argocd_hub access entry for the +# hub Argo CD capability role — you do not need to add it here manually. + +variable "access_entries_hub" { + description = "Access entries for the hub cluster" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "access_entries_spoke_dev" { + description = "Access entries for module.eks_spoke_1 (cluster name from cluster_names.spoke_dev). argocd_hub (hub Argo CD role) is added automatically." + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "access_entries_spoke_prod" { + description = "Access entries for module.eks_spoke_2 (cluster name from cluster_names.spoke_prod). argocd_hub (hub Argo CD role) is added automatically." + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +# ── Argo CD (hub cluster only) ──────────────────────────────────────────────── +# argocd_idc_instance_arn is required: the hub needs AWS IAM Identity Center +# for the managed Argo CD capability, and spoke clusters are wired to the hub +# Argo CD role which only exists when argocd is in capabilities. + +variable "argocd_idc_instance_arn" { + description = "IAM Identity Center instance ARN. Required for hub Argo CD capability and spoke cluster wiring." + type = string +} + +variable "argocd_rbac_role_mappings" { + description = "RBAC role mappings for the hub Argo CD UI" + type = list(object({ + role = string + identity = list(object({ + type = string + id = string + })) + })) + default = [] +} + +variable "argocd_vpce_ids" { + description = "VPC interface endpoint IDs for the hub Argo CD UI (empty = public UI)" + type = list(string) + default = [] +} diff --git a/examples/minimal/README.md b/examples/minimal/README.md new file mode 100644 index 0000000..8c8fe97 --- /dev/null +++ b/examples/minimal/README.md @@ -0,0 +1,39 @@ +# minimal + +Smallest possible usage of the root module: Auto Mode cluster with BYO VPC and subnets. + +No capabilities, no Argo CD, no Secrets Manager, no EBS CSI driver. + +Add features incrementally — see [examples/auto-mode](../auto-mode) for the full Auto Mode example. + +From this directory: copy `terraform.tfvars.example` to `terraform.tfvars`, set `vpc_id` and `subnet_ids`, then `terraform init` / `plan` / `apply`. + +## Usage + +```hcl +module "eks" { + source = "github.com/cloudbuildlab/terraform-aws-eks-basic" + + name = "my-cluster" + kubernetes_version = "1.35" + vpc_id = "vpc-xxxxxxxxxxxxxxxxx" + subnet_ids = ["subnet-aaa", "subnet-bbb"] + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + addons = {} +} +``` + +## Required inputs + +| Name | Description | +| --- | --- | +| `vpc_id` | ID of an existing VPC | +| `subnet_ids` | List of subnet IDs (private subnets recommended) | + +## Connect to the cluster + +```bash +aws eks update-kubeconfig --region --name +``` diff --git a/examples/minimal/main.tf b/examples/minimal/main.tf new file mode 100644 index 0000000..4aab2f1 --- /dev/null +++ b/examples/minimal/main.tf @@ -0,0 +1,40 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +# Minimal EKS Auto Mode cluster — BYO VPC and subnets. +# No capabilities, no Argo CD, no Secrets Manager, no EBS CSI. +# Use this as a starting point; add features from the auto-mode example as needed. +module "eks" { + source = "../../" + + name = var.cluster_name + kubernetes_version = var.cluster_version + vpc_id = var.vpc_id + subnet_ids = var.subnet_ids + + endpoint_public_access = true + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + + # Auto Mode has built-in addons; do not install CoreDNS, vpc-cni, kube-proxy, or Pod Identity Agent here. + addons = {} + + tags = var.tags +} diff --git a/examples/eks-auto-mode/outputs.tf b/examples/minimal/outputs.tf similarity index 52% rename from examples/eks-auto-mode/outputs.tf rename to examples/minimal/outputs.tf index 0410ecf..d876060 100644 --- a/examples/eks-auto-mode/outputs.tf +++ b/examples/minimal/outputs.tf @@ -1,23 +1,3 @@ -output "aws_region" { - description = "AWS region (for update-kubeconfig)" - value = var.aws_region -} - -output "vpc_id" { - description = "ID of the VPC" - value = module.vpc.vpc_id -} - -output "public_subnet_ids" { - description = "IDs of the public subnets" - value = module.vpc.public_subnet_ids -} - -output "private_subnet_ids" { - description = "IDs of the private subnets" - value = module.vpc.private_subnet_ids -} - output "cluster_name" { description = "Name of the EKS cluster" value = module.eks.cluster_name diff --git a/examples/minimal/terraform.tfvars.example b/examples/minimal/terraform.tfvars.example new file mode 100644 index 0000000..bd31d6f --- /dev/null +++ b/examples/minimal/terraform.tfvars.example @@ -0,0 +1,15 @@ +# Copy to terraform.tfvars and set your values. +# Add terraform.tfvars to .gitignore if it contains account-specific data. + +# Required: existing VPC and subnets (private subnets recommended for Auto Mode nodes). +vpc_id = "vpc-xxxxxxxx" +subnet_ids = ["subnet-aaaaaaaa", "subnet-bbbbbbbb"] + +# Optional overrides (defaults exist in variables.tf): +# aws_region = "ap-southeast-2" +# cluster_name = "eks-minimal" +# cluster_version = "1.35" + +# Note: this example does not pass access_entries to the module. For kubectl access, +# either extend main.tf with access_entries or use an IAM identity that EKS grants +# via another path (e.g. cluster creator policies in your org). diff --git a/examples/minimal/variables.tf b/examples/minimal/variables.tf new file mode 100644 index 0000000..d07d663 --- /dev/null +++ b/examples/minimal/variables.tf @@ -0,0 +1,36 @@ +variable "aws_region" { + description = "AWS region for resources" + type = string + default = "ap-southeast-2" +} + +variable "cluster_name" { + description = "Name of the EKS cluster" + type = string + default = "eks-minimal" +} + +variable "cluster_version" { + description = "Kubernetes version for the EKS cluster (Auto Mode requires 1.29+)" + type = string + default = "1.35" +} + +variable "vpc_id" { + description = "ID of an existing VPC to deploy the cluster into" + type = string +} + +variable "subnet_ids" { + description = "List of subnet IDs (private recommended) for EKS nodes" + type = list(string) +} + +variable "tags" { + description = "Map of tags to apply to all resources" + type = map(string) + default = { + Environment = "dev" + ManagedBy = "terraform" + } +} diff --git a/examples/eks-auto-mode-keda-workload/.terraform.lock.hcl b/examples/multi-cluster-shared-vpc/.terraform.lock.hcl similarity index 61% rename from examples/eks-auto-mode-keda-workload/.terraform.lock.hcl rename to examples/multi-cluster-shared-vpc/.terraform.lock.hcl index d15c2fe..70f4507 100644 --- a/examples/eks-auto-mode-keda-workload/.terraform.lock.hcl +++ b/examples/multi-cluster-shared-vpc/.terraform.lock.hcl @@ -2,25 +2,25 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/aws" { - version = "6.38.0" + version = "6.39.0" constraints = ">= 6.0.0" hashes = [ - "h1:7F3W4qGLTbr4aploSI8eIqE4AueoNe/Tq5Osuo0IgJ4=", - "zh:143f118ae71059a7a7026c6b950da23fef04a06e2362ffa688bef75e43e869ed", - "zh:29ee220a017306effd877e1280f8b2934dc957e16e0e72ca0222e5514d0db522", - "zh:3a31baabf7aea7aa7669f5a3d76f3445e0e6cce5e9aea0279992765c0df12aee", - "zh:4c1908e62040dbc9901d4426ffb253f53e5dae9e3e1a9125311291ee265c8d8c", - "zh:550f4789f5f5b00e16118d4c17770be3ef4535d6b6928af1cf91ebd30f2c263b", - "zh:6537b7b70bf2c127771b0b84e4b726c834d10666b6104f017edae50c67ebae37", + "h1:jweey4Iefm/DuuBg84saQ8vz5IO3vC6hDFTU/eGdmBI=", + "zh:00c3e3c38063ff629d6fdbce04e9ac2e241566e0f5ad5399c335f0abdefd7bff", + "zh:148f95b62791080537d926b9d2f5d8457cca45921d9b1019d03ceb3ab93bf9db", + "zh:203da629ed5191dd5d7aa3427a5d1d1a83eed5c1b0114166897206973f0d0fd0", + "zh:21923eedbc60b4f68c8d717b951d16b0b1bbf31d66330c7be228869bec18f7ce", + "zh:26226f02e3661b3d071c01601b654a308b29d21758b75692bec66f70c6f6b945", + "zh:271c7c6fadcd8ac7ed37c11e61c0f374773eaaa5293703499f8a0f75830060e0", + "zh:46e319a8888dc50ed8d26a1cbee9637f529112a88f5d44decc8f1d10ef968ffe", "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:af2f9cea0c8bdf5b2a2391f2d179a946c117196f7c829b919673cae3b71d2943", - "zh:c53ffa685381aa4e73158fd9f529239f95938dea330e7aca0b32e7b2a1210432", - "zh:d0995e1d64a7ec8bbc79fc3fbec3749f989e07f211a318705c37cd6a7c7d19e4", - "zh:d2348ffcffc1282983d7a5838dd5d61f372152fe6c0d10868cd6473352318750", - "zh:e449312efb73e4747165e689302a68a1df8ba5755e7f59097069acf82c94f011", - "zh:ec3a538d264ef79380e56fdf107ffb6c0446814f07fc5890c36855fe1e03196b", - "zh:f441e69699b22e32c96a8cdd3bbe694ed302c0dcfe867cd9bd683a16df362714", - "zh:f6f8eaa605ff902234d7e9bdab4fda977185fce14f8576f7b622c914c7d98008", + "zh:a3c3ca09cdbf3b9a3f892a23c000ff04772bdf19f626959ea83d0803c8fd2350", + "zh:a5fa6515ffc3c815e0d2204d67e838f5bad8635009dab85211d166c7ae729d2c", + "zh:c0807566b4ddde8390f50c5475464103f066bc7f511a6c0be762d75cb6d1a078", + "zh:da754a529fd0e06ac372f62d88566f85a8c4bcec7ee9a231b65e0a0148165e63", + "zh:dcb768e48363a9f4dffaf2dc7d01f1877285528925ec50de6335286298e37e1d", + "zh:eac9de9d123c679ea3035199fb9c588a08cda281cbabf948dc696e2a1a1b9063", + "zh:fef276b6331c663ca0e60dc7f637b2b8244825b8c9bc721481957e58f74ffb4f", ] } @@ -44,26 +44,6 @@ provider "registry.terraform.io/hashicorp/helm" { ] } -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = "~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - provider "registry.terraform.io/hashicorp/time" { version = "0.13.1" constraints = ">= 0.9.0" diff --git a/examples/multi-cluster-shared-vpc/README.md b/examples/multi-cluster-shared-vpc/README.md new file mode 100644 index 0000000..9775371 --- /dev/null +++ b/examples/multi-cluster-shared-vpc/README.md @@ -0,0 +1,72 @@ +# multi-cluster-shared-vpc + +One **VPC** and **three** separate EKS clusters that share it: + +| Cluster | Compute mode | Subnets used | +| --------- | ------------------- | ------------------------------------- | +| `classic` | EC2 managed nodes | Public + private | +| `fargate` | Fargate profiles | Public + private (profiles → private) | +| `automode` | EKS Auto Mode | Private only | + +Subnet tags: the VPC module sets `kubernetes.io/cluster/` = `shared` via `enable_eks_tags`. The fargate and automode cluster tags are merged into the VPC module's `tags` input so the `aws_subnet` resource owns all three tag keys — avoiding the perpetual drift that `aws_ec2_tag` causes when fighting `aws_subnet` in AWS provider v6. + +## EBS CSI and kube-infra (`cluster_names`) + +The default `terraform.tfvars.example` uses **classic = `eks-1`** and **automode = `eks-2`**. That matches **kube-infra** `infrastructure/aws-ebs-csi-driver/overlays/`: **`eks-1`** is the vendored driver + `ebs.csi.aws.com` (EC2 nodes); **`eks-2`** patches `ebs-sc` to **`ebs.csi.eks.amazonaws.com`** for EKS Auto Mode. + +If you instead name your Auto Mode cluster `eks-1` and your classic cluster `eks-2`, swap those two keys in `cluster_names` **and** swap the EBS CSI overlay contents (or maintain cluster-specific overlays) so each cluster’s StorageClass matches its compute mode—otherwise PVCs (e.g. Headlamp) can sit in `VolumeBinding` until the scheduler times out. + +## Argo CD + +- Each cluster has its own **ACK / KRO / Argo CD** capability when `argocd_idc_instance_arn` is set. +- **Three** `argocd-codeconnections` modules create **three** GitHub connections (`github-`). Use the matching output UUID in each cluster’s Argo CD `repoURL` (do not reuse another cluster’s connection). + +Shared **tfvars** (one block for all three): `argocd_idc_instance_arn`, `argocd_rbac_role_mappings`, `argocd_vpce_ids`. + +## Fargate cluster: AWS Load Balancer Controller (IRSA) + +The **Fargate** cluster has no **Pod Identity** agent. For the AWS Load Balancer Controller (or any addon that needs AWS API access), use **IRSA** only. + +## Fargate cluster: IRSA (ALB controller + SQS / KEDA) + +[EKS Pod Identity is not supported for pods on Fargate](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). This example uses **IRSA** (OIDC) for AWS API access from Fargate workloads where needed. + +- **AWS Load Balancer Controller:** `aws_load_balancer_controller_identity_type = "irsa"`. After apply, use `terraform output -raw fargate_aws_load_balancer_controller_role_arn` for the controller ServiceAccount `eks.amazonaws.com/role-arn` (see `kube-infra` ALB overlay for eks-3). +- **Celery SQS + KEDA:** `enable_sqs_access = true` with **`sqs_identity_type = "irsa"`** on the Fargate module only. Classic and Auto Mode clusters keep **`sqs_identity_type = "pod_identity"`**. After apply, use **`terraform output -json fargate_sqs_role_arns`** — keys **`celery/celery-workload`** and **`keda/keda-operator`**. Annotate those Kubernetes ServiceAccounts in **kube-devops-apps** (celery eks-3 overlay) and **kube-infra** (KEDA eks-3 overlay) with `eks.amazonaws.com/role-arn: `. IAM role names follow `terraform-aws-eks-basic/sqs-iam.tf`: `-sqs---role` with `/` in the map key replaced by `-` (e.g. `eks-3-sqs-celery-celery-workload-role` when `cluster_names.fargate = "eks-3"`). + +The **EKS Pod Identity Agent** add-on remains enabled on the Fargate cluster for consistency with other examples; it does **not** grant Pod Identity credentials to Fargate pods. + +This example: + +- Adds **Fargate profiles** for `kube-system`, `aws-load-balancer-controller`, `gatekeeper-system`, **`keda`**, **`workload_sqs`**, and the **default app** namespace (`fargate_namespace`, e.g. `app`). Profile selectors are in `main.tf`. +- When **`argocd_idc_instance_arn` is set**, merges an **`argocd`** profile so the EKS **Argo CD** capability (namespace `argocd`) can schedule on this Fargate-only cluster. +- Sets `enable_aws_load_balancer_controller = true`, `aws_load_balancer_controller_identity_type = "irsa"`. + +Add more profiles (or label selectors) for any other GitOps-managed namespace that must run on this cluster (e.g. `external-secrets`). + +After `terraform apply`, use `terraform output -raw fargate_aws_load_balancer_controller_role_arn` for the Helm `eks.amazonaws.com/role-arn` annotation (or match the role name in `kube-infra` to your `cluster_names.fargate`). + +## Cost and operations + +Running three control planes and nodes/Fargate/Auto Mode is **expensive**. Use for demos or integration testing only. + +```bash +cd examples/multi-cluster-shared-vpc +cp terraform.tfvars.example terraform.tfvars # optional if you maintain terraform.tfvars locally (gitignored) +terraform init +terraform plan +``` + +### kubeconfig + +```bash +aws eks update-kubeconfig --name $(terraform output -raw classic_cluster_name) --region $(terraform output -raw aws_region) --alias classic +aws eks update-kubeconfig --name $(terraform output -raw fargate_cluster_name) --region $(terraform output -raw aws_region) --alias fargate +aws eks update-kubeconfig --name $(terraform output -raw automode_cluster_name) --region $(terraform output -raw aws_region) --alias automode +``` + +## Outputs + +- `classic_argocd_connection_ids`, `fargate_argocd_connection_ids`, `automode_argocd_connection_ids` — maps keyed by connection `name` (e.g. `github-eks-1`). +- `fargate_aws_load_balancer_controller_role_arn` — IRSA role for ALB controller on the Fargate cluster. +- `fargate_sqs_role_arns` — map of IRSA role ARNs for SQS (`celery/celery-workload`, `keda/keda-operator`) on the Fargate cluster. diff --git a/examples/multi-cluster-shared-vpc/main.tf b/examples/multi-cluster-shared-vpc/main.tf new file mode 100644 index 0000000..cf3e8b3 --- /dev/null +++ b/examples/multi-cluster-shared-vpc/main.tf @@ -0,0 +1,403 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +data "aws_caller_identity" "current" {} + +# Fargate and Auto Mode cluster subnet tags merged into vpc tags so aws_subnet owns them. +# aws_ec2_tag would conflict with aws_subnet in AWS provider v6 (both fighting over the same tag keys). +# +# EBS / kube-infra: name the classic cluster and Auto Mode cluster to match +# kube-infra `infrastructure/aws-ebs-csi-driver/overlays/` (this example uses +# classic = eks-1 → vendored CSI + ebs.csi.aws.com; automode = eks-2 → ebs.csi.eks.amazonaws.com). +locals { + vpc_tags = merge(var.tags, { + "kubernetes.io/cluster/${var.cluster_names.fargate}" = "shared" + "kubernetes.io/cluster/${var.cluster_names.automode}" = "shared" + }) + + ack_kro_capabilities = { + ack = { + iam_policy_arns = { + sqs = "arn:aws:iam::aws:policy/AmazonSQSFullAccess" + } + } + kro = {} + } + + sqs_access_queue_arn = { + classic = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.classic}-celery-jobs" + fargate = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.fargate}-celery-jobs" + automode = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.automode}-celery-jobs" + } + + sqs_access_queue_arn_dlq = { + classic = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.classic}-celery-jobs-dlq" + fargate = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.fargate}-celery-jobs-dlq" + automode = "arn:aws:sqs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:${var.cluster_names.automode}-celery-jobs-dlq" + } +} + +# One VPC for three clusters. enable_eks_tags adds kubernetes.io/cluster/=shared via eks_cluster_name. +# The extra fargate/automode cluster tags come from local.vpc_tags above. +module "vpc" { + source = "cloudbuildlab/vpc/aws" + + vpc_name = var.vpc_name + vpc_cidr = "10.0.0.0/16" + availability_zones = ["${var.aws_region}a", "${var.aws_region}b"] + + public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] + private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"] + + create_igw = true + nat_gateway_type = "single" + + enable_eks_tags = true + eks_cluster_name = var.cluster_names.classic + tags = local.vpc_tags +} + +# ── Classic EC2 managed node groups ─────────────────────────────────────────── +module "eks_classic" { + source = "../../" + + name = var.cluster_names.classic + kubernetes_version = var.kubernetes_version + vpc_id = module.vpc.vpc_id + subnet_ids = concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids) + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries_classic + enable_cluster_creator_admin_permissions = true + cloudwatch_log_group_force_destroy = true + + addons = { + coredns = { addon_version = "v1.13.2-eksbuild.4" } + eks-pod-identity-agent = { + before_compute = true + addon_version = "v1.3.10-eksbuild.2" + } + kube-proxy = { addon_version = "v1.35.3-eksbuild.2" } + vpc-cni = { + before_compute = true + addon_version = "v1.21.1-eksbuild.7" + configuration_values = jsonencode({ + enableNetworkPolicy = "true" + nodeAgent = { enablePolicyEventLogs = "true" } + }) + } + } + + eks_managed_node_groups = { + one = { + name = "ng-1" + ami_type = "AL2023_x86_64_STANDARD" + instance_types = ["t3a.large"] + min_size = 1 + max_size = 3 + desired_size = 2 + metadata_options = { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 1 + } + } + } + + capabilities = merge( + local.ack_kro_capabilities, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [{ + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + }] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { idc_instance_arn = var.argocd_idc_instance_arn, idc_region = var.aws_region } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { vpce_ids = var.argocd_vpce_ids } + } + } + } + } : {} + ) + + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + enable_secrets_manager = true + secrets_manager_identity_type = "pod_identity" + secrets_manager_associations = [ + { namespace = "sm-operator-system", service_account = "awssm-sync" }, + ] + secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] + + enable_sqs_access = true + sqs_identity_type = "pod_identity" + sqs_access = [ + { + namespace = "celery" + service_account = "celery-workload" + queue_arns = [ + local.sqs_access_queue_arn.classic, + local.sqs_access_queue_arn_dlq.classic, + ] + mode = "consumer" + }, + { + namespace = "keda" + service_account = "keda-operator" + queue_arns = [local.sqs_access_queue_arn.classic] + mode = "read_only" + }, + ] + + tags = var.tags +} + +# ── Fargate only ───────────────────────────────────────────────────────────── +module "eks_fargate" { + source = "../../" + + name = var.cluster_names.fargate + kubernetes_version = var.kubernetes_version + vpc_id = module.vpc.vpc_id + subnet_ids = concat(module.vpc.public_subnet_ids, module.vpc.private_subnet_ids) + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries_fargate + enable_cluster_creator_admin_permissions = true + cloudwatch_log_group_force_destroy = true + + addons = { + coredns = { + addon_version = "v1.13.2-eksbuild.4" + configuration_values = jsonencode({ computeType = "fargate" }) + } + vpc-cni = { + before_compute = true + addon_version = "v1.21.1-eksbuild.7" + } + eks-pod-identity-agent = { + before_compute = true + addon_version = "v1.3.10-eksbuild.2" + } + } + + # Per-namespace profiles (Fargate matches namespace only, not labels, unless you add selectors). + # When argocd_idc_instance_arn is set, the module installs Argo CD into namespace "argocd" on this + # cluster — without an argocd profile, those pods stay Pending on Fargate-only. + fargate_profiles = merge( + { + kube-system = { + selectors = [{ namespace = "kube-system" }] + subnet_ids = module.vpc.private_subnet_ids + } + aws-load-balancer-controller = { + selectors = [{ namespace = "aws-load-balancer-controller" }] + subnet_ids = module.vpc.private_subnet_ids + } + gatekeeper-system = { + selectors = [{ namespace = "gatekeeper-system" }] + subnet_ids = module.vpc.private_subnet_ids + } + keda = { + selectors = [{ namespace = "keda" }] + subnet_ids = module.vpc.private_subnet_ids + } + workload_sqs = { + selectors = [{ namespace = "celery" }] + subnet_ids = module.vpc.private_subnet_ids + } + default = { + selectors = [{ namespace = var.fargate_namespace }] + subnet_ids = module.vpc.private_subnet_ids + } + }, + var.argocd_idc_instance_arn != null ? { + argocd = { + selectors = [{ namespace = "argocd" }] + subnet_ids = module.vpc.private_subnet_ids + } + } : {} + ) + + enable_aws_load_balancer_controller = true + aws_load_balancer_controller_identity_type = "irsa" + + capabilities = merge( + local.ack_kro_capabilities, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [{ + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + }] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { idc_instance_arn = var.argocd_idc_instance_arn, idc_region = var.aws_region } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { vpce_ids = var.argocd_vpce_ids } + } + } + } + } : {} + ) + + # Fargate: EKS Pod Identity is not supported for pods on Fargate (AWS). Use IRSA for SQS roles + # and annotate ServiceAccounts (kube-devops-apps celery SA; kube-infra keda-operator SA on eks-3). + enable_sqs_access = true + sqs_identity_type = "irsa" + sqs_access = [ + { + namespace = "celery" + service_account = "celery-workload" + queue_arns = [ + local.sqs_access_queue_arn.fargate, + local.sqs_access_queue_arn_dlq.fargate, + ] + mode = "consumer" + }, + { + namespace = "keda" + service_account = "keda-operator" + queue_arns = [local.sqs_access_queue_arn.fargate] + mode = "read_only" + }, + ] + + tags = var.tags +} + +# ── Auto Mode ─────────────────────────────────────────────────────────────── +module "eks_automode" { + source = "../../" + + name = var.cluster_names.automode + kubernetes_version = var.kubernetes_version + vpc_id = module.vpc.vpc_id + subnet_ids = module.vpc.private_subnet_ids + + endpoint_public_access = var.endpoint_public_access + public_access_cidrs = var.public_access_cidrs + private_access_cidrs = var.private_access_cidrs + + access_entries = var.access_entries_automode + enable_cluster_creator_admin_permissions = true + cloudwatch_log_group_force_destroy = true + + enable_automode = true + automode_node_pools = ["system", "general-purpose"] + addons = {} + + capabilities = merge( + local.ack_kro_capabilities, + var.argocd_idc_instance_arn != null ? { + argocd = { + access_entry_policy_associations = [{ + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + }] + configuration = { + argo_cd = { + namespace = "argocd" + aws_idc = { idc_instance_arn = var.argocd_idc_instance_arn, idc_region = var.aws_region } + rbac_role_mapping = var.argocd_rbac_role_mappings + network_access = { vpce_ids = var.argocd_vpce_ids } + } + } + } + } : {} + ) + + ebs_csi_driver_identity_type = "pod_identity" + enable_ebs_csi_driver = true + + enable_secrets_manager = true + secrets_manager_identity_type = "pod_identity" + secrets_manager_associations = [ + { namespace = "sm-operator-system", service_account = "awssm-sync" }, + ] + secrets_manager_secret_name_prefixes = ["bitwarden/sm-operator"] + + enable_sqs_access = true + sqs_identity_type = "pod_identity" + sqs_access = [ + { + namespace = "celery" + service_account = "celery-workload" + queue_arns = [ + local.sqs_access_queue_arn.automode, + local.sqs_access_queue_arn_dlq.automode, + ] + mode = "consumer" + }, + { + namespace = "keda" + service_account = "keda-operator" + queue_arns = [local.sqs_access_queue_arn.automode] + mode = "read_only" + }, + ] + + tags = var.tags +} + +# ── CodeConnections: one per cluster (separate Argo CD capability roles) ──── +module "argocd_connections_classic" { + source = "../../modules/argocd-codeconnections" + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks_classic.cluster_capability_role_names["argocd"] + connections = [ + { name = "github-${var.cluster_names.classic}", provider_type = "GitHub" } + ] + tags = var.tags +} + +module "argocd_connections_fargate" { + source = "../../modules/argocd-codeconnections" + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks_fargate.cluster_capability_role_names["argocd"] + connections = [ + { name = "github-${var.cluster_names.fargate}", provider_type = "GitHub" } + ] + tags = var.tags +} + +module "argocd_connections_automode" { + source = "../../modules/argocd-codeconnections" + count = var.argocd_idc_instance_arn != null ? 1 : 0 + + argocd_capability_role_name = module.eks_automode.cluster_capability_role_names["argocd"] + connections = [ + { name = "github-${var.cluster_names.automode}", provider_type = "GitHub" } + ] + tags = var.tags +} diff --git a/examples/multi-cluster-shared-vpc/outputs.tf b/examples/multi-cluster-shared-vpc/outputs.tf new file mode 100644 index 0000000..109d81c --- /dev/null +++ b/examples/multi-cluster-shared-vpc/outputs.tf @@ -0,0 +1,83 @@ +output "aws_region" { + description = "AWS region" + value = var.aws_region +} + +output "vpc_id" { + description = "Shared VPC ID" + value = module.vpc.vpc_id +} + +output "public_subnet_ids" { + value = module.vpc.public_subnet_ids +} + +output "private_subnet_ids" { + value = module.vpc.private_subnet_ids +} + +# Classic +output "classic_cluster_name" { + value = module.eks_classic.cluster_name +} + +output "classic_cluster_endpoint" { + value = module.eks_classic.cluster_endpoint +} + +output "classic_oidc_provider_arn" { + value = module.eks_classic.oidc_provider_arn +} + +output "classic_argocd_connection_ids" { + description = "CodeConnection UUIDs for the classic cluster Argo CD (repoURL must use these)" + value = var.argocd_idc_instance_arn != null ? module.argocd_connections_classic[0].connection_ids : null +} + +# Fargate +output "fargate_cluster_name" { + value = module.eks_fargate.cluster_name +} + +output "fargate_cluster_endpoint" { + value = module.eks_fargate.cluster_endpoint +} + +output "fargate_oidc_provider_arn" { + value = module.eks_fargate.oidc_provider_arn +} + +output "fargate_argocd_connection_ids" { + value = var.argocd_idc_instance_arn != null ? module.argocd_connections_fargate[0].connection_ids : null +} + +output "fargate_aws_load_balancer_controller_role_arn" { + description = "IRSA role for AWS Load Balancer Controller on the Fargate cluster (use in Helm serviceAccount.annotation)" + value = module.eks_fargate.aws_load_balancer_controller_role_arn +} + +output "fargate_sqs_role_arns" { + description = "IRSA role ARNs for SQS (celery consumer + KEDA metrics) on the Fargate cluster when sqs_identity_type = irsa" + value = module.eks_fargate.sqs_role_arns +} + +output "fargate_profiles" { + value = module.eks_fargate.fargate_profiles +} + +# Auto Mode +output "automode_cluster_name" { + value = module.eks_automode.cluster_name +} + +output "automode_cluster_endpoint" { + value = module.eks_automode.cluster_endpoint +} + +output "automode_oidc_provider_arn" { + value = module.eks_automode.oidc_provider_arn +} + +output "automode_argocd_connection_ids" { + value = var.argocd_idc_instance_arn != null ? module.argocd_connections_automode[0].connection_ids : null +} diff --git a/examples/multi-cluster-shared-vpc/terraform.tfvars.example b/examples/multi-cluster-shared-vpc/terraform.tfvars.example new file mode 100644 index 0000000..f8f4e9d --- /dev/null +++ b/examples/multi-cluster-shared-vpc/terraform.tfvars.example @@ -0,0 +1,67 @@ +# Copy to terraform.tfvars. Repo .gitignore ignores *.tfvars. +# +# One VPC, three EKS clusters (classic EC2, Fargate, Auto Mode), shared networking. +# Each cluster has its own Argo CD capability and its own CodeConnections (three GitHub connections). +# Argo CD settings below are shared (same IdC / RBAC / VPCE) but apply to all three UIs. + +aws_region = "ap-southeast-2" +# Name tag for the shared VPC. The VPC module tags subnets with kubernetes.io/cluster/. +vpc_name = "eks-shared" + +tags = { + Environment = "dev" + ManagedBy = "terraform" +} + +# Must be unique and stable: classic name is used by the VPC module for kubernetes.io/cluster/ on subnets. +# Keep names aligned with kube-infra EBS CSI overlays: eks-1 = classic (ebs.csi.aws.com), eks-2 = Auto Mode +# (ebs.csi.eks.amazonaws.com). See README "EBS CSI and kube-infra". +cluster_names = { + classic = "eks-1" # EC2 managed node groups + automode = "eks-2" # EKS Auto Mode + fargate = "eks-3" # Fargate only +} + +kubernetes_version = "1.35" + +# ── API access (all three clusters) ───────────────────────────────────────── +endpoint_public_access = true +public_access_cidrs = ["0.0.0.0/0"] +# private_access_cidrs = ["10.0.0.0/8"] + +# ── Access entries (per cluster; empty ok — cluster creator admin is enabled on each) ── +access_entries_classic = {} +access_entries_fargate = {} +access_entries_automode = {} + +# Example — same admin principal on all three (replace 123456789012 and role name): +# access_entries_classic = { +# admin = { +# principal_arn = "arn:aws:iam::123456789012:role/MyAdminRole" +# type = "STANDARD" +# policy_associations = { +# admin = { +# policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" +# access_scope = { type = "cluster" } +# } +# } +# } +# } +# access_entries_fargate = { ... same structure ... } +# access_entries_automode = { ... same structure ... } + +# ── Argo CD (all three clusters) + three CodeConnections ──────────────────── +# Set to null to skip Argo CD and CodeConnections on every cluster. +argocd_idc_instance_arn = null +# argocd_idc_instance_arn = "arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxx" + +argocd_rbac_role_mappings = [] +# argocd_rbac_role_mappings = [ +# { role = "ADMIN", identity = [{ type = "SSO_USER", id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }] } +# ] + +argocd_vpce_ids = [] +# argocd_vpce_ids = ["vpce-xxxxxxxx"] # private Argo CD UI; KNOWN BUG if toggling public/private in-place + +# ── Fargate profile namespace ─────────────────────────────────────────────── +fargate_namespace = "app" diff --git a/examples/multi-cluster-shared-vpc/variables.tf b/examples/multi-cluster-shared-vpc/variables.tf new file mode 100644 index 0000000..82c996d --- /dev/null +++ b/examples/multi-cluster-shared-vpc/variables.tf @@ -0,0 +1,153 @@ +variable "aws_region" { + description = "AWS region for all resources" + type = string + default = "ap-southeast-2" +} + +variable "vpc_name" { + description = "Name tag for the shared VPC (also drives subnet kubernetes.io/cluster tag for the classic cluster via the VPC module)" + type = string + default = "eks-shared" +} + +variable "tags" { + description = "Common tags for VPC and all three clusters" + type = map(string) + default = { + Environment = "dev" + ManagedBy = "terraform" + } +} + +# ── Cluster identity ───────────────────────────────────────────────────────── + +variable "cluster_names" { + description = "Unique EKS cluster names (must be distinct). Classic name should match what the VPC module uses for kubernetes.io/cluster/* subnet tags." + type = object({ + classic = string + fargate = string + automode = string + }) + default = { + classic = "eks-shared-classic" + fargate = "eks-shared-fargate" + automode = "eks-shared-automode" + } +} + +variable "kubernetes_version" { + description = "Kubernetes version for all three clusters (Auto Mode requires 1.29+)" + type = string + default = "1.35" +} + +# ── API access (shared defaults for all three clusters) ───────────────────── + +variable "endpoint_public_access" { + description = "Whether each cluster's Kubernetes API is publicly reachable" + type = bool + default = true +} + +variable "public_access_cidrs" { + type = list(string) + default = ["0.0.0.0/0"] +} + +variable "private_access_cidrs" { + type = list(string) + default = [] +} + +# ── EKS access entries (one map per cluster) ──────────────────────────────── + +variable "access_entries_classic" { + description = "Access entries for the classic (EC2) cluster" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "access_entries_fargate" { + description = "Access entries for the Fargate cluster" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +variable "access_entries_automode" { + description = "Access entries for the Auto Mode cluster" + type = map(object({ + kubernetes_groups = optional(list(string)) + principal_arn = string + type = optional(string, "STANDARD") + user_name = optional(string) + tags = optional(map(string), {}) + policy_associations = optional(map(object({ + policy_arn = string + access_scope = object({ + namespaces = optional(list(string)) + type = string + }) + })), {}) + })) + default = {} +} + +# ── Argo CD (same IdC config applied to all three clusters; separate installs + CodeConnections each) ── + +variable "argocd_idc_instance_arn" { + description = "IAM Identity Center instance ARN. When null, Argo CD and CodeConnections are disabled on all three clusters." + type = string + default = null +} + +variable "argocd_rbac_role_mappings" { + description = "Shared RBAC mappings for all three Argo CD UIs" + type = list(object({ + role = string + identity = list(object({ + type = string + id = string + })) + })) + default = [] +} + +variable "argocd_vpce_ids" { + description = "Shared VPC interface endpoint IDs for Argo CD UI on all three clusters (empty = public UI)" + type = list(string) + default = [] +} + +# ── Fargate-only ─────────────────────────────────────────────────────────── + +variable "fargate_namespace" { + description = "Namespace matched by the default Fargate profile on the Fargate cluster" + type = string + default = "app" +} diff --git a/examples/pod-identity/.terraform.lock.hcl b/examples/pod-identity/.terraform.lock.hcl deleted file mode 100644 index 366ba2e..0000000 --- a/examples/pod-identity/.terraform.lock.hcl +++ /dev/null @@ -1,104 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "6.33.0" - constraints = ">= 6.0.0" - hashes = [ - "h1:MxndtTQD9qHGQwIjScLlY4BQZgdjJ1Lsq1TdwodsxmU=", - "zh:207f3f9db05c11429a241b84deeecfbd4caa941792c2c49b09c8c85cd59474dd", - "zh:25c36ad1f4617aeb23f8cd18efc7856127db721f6cf3e2e474236af019ce9ad1", - "zh:2685af1f3eb9abfce3168777463eaaad9dba5687f9f84d8bb579cb878bcfa18b", - "zh:57e28457952cf43923533af0a9bb322164be5fc3d66c080b5c59ee81950e9ef6", - "zh:5b6cd074f9e3a8d91841e739d259fe11f181e69c4019e3321231b35c0dde08c8", - "zh:6e3251500cebf1effb9c68d49041268ea270f75b122b94d261af231a8ebfa981", - "zh:7eee56f52f4b94637793508f3e83f68855f5f884a77aed2bd2fe77480c89e33d", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9e228c92db1b9e36a0f899d6ab7446e6b8cf3183112d4f1c1613d6827a0ed3d6", - "zh:b34a84475e91715352ed1119b21e51a81d8ad12e93c86d4e78cd2d315d02dcab", - "zh:cdcc05a423a78a9b2c4e2844c58ecbf2ce6a3117cab353fa05197782d6f76667", - "zh:d0f5f6b1399cfa1b64f3e824bee9e39ff15d5a540ff197e9bfc157fe354a8426", - "zh:d9525dbb53468dee6b8e6d15669d25957e9872bf1cd386231dff93c8c659f1d7", - "zh:ed37db2df08b961a7fc390164273e602767ca6922f57560daa9678a2e1315fd0", - "zh:f6adc66b86e12041a2d3739600e6a153a1f5752dd363db11469f6f4dbd090080", - ] -} - -provider "registry.terraform.io/hashicorp/helm" { - version = "2.17.0" - constraints = ">= 2.13.0, ~> 2.13" - hashes = [ - "h1:K5FEjxvDnxb1JF1kG1xr8J3pNGxoaR3Z0IBG9Csm/Is=", - "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", - "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", - "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", - "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", - "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", - "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", - "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", - "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", - "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", - "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", - "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = ">= 2.30.0, ~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - hashes = [ - "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.2.1" - constraints = ">= 4.0.0, ~> 4.0" - hashes = [ - "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", - "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", - "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", - "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", - "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", - "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", - "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", - "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", - "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", - "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", - "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", - "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/examples/pod-identity/loki-logs-bucket.tf b/examples/pod-identity/loki-logs-bucket.tf index 53f057b..d6eb3bf 100644 --- a/examples/pod-identity/loki-logs-bucket.tf +++ b/examples/pod-identity/loki-logs-bucket.tf @@ -1,9 +1,10 @@ -# S3 bucket for Loki chunks and index (created by this example; IAM wired via module.eks s3_access) +# S3 bucket for Loki chunks and index (created by this example; IAM wired via module.eks s3_access). +# Bucket name uses cluster_name so it is unique in your account and avoids a hard-coded global S3 name. resource "aws_s3_bucket" "loki_logs" { - bucket = "loki-logs-geonet-dev" + bucket = "${var.cluster_name}-loki-logs" tags = merge(local.tags, { - Name = "loki-logs-geonet-dev" + Name = "${var.cluster_name}-loki-logs" }) } diff --git a/examples/pod-identity/outputs.tf b/examples/pod-identity/outputs.tf index 70547d3..3df7be9 100644 --- a/examples/pod-identity/outputs.tf +++ b/examples/pod-identity/outputs.tf @@ -41,7 +41,7 @@ output "s3_role_arns" { } output "loki_logs_bucket_name" { - description = "Name of the S3 bucket created for Loki storage (loki-logs-geonet-dev)" + description = "Name of the S3 bucket created for Loki storage (`{cluster_name}-loki-logs`)" value = aws_s3_bucket.loki_logs.id } diff --git a/examples/private-endpoint/.terraform.lock.hcl b/examples/private-endpoint/.terraform.lock.hcl deleted file mode 100644 index 22135e5..0000000 --- a/examples/private-endpoint/.terraform.lock.hcl +++ /dev/null @@ -1,123 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "6.35.1" - constraints = ">= 6.0.0" - hashes = [ - "h1:0/uXxSpL98lpRqjRhjAvvWZVnJZnbOehfAlTrcPXURI=", - "zh:0a16d1b0ba9379e5c5295e6b3caa42f0b8ba6b9f0a7cc9dbe58c232cf995db2d", - "zh:4b2e69907a1a2c557e45ef590f9fd6187ab5bf90378346ba9f723535e49ce908", - "zh:56bdafda0d629e15dc3dd9275b54f1fb953e2e09a3bc1a34e027da9d03ea4893", - "zh:5b84e933989150249036f84faad221dce0daa9d3043ff24401547e18f00b121e", - "zh:70bac98c27a14cb2cedabd741a1f7f1bab074c127efdcf02b54dbcf0d03db3cc", - "zh:7184f48bd077eaf68e184fd44f97e2d971cb77c59a68aedb95a0f8dc01b134fe", - "zh:7367589ae8b584bfcd83c973f5003e15010a453349c017a0d2cca8772d4fcfd9", - "zh:7ec9699dee49dd31bbc2d0e50fa1fff451eee5c1d9fd59bca7412acb49ce6594", - "zh:92dd139b96977a64af0e976cd06e84921033678ab97550f1b687c0ea54a8e82c", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9f2df575a5b010db60068668c48806595a3d617a2c0305035283fe8b72f07b19", - "zh:a4602b7602c75c8f726bdc7e706dc5c26736e47cc8381be01386aa8d8d998403", - "zh:bc25fefeeee10425df7aebfc21dc6532d19acdf03fa97b9e6d8c113adffd0a1d", - "zh:f445592040b5fc368a12e6edeffc951b2eb41e86413c4074638a13376e25a9cc", - "zh:ff43962a48bd8f85e17188736bbd3c145b6a1320bd8303221f6b4f9ec861e1e6", - ] -} - -provider "registry.terraform.io/hashicorp/helm" { - version = "2.17.0" - constraints = ">= 2.13.0, ~> 2.13" - hashes = [ - "h1:K5FEjxvDnxb1JF1kG1xr8J3pNGxoaR3Z0IBG9Csm/Is=", - "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", - "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", - "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", - "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", - "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", - "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", - "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", - "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", - "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", - "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", - "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/http" { - version = "3.5.0" - hashes = [ - "h1:8bUoPwS4hahOvzCBj6b04ObLVFXCEmEN8T/5eOHmWOM=", - "zh:047c5b4920751b13425efe0d011b3a23a3be97d02d9c0e3c60985521c9c456b7", - "zh:157866f700470207561f6d032d344916b82268ecd0cf8174fb11c0674c8d0736", - "zh:1973eb9383b0d83dd4fd5e662f0f16de837d072b64a6b7cd703410d730499476", - "zh:212f833a4e6d020840672f6f88273d62a564f44acb0c857b5961cdb3bbc14c90", - "zh:2c8034bc039fffaa1d4965ca02a8c6d57301e5fa9fff4773e684b46e3f78e76a", - "zh:5df353fc5b2dd31577def9cc1a4ebf0c9a9c2699d223c6b02087a3089c74a1c6", - "zh:672083810d4185076c81b16ad13d1224b9e6ea7f4850951d2ab8d30fa6e41f08", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7b4200f18abdbe39904b03537e1a78f21ebafe60f1c861a44387d314fda69da6", - "zh:843feacacd86baed820f81a6c9f7bd32cf302db3d7a0f39e87976ebc7a7cc2ee", - "zh:a9ea5096ab91aab260b22e4251c05f08dad2ed77e43e5e4fadcdfd87f2c78926", - "zh:d02b288922811739059e90184c7f76d45d07d3a77cc48d0b15fd3db14e928623", - ] -} - -provider "registry.terraform.io/hashicorp/kubernetes" { - version = "2.38.0" - constraints = ">= 2.30.0, ~> 2.30" - hashes = [ - "h1:5CkveFo5ynsLdzKk+Kv+r7+U9rMrNjfZPT3a0N/fhgE=", - "zh:0af928d776eb269b192dc0ea0f8a3f0f5ec117224cd644bdacdc682300f84ba0", - "zh:1be998e67206f7cfc4ffe77c01a09ac91ce725de0abaec9030b22c0a832af44f", - "zh:326803fe5946023687d603f6f1bab24de7af3d426b01d20e51d4e6fbe4e7ec1b", - "zh:4a99ec8d91193af961de1abb1f824be73df07489301d62e6141a656b3ebfff12", - "zh:5136e51765d6a0b9e4dbcc3b38821e9736bd2136cf15e9aac11668f22db117d2", - "zh:63fab47349852d7802fb032e4f2b6a101ee1ce34b62557a9ad0f0f0f5b6ecfdc", - "zh:924fb0257e2d03e03e2bfe9c7b99aa73c195b1f19412ca09960001bee3c50d15", - "zh:b63a0be5e233f8f6727c56bed3b61eb9456ca7a8bb29539fba0837f1badf1396", - "zh:d39861aa21077f1bc899bc53e7233262e530ba8a3a2d737449b100daeb303e4d", - "zh:de0805e10ebe4c83ce3b728a67f6b0f9d18be32b25146aa89116634df5145ad4", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:faf23e45f0090eef8ba28a8aac7ec5d4fdf11a36c40a8d286304567d71c1e7db", - ] -} - -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - hashes = [ - "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", - ] -} - -provider "registry.terraform.io/hashicorp/tls" { - version = "4.2.1" - constraints = ">= 4.0.0, ~> 4.0" - hashes = [ - "h1:F5d6bQY8UlBo0D71Sv7CsV+3aZOFz0yeNF+vufog7h4=", - "zh:0d1e7d07ac973b97fa228f46596c800de830820506ee145626f079dd6bbf8d8a", - "zh:5c7e3d4348cb4861ab812973ef493814a4b224bdd3e9d534a7c8a7c992382b86", - "zh:7c6d4a86cd7a4e9c1025c6b3a3a6a45dea202af85d870cddbab455fb1bd568ad", - "zh:7d0864755ba093664c4b2c07c045d3f5e3d7c799dda1a3ef33d17ed1ac563191", - "zh:83734f57950ab67c0d6a87babdb3f13c908cbe0a48949333f489698532e1391b", - "zh:951e3c285218ebca0cf20eaa4265020b4ef042fea9c6ade115ad1558cfe459e5", - "zh:b9543955b4297e1d93b85900854891c0e645d936d8285a190030475379c5c635", - "zh:bb1bd9e86c003d08c30c1b00d44118ed5bbbf6b1d2d6f7eaac4fa5c6ebea5933", - "zh:c9477bfe00653629cd77ddac3968475f7ad93ac3ca8bc45b56d1d9efb25e4a6e", - "zh:d4cfda8687f736d0cba664c22ec49dae1188289e214ef57f5afe6a7217854fed", - "zh:dc77ee066cf96532a48f0578c35b1eaf6dc4d8ddd0e3ae8e029a3b10676dd5d3", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} diff --git a/examples/private-endpoint/README.md b/examples/private-endpoint/README.md index 0150e88..82a671b 100644 --- a/examples/private-endpoint/README.md +++ b/examples/private-endpoint/README.md @@ -31,5 +31,6 @@ Then run `kubectl get nodes` (with AWS credentials if needed, e.g. `aws-vault ex ## Usage +- Copy `terraform.tfvars.example` to `terraform.tfvars` and set `access_entries`. - Run `terraform init`, `terraform plan`, `terraform apply`. - Connect from a network that can reach the private endpoint (e.g. VPN). Use `cluster_endpoint` or `cluster_endpoint_hostname` outputs if needed. diff --git a/examples/private-endpoint/terraform.tfvars.example b/examples/private-endpoint/terraform.tfvars.example new file mode 100644 index 0000000..9923e9a --- /dev/null +++ b/examples/private-endpoint/terraform.tfvars.example @@ -0,0 +1,23 @@ +# Copy to terraform.tfvars and set your values. +# Add terraform.tfvars to .gitignore if it contains account-specific data. +# +# This example uses endpoint_public_access = false (private API only). +# Run Terraform and kubectl from inside the VPC or over VPN. + +# Cluster access (required for kubectl) +access_entries = { + admin = { + principal_arn = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME" + type = "STANDARD" + policy_associations = { + admin = { + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + access_scope = { type = "cluster" } + } + } + } +} + +# Optional: +# aws_region = "ap-southeast-2" +# cluster_name = "cltest" diff --git a/fargate.tf b/fargate.tf index 0733b93..9b4bc53 100644 --- a/fargate.tf +++ b/fargate.tf @@ -66,13 +66,20 @@ resource "aws_eks_fargate_profile" "this" { for_each = each.value.selectors content { namespace = selector.value.namespace - labels = try(selector.value.labels, null) + # coalesce: optional labels are null when omitted; AWS returns {} on read — matching avoids output/state drift. + labels = coalesce(selector.value.labels, {}) } } tags = merge(var.tags, each.value.tags) - depends_on = [aws_eks_cluster.this] + depends_on = [ + aws_eks_cluster.this, + # Role must exist and be attachable before the profile can use it (BYO role: attachment count is 0). + aws_iam_role_policy_attachment.eks_fargate, + # API auth clusters need the FARGATE_LINUX access entry before Fargate can run workloads reliably. + aws_eks_access_entry.fargate, + ] } ################################################################################ diff --git a/karpenter.tf b/karpenter.tf new file mode 100644 index 0000000..59b5f5b --- /dev/null +++ b/karpenter.tf @@ -0,0 +1,64 @@ +################################################################################ +# Karpenter — node autoscaling (terraform-aws-modules/eks/karpenter) +# Controller: Pod Identity (default). Node role + interruption queue + EventBridge. +################################################################################ + +check "karpenter_classic_cluster" { + assert { + condition = !(var.enable_karpenter && var.enable_automode) + error_message = "enable_karpenter requires a classic EKS cluster (enable_automode must be false)." + } +} + +module "karpenter" { + source = "terraform-aws-modules/eks/aws//modules/karpenter" + version = "~> 21.3.0" + + count = var.enable_karpenter ? 1 : 0 + + cluster_name = aws_eks_cluster.this.name + cluster_ip_family = var.cluster_ip_family + region = var.region + tags = var.tags + + namespace = var.karpenter_namespace + service_account = var.karpenter_service_account + + # Match upstream CloudFormation default (queue name = cluster name) for settings.interruptionQueue + queue_name = var.name + + iam_role_use_name_prefix = false + iam_role_name = "${var.name}-karpenter-controller" + iam_policy_use_name_prefix = false + iam_policy_name = "${var.name}-karpenter-controller" + node_iam_role_use_name_prefix = false + node_iam_role_name = "${var.name}-karpenter-node" + + create_pod_identity_association = false + + node_iam_role_additional_policies = { + AmazonSSMManagedInstanceCore = "arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + depends_on = [ + aws_eks_cluster.this, + aws_iam_openid_connect_provider.oidc_provider, + ] +} + +resource "aws_eks_pod_identity_association" "karpenter" { + count = var.enable_karpenter && var.karpenter_identity_type == "pod_identity" ? 1 : 0 + + cluster_name = aws_eks_cluster.this.name + namespace = var.karpenter_namespace + service_account = var.karpenter_service_account + role_arn = module.karpenter[0].iam_role_arn +} + +resource "aws_ec2_tag" "karpenter_subnet_discovery" { + for_each = var.enable_karpenter ? toset(var.karpenter_discovery_subnet_ids) : toset([]) + + resource_id = each.value + key = "karpenter.sh/discovery" + value = aws_eks_cluster.this.name +} diff --git a/main.tf b/main.tf index 34909da..abdd30d 100644 --- a/main.tf +++ b/main.tf @@ -802,7 +802,14 @@ resource "aws_eks_node_group" "this" { } labels = try(each.value.labels, {}) - tags = merge(var.tags, try(each.value.tags, {})) + tags = merge( + var.tags, + try(each.value.tags, {}), + var.enable_cluster_autoscaler_iam ? { + "k8s.io/cluster-autoscaler/enabled" = "true" + "k8s.io/cluster-autoscaler/${aws_eks_cluster.this.name}" = "owned" + } : {} + ) timeouts { create = "60m" @@ -859,7 +866,10 @@ resource "aws_eks_addon" "before_compute" { depends_on = [ time_sleep.this, aws_iam_role.addon, - aws_eks_pod_identity_association.addon + aws_eks_pod_identity_association.addon, + # Pure Fargate: vpc-cni / other before_compute addons must not run until Fargate + # profiles exist, or scheduling fails (e.g. InsufficientNumberOfReplicas). + aws_eks_fargate_profile.this, ] } @@ -884,6 +894,9 @@ resource "aws_eks_addon" "this" { time_sleep.this, aws_eks_node_group.this, aws_iam_role.addon, - aws_eks_pod_identity_association.addon + aws_eks_pod_identity_association.addon, + # Fargate-only clusters have no node groups; without this, CoreDNS (computeType + # fargate) can be created before profiles exist and stays DEGRADED — no capacity. + aws_eks_fargate_profile.this, ] } diff --git a/modules/argocd-codeconnections/README.md b/modules/argocd-codeconnections/README.md index ddea549..ef70f2a 100644 --- a/modules/argocd-codeconnections/README.md +++ b/modules/argocd-codeconnections/README.md @@ -10,7 +10,7 @@ Call this submodule after creating the EKS cluster with the Argo CD capability. module "argocd_connections" { source = "../../modules/argocd-codeconnections" - argocd_capability_role_arn = module.eks.cluster_capability_role_arns["argocd"] + argocd_capability_role_name = module.eks.cluster_capability_role_names["argocd"] connections = [ { name = "github", provider_type = "GitHub" } @@ -35,11 +35,13 @@ spec: Replace `CONNECTION_ID` with `module.argocd_connections.connection_ids["github"]`, and `owner`/`repo` with your Git org and repository name. +This module only grants `UseConnection`/`GetConnection` on connections it creates. Point Argo CD Applications at those connection IDs; if you need another connection, attach IAM elsewhere (e.g. root module `capabilities.argocd.code_connection_arns`) or add a second entry under `connections` here. + ## Inputs | Name | Description | | --- | --- | -| argocd_capability_role_arn | IAM role ARN of the Argo CD capability (e.g. from `cluster_capability_role_arns["argocd"]`). | +| argocd_capability_role_name | IAM role name of the Argo CD capability (e.g. `cluster_capability_role_names["argocd"]`). | | connections | List of `{ name, provider_type }`. `provider_type`: `GitHub`, `Bitbucket`, or `GitHubEnterpriseServer`. Optional `host_arn` for GitHub Enterprise / GitLab. | | tags | Tags for created resources. | diff --git a/modules/argocd-codeconnections/main.tf b/modules/argocd-codeconnections/main.tf index dee3aeb..9355b9b 100644 --- a/modules/argocd-codeconnections/main.tf +++ b/modules/argocd-codeconnections/main.tf @@ -4,12 +4,14 @@ ################################################################################ locals { - # IAM role_policy requires role name; derive from ARN (arn:aws:iam::ACCOUNT:role/NAME) - argocd_role_name = try(regex("role/(.+)$", var.argocd_capability_role_arn)[0], null) + connections_map = { + for c in var.connections : + coalesce(try(c.key, null), c.name) => c + } } resource "aws_codestarconnections_connection" "this" { - for_each = { for i, c in var.connections : c.name => c } + for_each = local.connections_map name = each.value.name provider_type = each.value.provider_type @@ -33,7 +35,14 @@ data "aws_iam_policy_document" "codeconnections_use" { resource "aws_iam_role_policy" "codeconnections" { count = var.attach_codeconnections_policy ? 1 : 0 - name = "argocd-codeconnections-use" - role = local.argocd_role_name + name = var.iam_role_policy_name + role = var.argocd_capability_role_name policy = data.aws_iam_policy_document.codeconnections_use.json + + lifecycle { + precondition { + condition = length(var.connections) > 0 + error_message = "Provide at least one entry in `connections` when attach_codeconnections_policy is true." + } + } } diff --git a/modules/argocd-codeconnections/outputs.tf b/modules/argocd-codeconnections/outputs.tf index 3d094d2..d3bec9f 100644 --- a/modules/argocd-codeconnections/outputs.tf +++ b/modules/argocd-codeconnections/outputs.tf @@ -1,3 +1,13 @@ +output "codeconnections_iam_role_name" { + description = "IAM role name that receives the CodeConnections inline policy (Argo CD capability role)." + value = var.argocd_capability_role_name +} + +output "codeconnections_iam_policy_name" { + description = "Name of the inline IAM policy on the Argo CD capability role (UseConnection + GetConnection)." + value = var.iam_role_policy_name +} + output "connection_arns" { description = "ARNs of the created CodeStar Connections. Use in Argo CD Application repoURL or pass to root module capabilities.argocd.code_connection_arns if not using this submodule's IAM attachment." value = [for c in aws_codestarconnections_connection.this : c.arn] @@ -9,7 +19,7 @@ locals { } output "connection_ids" { - description = "Map of connection name to connection ID (UUID only, for building repo URL). Do not use the full ARN in the URL path." + description = "Map of connection key (connections[].key if set, else name) to connection ID (UUID only, for building repo URL). Do not use the full ARN in the URL path." value = local.connection_id_uuid } @@ -19,7 +29,7 @@ output "repository_url_template" { } output "repository_url_templates" { - description = "Map of connection name to repo URL template using that connection's UUID. Use format replace CONNECTION_ID with the value for the connection you want." + description = "Map of connection key (connections[].key if set, else name) to repo URL template using that connection's UUID." value = { for k, c in aws_codestarconnections_connection.this : k => "https://codeconnections.${data.aws_region.current.id}.amazonaws.com/git-http/${data.aws_caller_identity.current.account_id}/${data.aws_region.current.id}/${local.connection_id_uuid[k]}/OWNER/REPO.git" diff --git a/modules/argocd-codeconnections/variables.tf b/modules/argocd-codeconnections/variables.tf index 781f72c..821abd6 100644 --- a/modules/argocd-codeconnections/variables.tf +++ b/modules/argocd-codeconnections/variables.tf @@ -1,5 +1,5 @@ -variable "argocd_capability_role_arn" { - description = "IAM role ARN of the Argo CD EKS Capability (e.g. module.eks.cluster_capability_role_arns[\"argocd\"]). UseConnection and GetConnection policies will be attached to this role." +variable "argocd_capability_role_name" { + description = "IAM role name of the Argo CD EKS capability (e.g. module.eks.cluster_capability_role_names[\"argocd\"])." type = string } @@ -10,14 +10,21 @@ variable "attach_codeconnections_policy" { } variable "connections" { - description = "List of CodeStar Connections to create. Each connection is created PENDING; complete authentication in the AWS Console (e.g. GitHub OAuth) before use." + description = "List of CodeStar Connections to create. Each connection is created PENDING; complete authentication in the AWS Console (e.g. GitHub OAuth) before use. Optional `key` sets Terraform/for_each and output map keys; `name` is the AWS connection name only." type = list(object({ - name = string + key = optional(string) # Map key for outputs; default is name + name = string # AWS CodeStar connection name (unrelated to provider_type) provider_type = string # GitHub, Bitbucket, or GitHubEnterpriseServer host_arn = optional(string) # For GitHub Enterprise Server or GitLab Self-Managed })) } +variable "iam_role_policy_name" { + description = "Name of the inline IAM policy on the Argo CD capability role granting codeconnections:UseConnection and GetConnection." + type = string + default = "argocd-codeconnections-use" +} + variable "tags" { description = "Tags to apply to created resources" type = map(string) diff --git a/outputs.tf b/outputs.tf index f909500..b6b7d72 100644 --- a/outputs.tf +++ b/outputs.tf @@ -135,6 +135,26 @@ output "ebs_csi_driver_role_arn" { value = try(aws_iam_role.ebs_csi_driver[0].arn, null) } +output "cluster_autoscaler_role_arn" { + description = "IAM role ARN for Cluster Autoscaler (when enable_cluster_autoscaler_iam). For IRSA, annotate the cluster-autoscaler ServiceAccount with this ARN." + value = try(aws_iam_role.cluster_autoscaler[0].arn, null) +} + +output "karpenter_controller_role_arn" { + description = "IAM role ARN for the Karpenter controller (when enable_karpenter)" + value = try(module.karpenter[0].iam_role_arn, null) +} + +output "karpenter_node_role_name" { + description = "IAM role name for nodes launched by Karpenter (EC2NodeClass spec.role)" + value = try(module.karpenter[0].node_iam_role_name, null) +} + +output "karpenter_interruption_queue_name" { + description = "SQS queue name for Karpenter interruption handling (Helm settings.interruptionQueue)" + value = try(module.karpenter[0].queue_name, null) +} + output "secrets_manager_role_arn" { description = "IAM role ARN for Secrets Manager (when enabled)" value = try(aws_iam_role.secrets_manager[0].arn, null) @@ -161,12 +181,14 @@ output "dynamodb_role_arns" { } output "cluster_pod_identity_associations" { - description = "Map of EKS Pod Identity associations (addon, ALB controller, External DNS, EBS CSI driver, Secrets Manager, S3) when using Pod Identity" + description = "Map of EKS Pod Identity associations (addon, ALB controller, External DNS, EBS CSI driver, Cluster Autoscaler, Karpenter, Secrets Manager, S3) when using Pod Identity" value = merge( aws_eks_pod_identity_association.addon, length(aws_eks_pod_identity_association.aws_lb_controller) > 0 ? { "aws_lb_controller" = aws_eks_pod_identity_association.aws_lb_controller[0] } : {}, length(aws_eks_pod_identity_association.external_dns) > 0 ? { "external_dns" = aws_eks_pod_identity_association.external_dns[0] } : {}, length(aws_eks_pod_identity_association.ebs_csi_driver) > 0 ? { "ebs_csi_driver" = aws_eks_pod_identity_association.ebs_csi_driver[0] } : {}, + length(aws_eks_pod_identity_association.cluster_autoscaler) > 0 ? { "cluster_autoscaler" = aws_eks_pod_identity_association.cluster_autoscaler[0] } : {}, + length(aws_eks_pod_identity_association.karpenter) > 0 ? { "karpenter" = aws_eks_pod_identity_association.karpenter[0] } : {}, { for k, v in aws_eks_pod_identity_association.secrets_manager : "secrets_manager_${k}" => v }, { for k, v in aws_eks_pod_identity_association.s3 : "s3_${k}" => v }, { for k, v in aws_eks_pod_identity_association.sqs : "sqs_${k}" => v }, @@ -276,3 +298,8 @@ output "cluster_capability_role_arns" { description = "Map of IAM role ARNs for EKS capabilities created by the module (keyed by capability name). Use for ACK controller config or external reference." value = { for k, r in aws_iam_role.capability : k => r.arn } } + +output "cluster_capability_role_names" { + description = "Map of IAM role names for EKS capabilities created by the module (same keys as cluster_capability_role_arns). Use for aws_iam_role_policy.role and similar APIs that expect a role name." + value = { for k, r in aws_iam_role.capability : k => r.name } +} diff --git a/variables.tf b/variables.tf index 9561f35..9729bc7 100644 --- a/variables.tf +++ b/variables.tf @@ -206,6 +206,12 @@ variable "enable_aws_load_balancer_controller" { default = false } +variable "enable_cluster_autoscaler_iam" { + description = "Whether to create IAM role for Cluster Autoscaler (IRSA or Pod Identity per cluster_autoscaler_identity_type). For EC2 managed node groups only; not supported with enable_automode. When true, adds k8s.io/cluster-autoscaler/* tags to managed node groups for ASG autodiscovery." + type = bool + default = false +} + variable "enable_ebs_csi_driver" { description = "Whether to create IAM role for EBS CSI driver (IRSA or Pod Identity per ebs_csi_driver_identity_type)" type = bool @@ -224,6 +230,41 @@ variable "enable_secrets_manager" { default = false } +variable "enable_karpenter" { + description = "Whether to create Karpenter IAM (controller + node roles), interruption SQS queue, EventBridge rules, and EKS access entry for the node role (via terraform-aws-modules/eks/karpenter). Mutually exclusive with enable_automode." + type = bool + default = false +} + +variable "karpenter_identity_type" { + description = "Credential mode for the Karpenter controller. Only pod_identity is wired (EKS Pod Identity association); use eks-pod-identity-agent addon." + type = string + default = "pod_identity" + + validation { + condition = contains(["pod_identity"], var.karpenter_identity_type) + error_message = "karpenter_identity_type must be 'pod_identity'." + } +} + +variable "karpenter_namespace" { + description = "Kubernetes namespace for the Karpenter controller service account (must match GitOps Helm)." + type = string + default = "karpenter" +} + +variable "karpenter_service_account" { + description = "Kubernetes service account name for Karpenter (must match Helm chart)." + type = string + default = "karpenter" +} + +variable "karpenter_discovery_subnet_ids" { + description = "Private subnet IDs to tag with karpenter.sh/discovery = cluster name for Karpenter subnet discovery." + type = list(string) + default = [] +} + ################################################################################ # Pod Identity Configuration (alternative to IRSA per component) ################################################################################ @@ -297,6 +338,29 @@ variable "ebs_csi_driver_service_account" { default = "ebs-csi-controller-sa" } +variable "cluster_autoscaler_identity_type" { + description = "Identity type for Cluster Autoscaler. Use 'pod_identity' to create Pod Identity association; requires eks-pod-identity-agent addon." + type = string + default = "irsa" + + validation { + condition = contains(["irsa", "pod_identity"], var.cluster_autoscaler_identity_type) + error_message = "cluster_autoscaler_identity_type must be 'irsa' or 'pod_identity'." + } +} + +variable "cluster_autoscaler_namespace" { + description = "Kubernetes namespace for Cluster Autoscaler service account. Used for IRSA OIDC condition and when cluster_autoscaler_identity_type = 'pod_identity'." + type = string + default = "kube-system" +} + +variable "cluster_autoscaler_service_account" { + description = "Kubernetes service account name for Cluster Autoscaler. Used for IRSA OIDC condition and when cluster_autoscaler_identity_type = 'pod_identity'." + type = string + default = "cluster-autoscaler" +} + variable "secrets_manager_identity_type" { description = "Identity type for Secrets Manager. Use 'pod_identity' to create Pod Identity association; requires eks-pod-identity-agent addon." type = string