Introducción

Google Cloud Platform (GCP) es el tercer gran proveedor de nube, conocido por su fortaleza en inteligencia artificial, análisis de datos y Kubernetes (Google inventó Kubernetes). Tiene una estructura diferente a AWS y Azure que es importante entender antes de empezar a aprovisionar recursos.

En este módulo crearás infraestructura real en GCP con Terraform y aprenderás las diferencias clave con los otros dos grandes proveedores. Al final encontrarás una tabla comparativa completa de los tres.


Configuración del Provider Google Cloud

El provider oficial de GCP para Terraform se llama google y es mantenido por Google y HashiCorp.

# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    google = {
      # Fuente oficial del provider de GCP
      source  = "hashicorp/google"
      # El provider google-beta tiene recursos en preview
      version = "~> 5.0"
    }
  }
}

# Configuración del provider principal de GCP
provider "google" {
  # ID del proyecto de GCP (no el nombre, sino el ID único)
  project = var.project_id

  # Región por defecto donde se crearán los recursos regionales
  # Equivalente a "region" en AWS y Azure
  region = var.region

  # Zona por defecto para recursos zonales (como VMs de Compute Engine)
  # Una región puede tener múltiples zonas (ej: us-central1-a, us-central1-b)
  zone = var.zone
}

# Provider beta: para recursos en preview o con características avanzadas
provider "google-beta" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}
# variables.tf
variable "project_id" {
  description = "ID del proyecto de GCP (ej: mi-proyecto-12345)"
  type        = string
}

variable "region" {
  description = "Región de GCP para desplegar recursos regionales"
  type        = string
  default     = "us-central1"
}

variable "zone" {
  description = "Zona de GCP para desplegar recursos zonales"
  type        = string
  default     = "us-central1-a"
}

variable "environment" {
  description = "Entorno de despliegue (dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "project_name" {
  description = "Nombre corto del proyecto para nombrar recursos"
  type        = string
  default     = "miapp"
}

Autenticación: Service Account y Application Default Credentials

GCP usa dos mecanismos principales de autenticación:

Método 1: Application Default Credentials (ADC) - Para desarrollo local

# Instalar gcloud CLI
# En Ubuntu/Debian:
curl https://sdk.cloud.google.com | bash
exec -l $SHELL

# Autenticarse con tu cuenta de Google
gcloud auth login

# Configurar Application Default Credentials (ADC)
# Esto crea un archivo de credenciales en ~/.config/gcloud/
gcloud auth application-default login

# Establecer el proyecto por defecto
gcloud config set project mi-proyecto-id

# Verificar la configuración
gcloud config list
gcloud auth list

Con ADC configurado, Terraform lo usa automáticamente:

