Salta al contenuto principale

Gestire Infrastruttura AWS Multi-Account con Terraform Workspaces

·5 minuti
Indice dei contenuti
Gestire Terraform su più account AWS e ambienti senza duplicare il codice. Questo articolo copre il pattern workspace, il remote state, le assunzioni di ruolo cross-account e la struttura dei moduli che scala da 3 account a 30.

Quando gestisci infrastruttura su decine di account AWS, hai bisogno di pattern che scalino. In questo articolo condivido l’approccio che uso per gestire infrastruttura AWS multi-account e multi-ambiente usando Terraform workspaces, codice modulare e una strategia di tagging coerente.

Il Problema
#

Immagina questo scenario: hai più scope organizzativi (team, business unit, progetti), ognuno con i propri account AWS per non-produzione e produzione. Sopra a questo, il tuo account non-produzione ospita più ambienti (dev, integrazione, certificazione). Moltiplica questo per diversi paesi o regioni, e hai molta infrastruttura da gestire.

L’approccio naive di copiare e incollare codice Terraform per ogni ambiente diventa rapidamente insostenibile. Hai bisogno di una strategia che ti permetta di definire l’infrastruttura una volta e distribuirla in modo coerente in tutti gli ambienti.

Separazione degli Ambienti Basata su Workspace
#

I workspace Terraform sono la base di questo approccio. Ogni workspace mappa a un tier di ambiente:

backend.tf
terraform {
  required_version = ">= 1.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  backend "s3" {}
}

Usiamo la configurazione backend parziale con file .hcl per ambiente:

backend-qual.hcl
bucket         = "my-scope-terraform-state-qual"
key            = "my-service/terraform.tfstate"
region         = "eu-central-1"
dynamodb_table = "terraform-lock"
encrypt        = true

Inizializza con il backend appropriato:

terminal
terraform init -backend-config=backend-qual.hcl
terraform workspace select qual || terraform workspace new qual

Variabili Specifiche per Ambiente con Lookup Map
#

Invece di file .tfvars separati, uso lookup map con chiave terraform.workspace. Questo mantiene tutto in un posto e rende immediatamente visibili le differenze tra ambienti:

main.tf
locals {
  environment = terraform.workspace

  # Configurazione ECS per ambiente
  ecs_cpu = lookup({
    qual = 512
    prod = 1024
  }, terraform.workspace, 512)

  ecs_memory = lookup({
    qual = 1024
    prod = 2048
  }, terraform.workspace, 1024)

  # Scaling Aurora Serverless v2
  aurora_min_acu = lookup({
    qual = 0.5
    prod = 1
  }, terraform.workspace, 0.5)

  aurora_max_acu = lookup({
    qual = 2
    prod = 4
  }, terraform.workspace, 2)

  # Strategia capacity provider Fargate
  fargate_spot_weight = lookup({
    qual = 4
    prod = 1
  }, terraform.workspace, 4)

  # Tag comuni applicati a tutte le risorse
  common_tags = {
    Environment = local.environment
    Project     = var.project_name
    ManagedBy   = "terraform"
    Team        = "platform"
  }
}

Questo pattern rende facile vedere a colpo d’occhio come gli ambienti differiscono. Non-produzione ottiene istanze più piccole e più capacità Spot; produzione ottiene istanze più grandi e più stabilità On-Demand.

Infrastruttura Modulare
#

Ogni concernere infrastrutturale vive nel proprio modulo:

terraform/
  modules/
    alb/
    aurora/
    cloudwatch/
    ecr/
    ecs/
    route53/
    security-groups/
    waf/
  main.tf
  locals.tf
  terraform.tf
  backend-qual.hcl
  backend-prod.hcl

Il modulo root li compone:

main.tf
module "ecs" {
  source              = "./modules/ecs"
  service_name        = var.service_name
  cpu                 = local.ecs_cpu
  memory              = local.ecs_memory
  image               = "${module.ecr.repository_url}:latest"
  target_group_arn    = module.alb.target_group_arn
  spot_weight         = local.fargate_spot_weight
  ondemand_weight     = local.fargate_ondemand_weight
  tags                = local.common_tags
}

module "aurora" {
  source            = "./modules/aurora"
  cluster_name      = "${var.service_name}-${local.environment}"
  min_acu           = local.aurora_min_acu
  max_acu           = local.aurora_max_acu
  tags              = local.common_tags
}

Il Pattern dei Security Group a Tre Tier
#

Ogni servizio segue lo stesso modello di sicurezza a strati:

iam.tf
# ALB accetta HTTPS dal VPC
# ECS accetta traffico solo dall'ALB
# Aurora accetta connessioni solo dall'ECS
# Nessun CIDR hardcoded tra i tier

Il principio chiave: ogni layer accetta solo traffico dal layer direttamente superiore. Nessun CIDR hardcoded tra i tier.

IAM con Permissions Boundary
#

In un setup enterprise multi-account, tipicamente hai un layer di governance che limita cosa ogni scope può fare. I permissions boundary sono il meccanismo:

iam.tf
resource "aws_iam_role" "ecs_task" {
  name = "${var.service_name}-ecs-task-${local.environment}"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "ecs-tasks.amazonaws.com" }
    }]
  })

  permissions_boundary = data.aws_iam_policy.scope_boundary.arn
  tags                 = local.common_tags
}

Ogni ruolo IAM ottiene il permissions boundary dello scope. Questo garantisce che anche se una policy di ruolo è troppo permissiva, non possa superare ciò che lo scope organizzativo permette.

Integrazione CI/CD
#

La pipeline GitLab CI segue un flusso di promozione. Un commit su master attiva un plan; il merge su release attiva l’apply:

terraform/main.tf
plan:qual:
  stage: plan
  script:
    - terraform init -backend-config=backend-qual.hcl
    - terraform workspace select qual
    - terraform plan -out=plan.tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "master"

apply:qual:
  stage: apply
  script:
    - terraform apply plan.tfplan
  rules:
    - if: $CI_COMMIT_BRANCH == "release"
      when: manual

Lezioni Imparate
#

  1. Workspace invece di directory. Avere directory separate per ambiente porta a drift. I workspace con lookup map mantengono un’unica fonte di verità.

  2. Moduli con opinioni. Ogni modulo dovrebbe incorporare le best practice (circuit breaker, Container Insights, policy di retention dei log) invece di esporre ogni parametro. Se il 90% dei servizi ha bisogno della stessa config, rendila il default.

  3. Tagga tutto. Il tagging coerente su tutte le risorse è ciò che rende possibile l’allocazione dei costi, il reporting di conformità e la pulizia automatizzata su larga scala.

  4. I permissions boundary non sono negoziabili. In un’impresa multi-team, hai bisogno di guardrail. I permissions boundary permettono ai team di operare autonomamente entro limiti sicuri.

  5. Plan prima dell’apply, sempre. Anche in non-produzione. Un Terraform plan che mostra 47 risorse da eliminare è molto più economico da revisionare che da ripristinare.

Articoli correlati