Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,21 @@
# MkDocs
/site/
/.cache/

# Terraform
*.tfstate
*.tfstate.*
.terraform/
crash.log
crash.*.log
*.tfplan
*.tfplan.*
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc

# Backend config (local-only, not committed)
terraform/backend.hcl
7 changes: 6 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
in {
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
pkgs = import nixpkgs {
inherit system;
config.allowUnfreePredicate = pkg:
builtins.elem (nixpkgs.lib.getName pkg) [ "terraform" ];
};
docsPython = pkgs.python3.withPackages (ps: [
ps.mkdocs
ps."mkdocs-material"
Expand All @@ -35,6 +39,7 @@

# Cloud tooling
awscli2
terraform

goreleaser
actionlint
Expand Down
74 changes: 71 additions & 3 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ This directory provisions a cost-optimized Amazon EKS sandbox cluster in region
- Disk size: `20 GiB`
- AMI type: `AL2023_x86_64_STANDARD`
- EKS managed add-ons: `coredns`, `kube-proxy`, `vpc-cni`
- Local Terraform state (no remote backend configured)
- Remote Terraform state in AWS S3 with lockfile-based locking (no DynamoDB)

## Prerequisites

- Terraform `>= 1.5`
- Terraform `>= 1.11`
- AWS CLI v2 installed
- AWS identity with permissions to create VPC, IAM, EKS, and EC2 resources in your target account

Expand Down Expand Up @@ -71,10 +71,78 @@ If you prefer not to use `TF_VAR_aws_profile`, pass `-var="aws_profile=<your-ter

> Security note: do not commit `~/.aws/config`, `~/.aws/credentials`, or any copied credential values to git.

## Remote state backend (S3 + lockfile)

Terraform state is stored in an S3 bucket in `eu-central-1`. Locking uses Terraform's native S3 lockfile (`use_lockfile = true` in the backend block), so no DynamoDB lock table is required. The backend configuration committed in this repo only includes shared settings (`region`, `encrypt`, and `use_lockfile`); the bucket name and state key are supplied at init time via `-backend-config`.

### Bootstrap the S3 bucket (one-time)

Terraform cannot create its own backend bucket before backend initialization, so create and secure the bucket once with AWS CLI:

```bash
REGION=eu-central-1
BUCKET=<globally-unique-bucket-name>

# Create bucket
aws s3api create-bucket \
--bucket "$BUCKET" \
--region "$REGION" \
--create-bucket-configuration LocationConstraint="$REGION"

# Enable versioning (for state recovery)
aws s3api put-bucket-versioning \
--bucket "$BUCKET" \
--versioning-configuration Status=Enabled

# Block all public access
aws s3api put-public-access-block \
--bucket "$BUCKET" \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

# Default encryption (SSE-S3)
aws s3api put-bucket-encryption \
--bucket "$BUCKET" \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}
}]
}'
```

### Required IAM permissions

The identity used for Terraform backend access needs S3 permissions for `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject` (lockfile lifecycle), and `s3:ListBucket` on the state bucket path.

### Authentication note

The S3 backend authenticates separately from the AWS provider configuration. If you use `TF_VAR_aws_profile` for provider auth, also set `AWS_PROFILE` or add `profile` to `backend.hcl` so `terraform init` can authenticate to S3.

### Migrate existing local state

If you already have a local `.tfstate` file, migrate it into S3 during init:

```bash
terraform init -migrate-state -backend-config=backend.hcl
```

## Usage

Create a local backend config file `backend.hcl` in this directory (it is
git-ignored):

```hcl
bucket = "<your-tfstate-bucket>"
key = "terraform-ncp3/sandbox-eks/terraform.tfstate"

# Optional: set if you use an AWS CLI profile for backend access
# profile = "terraform"
```

Then initialize and apply:

```bash
terraform init
terraform init -backend-config=backend.hcl
terraform plan
terraform apply
```
Expand Down
2 changes: 2 additions & 0 deletions terraform/eks.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ resource "aws_eks_cluster" "this" {
role_arn = aws_iam_role.eks_cluster.arn
version = var.cluster_version

bootstrap_self_managed_addons = false

vpc_config {
subnet_ids = aws_subnet.private[*].id
endpoint_public_access = true
Expand Down
9 changes: 8 additions & 1 deletion terraform/versions.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
terraform {
required_version = ">= 1.5"
required_version = ">= 1.11"

backend "s3" {
region = "eu-central-1"
encrypt = true
use_lockfile = true
# bucket and key supplied via -backend-config at init time.
}

required_providers {
aws = {
Expand Down
10 changes: 5 additions & 5 deletions terraform/vpc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ locals {

azs = slice(data.aws_availability_zones.available.names, 0, local.az_count)

# Derive subnet ranges from the configurable VPC CIDR.
subnet_cidrs = cidrsubnets(var.vpc_cidr, 4, 4, 4, 4)

public_subnet_cidrs = slice(local.subnet_cidrs, 0, local.az_count)
private_subnet_cidrs = slice(local.subnet_cidrs, local.az_count, local.az_count * 2)
# Derive /24 subnet ranges from the VPC CIDR.
# Network numbers are intentionally spaced so public (1,2) and private (10,20)
# blocks don't collide and are easy to identify at a glance.
public_subnet_cidrs = [for i in [1, 2] : cidrsubnet(var.vpc_cidr, 8, i)]
private_subnet_cidrs = [for i in [10, 20] : cidrsubnet(var.vpc_cidr, 8, i)]
Comment thread
ThomasK33 marked this conversation as resolved.
Outdated
}

resource "aws_vpc" "this" {
Expand Down