provider "google" {
  # Sin credenciales explícitas: usa ADC automáticamente
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

Método 2: Service Account Key File (Para CI/CD)

# Crear un Service Account
gcloud iam service-accounts create terraform-sa \
  --display-name="Terraform Service Account" \
  --project=mi-proyecto-id

# Asignar el rol de Editor al Service Account
gcloud projects add-iam-policy-binding mi-proyecto-id \
  --member="serviceAccount:[email protected]" \
  --role="roles/editor"

# Crear y descargar la clave JSON del Service Account
gcloud iam service-accounts keys create terraform-key.json \
  --iam-account="[email protected]"

Seguridad: El archivo terraform-key.json contiene credenciales sensibles. Nunca lo subas a un repositorio git. Añádelo a .gitignore.

Configurar en Terraform:

provider "google" {
  # Ruta al archivo JSON de la clave del Service Account
  credentials = file("terraform-key.json")

  # O usando una variable de entorno (más seguro):
  # export GOOGLE_APPLICATION_CREDENTIALS="/ruta/a/terraform-key.json"

  project = var.project_id
  region  = var.region
  zone    = var.zone
}

O mediante variable de entorno:

# La forma más segura: variable de entorno
export GOOGLE_APPLICATION_CREDENTIALS="/ruta/a/terraform-key.json"
export GOOGLE_PROJECT="mi-proyecto-id"
export GOOGLE_REGION="us-central1"
export GOOGLE_ZONE="us-central1-a"

Método 3: Workload Identity (Para GKE)

Cuando Terraform se ejecuta desde GKE (Kubernetes en GCP), puedes usar Workload Identity para evitar manejar archivos de credenciales:

provider "google" {
  project = var.project_id
  region  = var.region
  # Workload Identity se configura a nivel de cluster/namespace, no en el provider
}

Conceptos Clave de GCP: Projects, Regions y Zones

GCP tiene una jerarquía diferente a AWS y Azure:

Jerarquía de GCP

Organization (empresa)
└── Folders (departamentos/equipos)
    └── Projects (unidades de trabajo y facturación)
        └── Resources (VMs, buckets, bases de datos, etc.)

Organization: El nodo raíz que representa tu empresa en GCP. Requiere G Suite o Cloud Identity.

Folders: Agrupaciones lógicas dentro de la organización (ej: por equipo, por departamento, por entorno).

Projects: La unidad fundamental de GCP. Cada recurso pertenece a un proyecto. El proyecto es también la unidad de facturación y gestión de APIs.

Diferencia clave: En GCP, cada proyecto tiene un ID único global (ej: mi-empresa-produccion-2024) además de un número de proyecto. Terraform usa el ID de proyecto, no el nombre.

Regions y Zones:

  • Region: Área geográfica (ej: us-central1, europe-west1)
  • Zone: Datacenter específico dentro de una región (ej: us-central1-a, us-central1-b)
  • Los recursos regionales (VPCs, Cloud Storage) no tienen zona
  • Los recursos zonales (VMs, discos persistentes) sí requieren zona

Tabla de regiones principales de GCP

Región Código Ubicación Zonas
Iowa, EE.UU. us-central1 Council Bluffs, Iowa a, b, c, f
Virginia, EE.UU. us-east4 Ashburn, Virginia a, b, c
Oregon, EE.UU. us-west1 The Dalles, Oregon a, b, c
Países Bajos europe-west4 Eemshaven a, b, c
Bélgica europe-west1 St. Ghislain b, c, d
Tokio, Japón asia-northeast1 Tokio a, b, c
São Paulo, Brasil southamerica-east1 São Paulo a, b, c
Sydney, Australia australia-southeast1 Sydney a, b, c

Habilitar APIs en GCP

A diferencia de AWS y Azure, en GCP debes habilitar explícitamente las APIs de los servicios que quieres usar. Terraform puede hacer esto automáticamente:

# apis.tf

# Habilitar APIs necesarias para el proyecto
resource "google_project_service" "compute" {
  project = var.project_id
  service = "compute.googleapis.com"  # API de Compute Engine (VMs)

  # No deshabilitar la API si el recurso se destruye
  # (puede afectar otros recursos del proyecto)
  disable_on_destroy = false
}

resource "google_project_service" "container" {
  project = var.project_id
  service = "container.googleapis.com"  # API de GKE (Kubernetes)

  disable_on_destroy = false
}

resource "google_project_service" "sqladmin" {
  project = var.project_id
  service = "sqladmin.googleapis.com"  # API de Cloud SQL

  disable_on_destroy = false
}

resource "google_project_service" "storage" {
  project = var.project_id
  service = "storage.googleapis.com"  # API de Cloud Storage

  disable_on_destroy = false
}

resource "google_project_service" "iam" {
  project = var.project_id
  service = "iam.googleapis.com"  # API de IAM

  disable_on_destroy = false
}

Tabla de Recursos GCP más Usados con sus Tipos en Terraform

Servicio GCP Recurso Tipo en Terraform
Compute Engine Instancia de VM google_compute_instance
Compute Engine Imagen de disco google_compute_image
VPC Red de VPC google_compute_network
VPC Subred google_compute_subnetwork
VPC Regla de firewall google_compute_firewall
VPC IP estática externa google_compute_address
Cloud Storage Bucket google_storage_bucket
Cloud SQL Instancia de BD google_sql_database_instance
Cloud SQL Base de datos google_sql_database
GKE Cluster de Kubernetes google_container_cluster
GKE Node Pool google_container_node_pool
IAM Service Account google_service_account
IAM IAM Binding google_project_iam_binding
IAM IAM Member google_project_iam_member
Cloud Functions Función serverless google_cloudfunctions_function
Cloud Run Contenedor serverless google_cloud_run_service
Pub/Sub Topic google_pubsub_topic
BigQuery Dataset google_bigquery_dataset
Cloud DNS Zona DNS google_dns_managed_zone

Recursos GCP más Comunes: Código Completo

VPC Networks y Subnets

# network.tf

# En GCP, la VPC es global (no está limitada a una región como en AWS)
# Las subredes sí son regionales
resource "google_compute_network" "principal" {
  name    = "${var.project_name}-${var.environment}-vpc"
  project = var.project_id

  # auto_create_subnetworks = true crearía una subred en cada región automáticamente
  # Lo ponemos en false para gestionar las subredes manualmente
  auto_create_subnetworks = false

  # Modo de enrutamiento:
  # REGIONAL: el tráfico interno solo se enruta dentro de la región
  # GLOBAL: el tráfico puede enrutarse entre regiones usando la red de Google
  routing_mode = "REGIONAL"

  # depends_on asegura que la API esté habilitada antes de crear la red
  depends_on = [google_project_service.compute]
}

# Subred dentro de la VPC (regional, no global)
resource "google_compute_subnetwork" "publica" {
  name    = "${var.project_name}-${var.environment}-subnet-publica"
  project = var.project_id

  # La subred pertenece a la red VPC que creamos
  network = google_compute_network.principal.id

  # Rango de IPs de la subred
  ip_cidr_range = "10.0.1.0/24"

  # Región donde vive la subred
  region = var.region

  # Habilitar acceso privado a APIs de Google (Cloud Storage, BigQuery, etc.)
  # sin necesidad de salir a internet
  private_ip_google_access = true

  # Rangos de IPs secundarios (necesarios para GKE)
  secondary_ip_range {
    range_name    = "pods"     # Rango para los Pods de Kubernetes
    ip_cidr_range = "10.1.0.0/16"
  }

  secondary_ip_range {
    range_name    = "services" # Rango para los Services de Kubernetes
    ip_cidr_range = "10.2.0.0/20"
  }
}

# Subred privada para recursos internos
resource "google_compute_subnetwork" "privada" {
  name                     = "${var.project_name}-${var.environment}-subnet-privada"
  project                  = var.project_id
  network                  = google_compute_network.principal.id
  ip_cidr_range            = "10.0.2.0/24"
  region                   = var.region
  private_ip_google_access = true
}

Reglas de Firewall

# firewall.tf

# En GCP, las reglas de firewall se aplican a la VPC completa
# y se filtran por tags de red (network tags) en lugar de Security Groups

# Regla para permitir SSH (puerto 22)
resource "google_compute_firewall" "allow_ssh" {
  name    = "${var.project_name}-${var.environment}-allow-ssh"
  project = var.project_id

  # La regla se aplica a toda la VPC
  network = google_compute_network.principal.id

  # Dirección: INGRESS (entrada) o EGRESS (salida)
  direction = "INGRESS"

  # Prioridad: 0-65535, menor número = mayor prioridad
  priority = 1000

  allow {
    protocol = "tcp"
    ports    = ["22"]  # Puerto SSH
  }

  # La regla se aplica a instancias que tengan este tag de red
  target_tags = ["ssh-allowed"]

  # Desde dónde se permite (rangos CIDR fuente)
  source_ranges = ["0.0.0.0/0"]  # En producción, limitar a tu IP
}

# Regla para permitir HTTP y HTTPS
resource "google_compute_firewall" "allow_web" {
  name    = "${var.project_name}-${var.environment}-allow-web"
  project = var.project_id
  network = google_compute_network.principal.id

  allow {
    protocol = "tcp"
    ports    = ["80", "443"]  # HTTP y HTTPS
  }

  target_tags   = ["web-server"]
  source_ranges = ["0.0.0.0/0"]
}

# Regla para tráfico interno dentro de la VPC
resource "google_compute_firewall" "allow_internal" {
  name    = "${var.project_name}-${var.environment}-allow-internal"
  project = var.project_id
  network = google_compute_network.principal.id

  allow {
    protocol = "tcp"
    ports    = ["0-65535"]  # Todos los puertos TCP
  }

  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "icmp"  # Ping y otros mensajes ICMP
  }

  # Solo tráfico desde dentro de la VPC
  source_ranges = ["10.0.0.0/16"]
}

