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 listCon 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.jsoncontiene 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:
- Habilitar las APIs necesarias (Compute Engine)
- Una VPC personalizada (no la default)
- Una subred en la región
us-central1con CIDR10.0.1.0/24 - Reglas de firewall para permitir SSH (22) y HTTP (80)
- Una IP estática externa
- 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
- Tipo
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-approveErrores 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
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 projectResumen 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:
plan→apply→destroy - 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:
- Aprovisionar infraestructura en cualquiera de los tres grandes proveedores
- Entender las diferencias arquitectónicas entre AWS, Azure y GCP
- 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.
Curso de Terraform
Módulo 1: Introducción a Terraform
- ¿Qué es Terraform?
- Instalando Terraform
- Conceptos Básicos de Terraform
- Primera Configuración de Terraform
Módulo 2: Lenguaje de Configuración de Terraform
Módulo 3: Gestión del Estado
Módulo 4: Módulos de Terraform
Módulo 5: Aprovisionamiento de Recursos
- Conceptos Básicos de Aprovisionamiento
- Aprovisionamiento de Recursos AWS
- Aprovisionamiento de Recursos Azure
- Aprovisionamiento de Recursos GCP
Módulo 6: Funcionalidades Avanzadas de Terraform
Módulo 7: Mejores Prácticas de Terraform
- Organización del Código
- Control de Versiones
- Pruebas del Código de Terraform
- Mejores Prácticas de Seguridad
Módulo 8: Terraform en CI/CD
- Integración de Terraform con CI/CD
- Automatización de Terraform con Jenkins
- Uso de Terraform con GitHub Actions
- Terraform Cloud y Enterprise
