Problema

En entornos de producción en Google Cloud Platform, montar una arquitectura segura implica varios componentes interdependientes: una red VPC con subredes bien definidas, Cloud NAT para salida de tráfico sin IP pública, un clúster GKE privado que no exponga sus nodos a Internet y, finalmente, una configuración de Workload Identity que permita a los pods autenticarse contra los servicios de GCP sin gestionar claves estáticas. Cuando se intenta combinar todo esto manualmente, es fácil perder consistencia, olvidar permisos mínimos o crear reglas de firewall que rompan la conectividad interna. El patrón problemático es la fragmentación de la infraestructura: VPC, NAT, GKE y IAM se provisionan en recursos separados, sin una visión unificada, lo que genera errores de despliegue, sobrecarga de mantenimiento y riesgos de seguridad.

Causa

  1. Definiciones dispersas – Cada equipo suele crear su propia VPC, subredes y reglas de firewall. Al añadir Cloud NAT después, la asociación por región puede quedar desalineada.
  2. Roles IAM insuficientes o excesivos – Los service accounts de los node‑pools a menudo reciben permisos de proyecto completo, lo que rompe el principio de menor privilegio.
  3. Workload Identity mal enlazado – La vinculación entre la cuenta de servicio del pod y la cuenta de GCP requiere crear una federación IAM y habilitarla en el clúster; omitir cualquiera de los pasos genera fallos de autenticación.
  4. Configuración de nodos privados incompleta – Habilitar private_nodes sin crear rutas adecuadas o sin Cloud NAT deja a los nodos sin salida a los servicios de Google (por ejemplo, Container Registry).
  5. Desincronización de rangos secundarios – Los rangos de pods y servicios deben declararse tanto en la VPC como en el clúster GKE; una discrepancia impide que el controlador de red asigne IPs correctamente.

Solución

Utilizar módulos Terraform reutilizables que encapsulen cada capa (VPC, GKE, IAM) y que expongan variables mínimas pero coherentes. La arquitectura propuesta sigue estos principios:

  1. Módulo VPC

    • Crea la red, subredes y rangos secundarios en una sola llamada.
    • Opcionalmente habilita Cloud NAT por región y activa Private Google Access.
    • Incluye reglas de firewall básicas (IAP SSH, tráfico interno).
  2. Módulo GKE

    • Acepta los nombres de red y subred, así como los rangos secundarios (pods_range_name, services_range_name).
    • Habilita private_cluster_config y private_nodes.
    • Configura node‑pools con service accounts específicas y permite marcar pools como Spot.
    • Activa workload_identity_config a nivel de clúster.
  3. Módulo IAM

    • Genera service accounts por node‑pool y asigna roles de forma granular (roles/container.nodeServiceAccount, roles/logging.logWriter, etc.).
    • Realiza el binding de Workload Identity (iam.serviceAccountIamMember) entre la cuenta de Kubernetes y la de GCP.
  4. Orquestación con Terratest (opcional) – Ejecuta pruebas de integración que provisionen, validen y destruyan la infraestructura para garantizar que los módulos funcionan en conjunto.

Paso a paso resumido

  1. Definir variables comunes (project ID, región, nombres).
  2. Instanciar el módulo VPC y habilitar NAT.
  3. Instanciar el módulo IAM para crear service accounts y bindings.
  4. Instanciar el módulo GKE, pasando la VPC y los service accounts generados.
  5. Ejecutar terraform init && terraform apply.
  6. Validar que los nodos pueden alcanzar gcr.io y que los pods pueden usar la cuenta de servicio mediante gcloud auth print-access-token.

Cuándo aplicar esta solución

  • Entornos de producción o pre‑producción donde la seguridad perimetral y el control de identidad son requisitos obligatorios.
  • Equipos multi‑cloud que ya usan un registro de módulos Terraform y buscan consistencia entre proveedores.
  • Proyectos con múltiples clústeres GKE que comparten la misma VPC y necesitan evitar duplicación de reglas de firewall.
  • Escenarios donde se usan Spot VMs para reducir costos y se requiere que cada pool tenga su propio service account.

No aplicar si se necesita una VPC extremadamente personalizada (por ejemplo, con Cloud Router BGP avanzado) que los módulos genéricos no cubren; en esos casos extienda el módulo o cree uno propio.

Código

# variables.tf
variable "project_id" {}
variable "region" { default = "europe-west2" }