Compute Engine Instances

# compute.tf

# IP estática para la instancia (opcional, pero útil para producción)
resource "google_compute_address" "web" {
  name    = "${var.project_name}-${var.environment}-web-ip"
  project = var.project_id
  region  = var.region

  # EXTERNAL: IP accesible desde internet
  # INTERNAL: IP privada dentro de la VPC
  address_type = "EXTERNAL"
}

# Instancia de Compute Engine (equivalente a EC2 en AWS)
resource "google_compute_instance" "web" {
  name    = "${var.project_name}-${var.environment}-web"
  project = var.project_id

  # Tipo de máquina: determina CPU y RAM
  # e2-micro: 2 vCPU (shared), 1 GB RAM (elegible para free tier)
  # n2-standard-2: 2 vCPU dedicadas, 8 GB RAM
  machine_type = "e2-micro"

  # Zona donde se crea la instancia
  zone = var.zone

  # Network tags: se usan para aplicar reglas de firewall
  # La instancia tendrá los permisos definidos en las reglas de firewall
  # con target_tags que incluyan estos valores
  tags = ["web-server", "ssh-allowed"]

  # Configuración del disco de arranque
  boot_disk {
    initialize_params {
      # Imagen del SO: "debian-cloud/debian-11", "ubuntu-os-cloud/ubuntu-2204-lts"
      image = "debian-cloud/debian-11"

      # Tamaño del disco en GB
      size = 20

      # Tipo de disco: pd-standard (HDD), pd-ssd (SSD), pd-balanced
      type = "pd-standard"
    }

    # Eliminar el disco cuando se elimine la instancia
    auto_delete = true
  }

  # Interfaz de red de la instancia
  network_interface {
    # Conectar la instancia a nuestra subred
    subnetwork = google_compute_subnetwork.publica.id

    # Configuración de IP externa para acceso desde internet
    access_config {
      # Asignar la IP estática que creamos
      nat_ip = google_compute_address.web.address
    }
  }

  # Script de inicio (equivalente a User Data en AWS)
  # Se ejecuta como root la primera vez que arranca la instancia
  metadata_startup_script = <<-EOF
    #!/bin/bash
    apt-get update -y
    apt-get install -y nginx
    systemctl start nginx
    systemctl enable nginx
    echo "<h1>GCP Compute Engine - Terraform</h1>" > /var/www/html/index.html
  EOF

  # Metadatos de la instancia
  metadata = {
    # Clave pública SSH para acceso remoto
    ssh-keys = "debian:${file("~/.ssh/id_rsa.pub")}"

    # Habilitar el OS Login (autenticación basada en IAM)
    # enable-oslogin = "TRUE"
  }

  # Cuenta de servicio que la instancia usará para acceder a APIs de GCP
  service_account {
    email = google_service_account.compute.email
    # Scopes que definen qué APIs puede usar la instancia
    # cloud-platform es el scope más amplio (recomienda usar IAM roles)
    scopes = ["cloud-platform"]
  }

  # Opciones de programación de la instancia
  scheduling {
    # Si la instancia es preemptible (spot instance): más barata pero puede ser
    # interrumpida por Google cuando necesiten los recursos
    preemptible = false

    # Si es false, la instancia NO se reinicia automáticamente si el host falla
    automatic_restart = true
  }

  labels = {
    environment = var.environment
    managed_by  = "terraform"
    role        = "webserver"
  }
}

