terraform { required_version = ">= 1.7" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } kubernetes = { source = "hashicorp/kubernetes" version = "~> 2.27" } } backend "s3" { bucket = "veylant-terraform-state" key = "production/eks/terraform.tfstate" region = "eu-west-3" encrypt = true dynamodb_table = "veylant-terraform-locks" } } provider "aws" { region = var.aws_region default_tags { tags = { Project = "veylant-ia" Environment = "production" ManagedBy = "terraform" } } } # ────────────────────────────────────────────── # VPC — 3 public + 3 private subnets across AZs # ────────────────────────────────────────────── module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.5" name = "veylant-production" cidr = var.vpc_cidr azs = ["${var.aws_region}a", "${var.aws_region}b", "${var.aws_region}c"] private_subnets = var.private_subnet_cidrs public_subnets = var.public_subnet_cidrs enable_nat_gateway = true single_nat_gateway = false # 1 NAT GW per AZ for HA enable_dns_hostnames = true enable_dns_support = true # Required tags for EKS auto-discovery of subnets. private_subnet_tags = { "kubernetes.io/role/internal-elb" = "1" "kubernetes.io/cluster/${var.cluster_name}" = "owned" } public_subnet_tags = { "kubernetes.io/role/elb" = "1" "kubernetes.io/cluster/${var.cluster_name}" = "owned" } } # ────────────────────────────────────────────── # EKS Cluster — Kubernetes 1.31, eu-west-3 # ────────────────────────────────────────────── module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 20.0" cluster_name = var.cluster_name cluster_version = "1.31" vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnet_ids cluster_endpoint_public_access = true # Access via kubectl from CI/CD # Enable IRSA — required for pod-level IAM roles (backup, Vault). enable_irsa = true cluster_addons = { aws-ebs-csi-driver = { most_recent = true service_account_role_arn = module.irsa_ebs_csi.iam_role_arn } coredns = { most_recent = true } kube-proxy = { most_recent = true } vpc-cni = { most_recent = true before_compute = true } } eks_managed_node_groups = { # One node group per AZ for topology-aware scheduling. veylant-az-a = { name = "veylant-az-a" subnet_ids = [module.vpc.private_subnets[0]] instance_types = [var.node_instance_type] min_size = 1 max_size = 5 desired_size = 2 ami_type = "AL2_x86_64" disk_size = 50 labels = { "topology.kubernetes.io/zone" = "${var.aws_region}a" workload = "veylant" } } veylant-az-b = { name = "veylant-az-b" subnet_ids = [module.vpc.private_subnets[1]] instance_types = [var.node_instance_type] min_size = 1 max_size = 5 desired_size = 2 ami_type = "AL2_x86_64" disk_size = 50 labels = { "topology.kubernetes.io/zone" = "${var.aws_region}b" workload = "veylant" } } veylant-az-c = { name = "veylant-az-c" subnet_ids = [module.vpc.private_subnets[2]] instance_types = [var.node_instance_type] min_size = 1 max_size = 5 desired_size = 2 ami_type = "AL2_x86_64" disk_size = 50 labels = { "topology.kubernetes.io/zone" = "${var.aws_region}c" workload = "veylant" } } } tags = { Environment = "production" Cluster = var.cluster_name } } # ────────────────────────────────────────────── # IRSA — IAM Roles for Service Accounts # ────────────────────────────────────────────── # EBS CSI Driver IRSA module "irsa_ebs_csi" { source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" version = "~> 5.39" role_name = "veylant-ebs-csi-driver" attach_ebs_csi_policy = true oidc_providers = { main = { provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"] } } } # Backup role IRSA (S3 write for pg_dump) module "irsa_backup" { source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks" version = "~> 5.39" role_name = "veylant-backup-role" role_policy_arns = { backup = aws_iam_policy.backup_s3.arn } oidc_providers = { main = { provider_arn = module.eks.oidc_provider_arn namespace_service_accounts = ["veylant:veylant-backup"] } } } resource "aws_iam_policy" "backup_s3" { name = "veylant-backup-s3" description = "Allow Veylant backup job to write to S3 backup bucket" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject" ] Resource = [ "arn:aws:s3:::veylant-backups-production", "arn:aws:s3:::veylant-backups-production/*" ] } ] }) } # ────────────────────────────────────────────── # S3 Backup Bucket with 7-day lifecycle # ────────────────────────────────────────────── resource "aws_s3_bucket" "backups" { bucket = "veylant-backups-production" } resource "aws_s3_bucket_versioning" "backups" { bucket = aws_s3_bucket.backups.id versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "backups" { bucket = aws_s3_bucket.backups.id rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } } resource "aws_s3_bucket_lifecycle_configuration" "backups" { bucket = aws_s3_bucket.backups.id rule { id = "expire-old-backups" status = "Enabled" filter { prefix = "postgres/" } # Delete backups older than 7 days. expiration { days = 7 } # Clean up incomplete multipart uploads. abort_incomplete_multipart_upload { days_after_initiation = 1 } } } resource "aws_s3_bucket_public_access_block" "backups" { bucket = aws_s3_bucket.backups.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true }