veylant/deploy/terraform/main.tf
2026-02-23 13:35:04 +01:00

270 lines
7.4 KiB
HCL

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
}