Cloud Storage Buckets

# storage.tf

resource "google_storage_bucket" "almacenamiento" {
  name    = "${var.project_id}-${var.environment}-almacenamiento"
  project = var.project_id

  # Ubicación del bucket:
  # Puede ser una región (us-central1), multi-región (US, EU, ASIA) o
  # bi-región (NAM4, EUR4)
  location = var.region

  # Clase de almacenamiento:
  # STANDARD: acceso frecuente (mayor costo, menor latencia)
  # NEARLINE: acceso < 1 vez al mes (30 días mínimo de almacenamiento)
  # COLDLINE: acceso < 1 vez al trimestre (90 días mínimo)
  # ARCHIVE: acceso < 1 vez al año (365 días mínimo)
  storage_class = "STANDARD"

  # Política de acceso uniforme a nivel de bucket
  # Simplifica la gestión de permisos (usa IAM en lugar de ACLs de objetos)
  uniform_bucket_level_access = true

  # Habilitar versionado de objetos
  versioning {
    enabled = true
  }

  # Reglas de ciclo de vida para gestionar objetos automáticamente
  lifecycle_rule {
    condition {
      # Se aplica a objetos con más de 30 días de antigüedad
      age = 30
    }
    action {
      # Mover a Nearline para reducir costos
      type          = "SetStorageClass"
      storage_class = "NEARLINE"
    }
  }

  lifecycle_rule {
    condition {
      # Eliminar versiones no actuales después de 90 días
      age                = 90
      with_state         = "ARCHIVED"
      num_newer_versions = 1
    }
    action {
      type = "Delete"
    }
  }

  # Encripción con clave gestionada por el cliente (CMEK)
  # Si no se especifica, GCP usa encripción con claves de Google (por defecto)
  # encryption {
  #   default_kms_key_name = google_kms_crypto_key.bucket_key.id
  # }

  labels = {
    environment = var.environment
    managed_by  = "terraform"
  }
}

# Hacer privado el bucket (bloquear acceso público)
resource "google_storage_bucket_iam_binding" "bloquear_publico" {
  bucket = google_storage_bucket.almacenamiento.name
  role   = "roles/storage.objectViewer"

  members = [
    # Solo el Service Account de la aplicación puede leer
    "serviceAccount:${google_service_account.app.email}",
  ]
}

Cloud SQL

# cloud_sql.tf

variable "db_password" {
  description = "Contraseña del usuario root de Cloud SQL"
  type        = string
  sensitive   = true
}