# main.tf
module "vpc" {
  source   = "github.com/Cloud-Architect-Emma/terraform-module-registry//modules/gcp/vpc?ref=main"
  name     = "prod-network"
  project_id = var.project_id
  subnets = [
    {
      name            = "prod-nodes"
      region          = var.region
      cidr            = "10.0.0.0/20"
      secondary_ranges = [
        { range_name = "pods",     cidr = "10.48.0.0/14" },
        { range_name = "services", cidr = "10.52.0.0/20" }
      ]
    }
  ]
  enable_cloud_nat = true
}

module "iam" {
  source     = "github.com/Cloud-Architect-Emma/terraform-module-registry//modules/gcp/iam?ref=main"
  project_id = var.project_id

  service_accounts = {
    gke_nodes_default = {
      display_name = "GKE default node pool SA"
      roles = [
        "roles/container.nodeServiceAccount",
        "roles/logging.logWriter",
        "roles/monitoring.metricWriter"
      ]
    }
    gke_nodes_spot = {
      display_name = "GKE spot node pool SA"
      roles = [
        "roles/container.nodeServiceAccount",
        "roles/logging.logWriter"
      ]
    }
  }

  workload_identity_bindings = {
    "gke-prod-default" = {
      kubernetes_service_account = "default"
      iam_service_account         = "gke_nodes_default"
    }
    "gke-prod-spot" = {
      kubernetes_service_account = "spot"
      iam_service_account         = "gke_nodes_spot"
    }
  }
}

module "gke" {
  source   = "github.com/Cloud-Architect-Emma/terraform-module-registry//modules/gcp/gke?ref=main"
  cluster_name = "prod-cluster"
  project_id   = var.project_id
  location     = var.region
  network      = module.vpc.network_name
  subnetwork   = "prod-nodes"
  pods_range_name      = "pods"
  services_range_name  = "services"
  enable_private_nodes = true

  node_pools = {
    default = {
      machine_type   = "e2-standard-2"
      min_node_count = 1
      max_node_count = 5
      service_account = module.iam.service_accounts["gke_nodes_default"].email
    }
    spot = {
      machine_type   = "e2-standard-2"
      min_node_count = 0
      max_node_count = 10
      spot           = true
      service_account = module.iam.service_accounts["gke_nodes_spot"].email
    }
  }
}

Verificación

  1. Comprobar la VPC

    gcloud compute networks describe prod-network --project=$PROJECT_ID
    gcloud compute routes list --filter="network:prod-network"
    

    Verifique que exista una ruta default-internet-gateway con nextHopNat apuntando al Cloud NAT creado.

  2. Validar el clúster privado

    gcloud container clusters describe prod-cluster --region=$REGION
    

    Asegúrese de que privateClusterConfig.enablePrivateNodes sea true y que masterAuthorizedNetworksConfig esté vacío (acceso solo vía VPC).

  3. Probar Workload Identity

    kubectl run test-pod --image=gcr.io/google-containers/busybox --restart=Never --command -- sleep 3600
    kubectl exec test-pod -- curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
    

    La salida debe coincidir con la dirección de correo del service account asignado al pod.

  4. Revisar permisos IAM

    gcloud iam service-accounts get-iam-policy $(gcloud iam service-accounts list --filter="displayName:gke_nodes_default" --format="value(email)") --project=$PROJECT_ID
    

    Confirme que los bindings incluyen roles/container.nodeServiceAccount y los roles de logging/monitoring.

  5. Destruir y volver a crear (si usa Terratest) para garantizar idempotencia.

Notas adicionales

  • Orden de creación: el módulo IAM debe ejecutarse antes que GKE para que los service accounts existan cuando el clúster los referencia. Terraform gestiona la dependencia automáticamente si se pasa la salida del módulo IAM como input al módulo GKE.
  • Cloud NAT y Private Google Access: sin Private Google Access, los nodos privados no pueden resolver *.googleapis.com. El módulo VPC lo habilita por defecto, pero revíselo si personaliza la VPC.
  • Límites de rangos secundarios: GKE impone un máximo de 8 rangos secundarios por subred. Planifique los CIDR de pods y services con suficiente espacio para futuros pools.
  • Spot pools y preemptión: los pods en node‑pools Spot deben estar configurados con PodDisruptionBudget o usar nodeSelector que permita re‑schedule en pools on‑demand.
  • Terratest: si decide incluir pruebas, mantenga los recursos de prueba en un proyecto de sandbox para evitar cargos inesperados. Use terraform destroy al final de la suite para garantizar limpieza.