resource "google_sql_database_instance" "principal" {
  name    = "${var.project_name}-${var.environment}-db"
  project = var.project_id

  # Motor de base de datos: MYSQL_8_0, POSTGRES_15, SQLSERVER_2019_STANDARD
  database_version = "MYSQL_8_0"

  # Región donde se desplegará la instancia
  region = var.region

  settings {
    # Tier: define los recursos de la instancia
    # db-f1-micro: 0.6 GB RAM (elegible para free tier, límite 1/proyecto)
    # db-n1-standard-1: 3.75 GB RAM
    # db-n1-highmem-2: 13 GB RAM
    tier = "db-f1-micro"

    # Tipo de disponibilidad:
    # ZONAL: instancia en una sola zona (sin HA)
    # REGIONAL: instancia con HA automática en otra zona (más costoso)
    availability_type = "ZONAL"

    # Configuración de disco
    disk_size       = 10      # GB de almacenamiento inicial
    disk_type       = "PD_SSD" # PD_SSD o PD_HDD
    disk_autoresize = true    # Aumentar automáticamente si se llena

    # Configuración de backups
    backup_configuration {
      enabled            = true
      binary_log_enabled = true  # Necesario para point-in-time recovery

      # Ventana de backup (hora de inicio en UTC)
      start_time                     = "03:00"
      transaction_log_retention_days = 7
      backup_retention_settings {
        retained_backups = 7  # Mantener 7 backups diarios
      }
    }

    # Configuración de red y acceso
    ip_configuration {
      # Si es true, la instancia tiene IP pública
      ipv4_enabled = false

      # Conectar la BD a la VPC privada (Private IP)
      private_network = google_compute_network.principal.id

      # Habilitar acceso desde IPs específicas (solo si ipv4_enabled = true)
      # authorized_networks {
      #   value = "203.0.113.0/24"
      # }
    }

    # Ventana de mantenimiento
    maintenance_window {
      day          = 1  # 1 = lunes
      hour         = 4  # 4:00 AM UTC
      update_track = "stable"
    }
  }

  # Protección contra eliminación accidental
  deletion_protection = false  # En producción: true
}

# Base de datos dentro de la instancia Cloud SQL
resource "google_sql_database" "app" {
  name     = "miapp"
  instance = google_sql_database_instance.principal.name
  project  = var.project_id
}

# Usuario de la base de datos
resource "google_sql_user" "app" {
  name     = "appuser"
  instance = google_sql_database_instance.principal.name
  project  = var.project_id
  password = var.db_password
}

GKE Clusters

# gke.tf

resource "google_container_cluster" "principal" {
  name    = "${var.project_name}-${var.environment}-gke"
  project = var.project_id

  # Ubicación del cluster:
  # Una región (regional): alta disponibilidad, nodos en múltiples zonas
  # Una zona (zonal): más económico, nodos en una sola zona
  location = var.region

  # Eliminar el node pool por defecto y crear uno personalizado
  # (práctica recomendada para mayor control)
  remove_default_node_pool = true
  initial_node_count       = 1

  # Configuración de red
  network    = google_compute_network.principal.id
  subnetwork = google_compute_subnetwork.publica.id

  # Configuración de IPs para pods y servicios
  ip_allocation_policy {
    cluster_secondary_range_name  = "pods"     # Rango secundario para pods
    services_secondary_range_name = "services" # Rango secundario para services
  }

  # Habilitar Workload Identity (autenticación de pods con Service Accounts de GCP)
  workload_identity_config {
    workload_pool = "${var.project_id}.svc.id.goog"
  }

  # Configuración de logging y monitoring
  logging_service    = "logging.googleapis.com/kubernetes"
  monitoring_service = "monitoring.googleapis.com/kubernetes"

  # Actualización automática del master
  release_channel {
    # RAPID: primero recibe actualizaciones (menos estable)
    # REGULAR: balance entre estabilidad y novedad
    # STABLE: actualizaciones más lentas pero más probadas
    channel = "REGULAR"
  }
}

# Node Pool personalizado (los nodos que ejecutarán los pods)
resource "google_container_node_pool" "principal" {
  name    = "${var.project_name}-${var.environment}-node-pool"
  project = var.project_id

  # El cluster al que pertenece este node pool
  cluster  = google_container_cluster.principal.id
  location = var.region

  # Número inicial de nodos por zona
  initial_node_count = 1

  # Configuración de autoescalado
  autoscaling {
    min_node_count = 1   # Mínimo de nodos por zona
    max_node_count = 5   # Máximo de nodos por zona
  }

  # Gestión de los nodos (actualizaciones y reparaciones automáticas)
  management {
    auto_repair  = true  # Reparar nodos que fallen automáticamente
    auto_upgrade = true  # Actualizar la versión de Kubernetes automáticamente
  }

  node_config {
    # Tipo de máquina para los nodos
    machine_type = "e2-medium"  # 2 vCPU, 4 GB RAM

    # Tipo de disco de los nodos
    disk_size_gb = 50
    disk_type    = "pd-standard"

    # El Service Account que usarán los nodos
    service_account = google_service_account.gke_nodes.email

    # Scopes de OAuth para los nodos
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform",
    ]

    # Metadatos de los nodos
    metadata = {
      disable-legacy-endpoints = "true"
    }

    labels = {
      environment = var.environment
    }

    # Taints: permiten marcar nodos para que solo ciertos pods se ejecuten en ellos
    # taint {
    #   key    = "dedicated"
    #   value  = "special"
    #   effect = "NO_SCHEDULE"
    # }
  }
}

IAM Bindings

# iam.tf

# Service Account para las instancias de Compute Engine
resource "google_service_account" "compute" {
  account_id   = "${var.project_name}-${var.environment}-compute"
  display_name = "Service Account para instancias de Compute Engine"
  project      = var.project_id
}

# Service Account para la aplicación
resource "google_service_account" "app" {
  account_id   = "${var.project_name}-${var.environment}-app"
  display_name = "Service Account para la aplicación"
  project      = var.project_id
}

# Service Account para los nodos de GKE
resource "google_service_account" "gke_nodes" {
  account_id   = "${var.project_name}-${var.environment}-gke-nodes"
  display_name = "Service Account para nodos de GKE"
  project      = var.project_id
}

# Dar permisos al SA de la app para leer Cloud Storage
resource "google_project_iam_member" "app_storage_viewer" {
  project = var.project_id

  # El rol predefinido que se asigna
  # roles/storage.objectViewer: solo lectura de objetos
  # roles/storage.objectAdmin: lectura y escritura
  # roles/storage.admin: administración completa del bucket
  role = "roles/storage.objectAdmin"

  # El miembro al que se asigna el rol
  member = "serviceAccount:${google_service_account.app.email}"
}

# Dar permisos a los nodos de GKE para hacer pull de imágenes
resource "google_project_iam_member" "gke_artifact_registry" {
  project = var.project_id
  role    = "roles/artifactregistry.reader"
  member  = "serviceAccount:${google_service_account.gke_nodes.email}"
}

# IAM Binding a nivel de recurso específico (bucket de storage)
resource "google_storage_bucket_iam_member" "app_bucket_access" {
  bucket = google_storage_bucket.almacenamiento.name
  role   = "roles/storage.objectAdmin"
  member = "serviceAccount:${google_service_account.app.email}"
}

Data Sources de GCP

# data_sources.tf

# Obtener información del proyecto actual
data "google_project" "actual" {
  project_id = var.project_id
}

# Obtener la imagen de SO más reciente de Debian
data "google_compute_image" "debian" {
  family  = "debian-11"
  project = "debian-cloud"
}

# Obtener información de las zonas disponibles en la región
data "google_compute_zones" "disponibles" {
  project = var.project_id
  region  = var.region
  status  = "UP"  # Solo zonas en funcionamiento
}

# Uso de los data sources
resource "google_compute_instance" "web_dinamico" {
  name         = "${var.project_name}-web"
  project      = var.project_id
  machine_type = "e2-micro"

  # Usar la primera zona disponible dinámicamente
  zone = data.google_compute_zones.disponibles.names[0]

  boot_disk {
    initialize_params {
      # Usar la imagen obtenida del data source (siempre la más reciente)
      image = data.google_compute_image.debian.self_link
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.publica.id
  }
}

Tabla Comparativa: AWS vs Azure vs GCP en Terraform

Concepto AWS Azure GCP
Red privada aws_vpc azurerm_virtual_network google_compute_network
Subred aws_subnet azurerm_subnet google_compute_subnetwork
Firewall aws_security_group azurerm_network_security_group google_compute_firewall
IP pública aws_eip azurerm_public_ip google_compute_address
VM aws_instance azurerm_linux_virtual_machine google_compute_instance
Almacenamiento de objetos aws_s3_bucket azurerm_storage_account google_storage_bucket
Base de datos aws_db_instance azurerm_mssql_server google_sql_database_instance
Kubernetes aws_eks_cluster azurerm_kubernetes_cluster google_container_cluster
Serverless aws_lambda_function azurerm_linux_function_app google_cloudfunctions_function
DNS aws_route53_zone azurerm_dns_zone google_dns_managed_zone
IAM rol/cuenta aws_iam_role azurerm_role_assignment google_service_account
CDN aws_cloudfront_distribution azurerm_cdn_endpoint google_compute_backend_bucket
Agrupación lógica Tags + VPC azurerm_resource_group Project
Autoscaling aws_autoscaling_group azurerm_linux_vmss google_compute_instance_group_manager
Load Balancer aws_lb azurerm_lb google_compute_forwarding_rule
Secretos aws_secretsmanager_secret azurerm_key_vault_secret google_secret_manager_secret
Registro de contenedores aws_ecr_repository azurerm_container_registry google_artifact_registry_repository

Ejercicio Práctico: Crear una Instancia de Compute Engine con Reglas de Firewall

Descripción del ejercicio

Crea la siguiente infraestructura en GCP:

  1. Habilitar las APIs necesarias (Compute Engine)
  2. Una VPC personalizada (no la default)
  3. Una subred en la región us-central1 con CIDR 10.0.1.0/24
  4. Reglas de firewall para permitir SSH (22) y HTTP (80)
  5. Una IP estática externa
  6. Una instancia de Compute Engine con:
    • Tipo e2-micro
    • Sistema operativo Debian 11
    • Nginx instalado mediante startup script
    • Tags de red para las reglas de firewall

Solución Completa

# main.tf - Solución completa del ejercicio

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

provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

# Variables
variable "project_id" {
  description = "ID del proyecto de GCP"
  type        = string
}

variable "region" {
  default = "us-central1"
}

variable "zone" {
  default = "us-central1-a"
}

variable "environment" {
  default = "ejercicio"
}

# ============================================
# HABILITAR APIS
# ============================================

resource "google_project_service" "compute" {
  project            = var.project_id
  service            = "compute.googleapis.com"
  disable_on_destroy = false
}

# ============================================
# VPC Y SUBRED
# ============================================

resource "google_compute_network" "ejercicio" {
  name                    = "vpc-ejercicio"
  project                 = var.project_id
  auto_create_subnetworks = false

  depends_on = [google_project_service.compute]
}

resource "google_compute_subnetwork" "publica" {
  name          = "subnet-ejercicio"
  project       = var.project_id
  network       = google_compute_network.ejercicio.id
  ip_cidr_range = "10.0.1.0/24"
  region        = var.region
}

# ============================================
# REGLAS DE FIREWALL
# ============================================

resource "google_compute_firewall" "allow_ssh" {
  name    = "fw-ejercicio-allow-ssh"
  project = var.project_id
  network = google_compute_network.ejercicio.id

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  target_tags   = ["ssh-allowed"]
  source_ranges = ["0.0.0.0/0"]
}

resource "google_compute_firewall" "allow_http" {
  name    = "fw-ejercicio-allow-http"
  project = var.project_id
  network = google_compute_network.ejercicio.id

  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  target_tags   = ["web-server"]
  source_ranges = ["0.0.0.0/0"]
}

# ============================================
# IP ESTÁTICA
# ============================================

resource "google_compute_address" "web" {
  name         = "ip-ejercicio-web"
  project      = var.project_id
  region       = var.region
  address_type = "EXTERNAL"
}

# ============================================
# INSTANCIA DE COMPUTE ENGINE
# ============================================

resource "google_compute_instance" "web" {
  name         = "vm-ejercicio-web"
  project      = var.project_id
  machine_type = "e2-micro"
  zone         = var.zone

  # Tags de red para las reglas de firewall
  tags = ["web-server", "ssh-allowed"]

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
      size  = 20
      type  = "pd-standard"
    }
    auto_delete = true
  }

  network_interface {
    subnetwork = google_compute_subnetwork.publica.id

    access_config {
      nat_ip = google_compute_address.web.address
    }
  }

  metadata_startup_script = <<-EOF
    #!/bin/bash
    apt-get update -y
    apt-get install -y nginx
    systemctl start nginx
    systemctl enable nginx
    cat > /var/www/html/index.html << 'HTML'
    <!DOCTYPE html>
    <html>
    <head><title>GCP Terraform Ejercicio</title></head>
    <body>
      <h1>Compute Engine desplegada con Terraform en GCP</h1>
      <p>Ejercicio del Módulo 5 completado exitosamente.</p>
    </body>
    </html>
    HTML
  EOF

  labels = {
    environment = var.environment
    managed_by  = "terraform"
    role        = "webserver"
  }
}

# ============================================
# OUTPUTS
# ============================================

output "ip_publica" {
  value       = google_compute_address.web.address
  description = "IP pública de la instancia"
}

output "url_web" {
  value       = "http://${google_compute_address.web.address}"
  description = "URL para acceder al servidor web"
}

output "ssh_command" {
  value       = "gcloud compute ssh vm-ejercicio-web --zone=${var.zone} --project=${var.project_id}"
  description = "Comando para conectarse via SSH usando gcloud"
}
# Comandos para ejecutar el ejercicio
# Reemplaza TU_PROJECT_ID con el ID de tu proyecto de GCP

terraform init

terraform plan -var="project_id=TU_PROJECT_ID"

terraform apply -var="project_id=TU_PROJECT_ID" -auto-approve

# Esperar ~60 segundos para que el startup script termine
sleep 60

# Verificar el servidor web
IP=$(terraform output -raw ip_publica)
curl http://$IP

# Conectarse via SSH
gcloud compute ssh vm-ejercicio-web --zone=us-central1-a --project=TU_PROJECT_ID

# Limpiar recursos al terminar
terraform destroy -var="project_id=TU_PROJECT_ID" -auto-approve

Errores Comunes con GCP

Error 1: API no habilitada

Error: Error creating Instance: googleapi: Error 403:
Compute Engine API has not been used in project 12345 before or it is disabled.
Enable it by visiting https://console.developers.google.com/apis/api/compute.googleapis.com/overview

Causa: Las APIs de GCP deben habilitarse antes de usar sus recursos.

Solución:

# Habilitar la API manualmente
gcloud services enable compute.googleapis.com --project=mi-proyecto-id

# O con Terraform (como se mostró en el ejemplo)
resource "google_project_service" "compute" {
  project = var.project_id
  service = "compute.googleapis.com"
}

Error 2: Cuota excedida

Error: Error creating Instance: googleapi: Error 429:
Quota 'CPUS' exceeded. Limit: 8.0 in region us-central1.

Causa: GCP tiene cuotas por defecto relativamente bajas, especialmente para cuentas nuevas.

Solución: Solicitar aumento de cuota en la consola de GCP (IAM & Admin > Quotas):

# Ver cuotas actuales
gcloud compute regions describe us-central1 --format="table(quotas.metric,quotas.limit,quotas.usage)"

Error 3: Permisos insuficientes del Service Account

Error: googleapi: Error 403: Required 'compute.instances.create'
permission for 'projects/mi-proyecto'

Solución: Asignar el rol correcto al Service Account:

# Asignar rol de Editor (amplio, cuidado en producción)
gcloud projects add-iam-policy-binding mi-proyecto-id \
  --member="serviceAccount:[email protected]" \
  --role="roles/editor"

# O roles más específicos (principio de mínimo privilegio)
gcloud projects add-iam-policy-binding mi-proyecto-id \
  --member="serviceAccount:[email protected]" \
  --role="roles/compute.admin"

Error 4: Nombre de recurso inválido

Error: Error creating Bucket: googleapi: Error 400:
Invalid bucket name 'Mi_Bucket_Con_Mayusculas'

Causa: Los buckets de GCP solo permiten minúsculas, números y guiones.

Solución: Usar solo minúsculas y guiones en nombres de buckets:

resource "google_storage_bucket" "ejemplo" {
  # Correcto: solo minúsculas, números y guiones
  name = "mi-bucket-${var.project_id}-${var.environment}"
}

Error 5: Proyecto no encontrado o no accesible

Error: Error retrieving project: Get "https://cloudresourcemanager.googleapis.com/v1/projects/mi-proyecto":
googleapi: Error 403: The caller does not have permission, forbidden.

Causa: El ID de proyecto es incorrecto o las credenciales no tienen acceso.

Solución:

# Listar todos los proyectos accesibles
gcloud projects list

# Verificar el proyecto correcto
gcloud projects describe mi-proyecto-id

# Verificar las credenciales actuales
gcloud auth list
gcloud config get-value project

Resumen del Módulo 5: Aprovisionamiento en los Tres Grandes Proveedores

Has completado el módulo más práctico del curso de Terraform. En estos cuatro temas has aprendido:

05-01: Conceptos Básicos

  • El modelo declarativo vs imperativo y por qué Terraform usa el declarativo
  • El flujo completo: planapplydestroy
  • Cómo interpretar el plan de Terraform y sus símbolos
  • Herramientas avanzadas: -target, terraform graph, -parallelism

05-02: AWS

  • Configuración del provider y autenticación IAM
  • Los recursos fundamentales: VPC, EC2, S3, RDS, IAM
  • Arquitectura web básica con alta disponibilidad multi-AZ

05-03: Azure

  • El modelo de Azure con Resource Groups como unidad de organización
  • Autenticación con Service Principals y Managed Identity
  • Recursos principales: VNet, VMs, Storage Accounts, SQL, App Service
  • Diferencias clave con AWS

05-04: GCP (este módulo)

  • Estructura de GCP: Projects, Regions, Zones
  • La peculiaridad de tener que habilitar APIs explícitamente
  • Recursos fundamentales: VPC global, Compute Engine, Cloud Storage, Cloud SQL, GKE
  • El sistema de tags de red para reglas de firewall (diferente a Security Groups)

Próximos pasos

Con el conocimiento de este módulo puedes:

  1. Aprovisionar infraestructura en cualquiera de los tres grandes proveedores
  2. Entender las diferencias arquitectónicas entre AWS, Azure y GCP
  3. Migrar conocimientos de un proveedor a otro usando la tabla comparativa

El Módulo 6 profundizará en características avanzadas de Terraform: workspaces para gestionar múltiples entornos, funciones built-in, bloques dinámicos e importación de recursos existentes.

Ejercicio final del módulo: Intenta crear la misma arquitectura (una VM con Nginx y acceso HTTP) en los tres proveedores y compara el código HCL. Observa las similitudes (estructura resource, variable, output) y las diferencias (nombres de atributos, modelo de red, autenticación). Esta comparación te dará una comprensión profunda del valor de Terraform como herramienta multi-cloud.

© Copyright 2026. Todos los derechos reservados