Cerramos el módulo 2 con un diagnóstico incómodo: todo lo que AlpinaShop ha construido está expuesto. La VM alpinashop-web-1 tiene el puerto 80 abierto a internet y el 22 accesible desde cualquier dirección, la instancia alpinashop-pedidos es alcanzable por IP pública, el Service de GKE publicó una IP sin HTTPS y no existe ninguna frontera entre lo que debe ser público y lo que jamás debería serlo. Esta lección construye esa frontera.

La red es la capa sobre la que se apoya todo lo demás. Antes de poner un balanceador, un CDN o un WAF hay que decidir dónde vive cada máquina, qué rangos de direcciones usa, quién puede hablar con quién y por dónde sale el tráfico a internet. En Google Cloud esa decisión se materializa en una VPC (Virtual Private Cloud): una red privada definida por software, aislada del resto de clientes, dentro de la cual conviven tus instancias, tus clústeres, tus bases de datos y tus balanceadores.

La red VPC de Google Cloud tiene una propiedad que sorprende a quien viene de AWS o de un centro de datos tradicional: es global. Una única red puede abarcar todas las regiones del planeta, y dos máquinas en Bélgica y en Tokio dentro de la misma VPC se hablan por IP privada sin túneles ni pasarelas. Las subredes, en cambio, son regionales. Entender esa asimetría es el 80 % de entender la red de Google.

En esta lección diseñarás e implementarás la red alpinashop-vpc con sus subredes, planificarás el direccionamiento sin solapes, escribirás el conjunto real de reglas de firewall de AlpinaShop, darás salida a internet a máquinas sin IP pública con Cloud NAT, alcanzarás las APIs de Google sin pasar por internet con Private Google Access y conectarás por fin alpinashop-pedidos por IP privada.

Aviso. Esta lección tiene implicaciones de seguridad directas. El diseño que se propone es razonable para una pyme como AlpinaShop, pero cualquier arquitectura de red que vaya a producción debe revisarla un profesional de seguridad, y si tratas datos personales o medios de pago, también un responsable de cumplimiento normativo.

Contenido

  1. Qué es una VPC y en qué se diferencia de una red tradicional
  2. Red por defecto, modo automático y modo personalizado
  3. Planificación del direccionamiento: CIDR sin solapes
  4. Creación de alpinashop-vpc y sus subredes
  5. Rangos secundarios para GKE
  6. Direcciones IP internas y externas, efímeras y estáticas
  7. Reglas de firewall VPC: el modelo completo
  8. El conjunto real de reglas de AlpinaShop
  9. Rutas y la puerta de enlace por defecto
  10. Cloud NAT: salida a internet sin IP pública
  11. Private Google Access: llegar a las APIs sin salir a internet
  12. Acceso privado a servicios: Cloud SQL por IP privada
  13. VPC Flow Logs: diagnóstico y auditoría
  14. Qué queda fuera de esta lección

  1. Qué es una VPC y en qué se diferencia de una red tradicional

En un centro de datos clásico, la red es física: switches, VLAN, cableado, rangos que alguien apuntó en una hoja de cálculo hace ocho años. En Google Cloud, la red es una abstracción de software montada sobre la red global de Google. Las diferencias prácticas son estas:

Concepto Red tradicional VPC de Google Cloud
Alcance Un centro de datos, una sede Global: todas las regiones a la vez
Segmentación VLAN + routers físicos Subredes regionales dentro de la misma red
Conexión entre sedes Enlaces dedicados, VPN Automática dentro de la VPC, por la red interna de Google
Firewall Aparato en un punto de la red Distribuido: se aplica en cada interfaz de cada VM
Cambiar un rango Ventana de mantenimiento Se añade una subred nueva; ampliar rango es una orden
Coste de la red interna Amortización del hardware Tráfico entre zonas y regiones, medido por GB

Tres consecuencias que conviene grabar desde el principio:

  • La red es un recurso global del proyecto. No pertenece a una región. Cuando creas alpinashop-vpc en alpinashop-prod, existe en todas partes.
  • Las subredes son regionales, no zonales. Una subred en europe-west1 cubre europe-west1-b, -c y -d. Una VM en cualquiera de esas zonas puede tomar una IP de esa subred. Esto es lo que permite que el MIG regional alpinashop-web-mig reparta instancias entre tres zonas usando una sola subred.
  • El firewall no está "en un sitio". No hay un cortafuegos perimetral por el que pase el tráfico: cada regla se aplica en el hipervisor, en la propia interfaz de red de cada VM. Por eso el tráfico entre dos VM de la misma subred también está sujeto a reglas, y por eso no puedes "saltarte" el firewall poniendo las máquinas juntas.

  1. Red por defecto, modo automático y modo personalizado

Cuando creaste el proyecto alpinashop-prod, Google creó automáticamente una red llamada default. Es cómoda para probar y mala idea para producción. Veamos por qué.

Existen dos modos de red:

Modo automático (--subnet-mode=auto) Modo personalizado (--subnet-mode=custom)
Subredes Una por región, creadas solas, con rangos fijos 10.128.0.0/9 Ninguna: las creas tú, con el rango que decidas
Nuevas regiones Aparece una subred automáticamente No cambia nada
Control del CIDR Ninguno Total
Riesgo de solape con la oficina o con otra VPC Alto Controlado
Reglas de firewall iniciales Permisivas (incluye SSH y RDP desde 0.0.0.0/0) Ninguna, salvo las implícitas

La red default es de modo automático y además trae reglas de firewall preconfiguradas que abren el puerto 22 (SSH) y el 3389 (RDP) a todo internet. Eso es exactamente el problema que AlpinaShop arrastra desde el módulo 2.

Toda organización seria usa modo personalizado por tres motivos:

  1. Control del direccionamiento. Si mañana AlpinaShop conecta su oficina por VPN (tema de 07-03), los rangos no pueden chocar. Con modo automático, Google decide por ti en todas las regiones presentes y futuras.
  2. Superficie de ataque mínima. No hay subredes en regiones que no usas ni reglas permisivas que alguien olvidó revisar.
  3. Reproducibilidad. La red se describe en código (Terraform, en 06-07) y se recrea idéntica en alpinashop-dev.

La primera tarea real, entonces, es crear una red personalizada y dejar de usar default:

# Fijamos el proyecto de trabajo (configuración nombrada del módulo 1)
gcloud config set project alpinashop-prod

# Vemos qué hay ahora mismo
gcloud compute networks list
gcloud compute networks subnets list --filter="network:default"

Más adelante, cuando todo esté migrado, la red default se elimina. No se elimina el primer día: hay recursos vivos dentro. El orden correcto es crear la red nueva → mover cargas → borrar la vieja.

  1. Planificación del direccionamiento: CIDR sin solapes

Antes de escribir un solo comando, se planifica en papel. Un plan de direccionamiento malo se paga durante años, porque el rango primario de una subred se puede ampliar, pero no reducir ni mover, y porque dos redes con rangos solapados no se pueden conectar nunca.

Recordatorio mínimo de CIDR:

Notación Máscara Direcciones totales Direcciones usables en GCP
/24 255.255.255.0 256 252
/22 255.255.252.0 1 024 1 020
/20 255.255.240.0 4 096 4 092
/16 255.255.0.0 65 536 65 532

Google reserva cuatro direcciones en cada subred: la de red, la puerta de enlace (siempre la segunda, x.x.x.1), y dos reservadas para uso futuro (las dos últimas). Por eso un /24 da 252 IP utilizables, no 254.

El plan de AlpinaShop reserva el bloque privado 10.10.0.0/16 para producción y deja espacio deliberado entre subredes:

Uso Rango Región Comentario
sn-web-euw1 (primario) 10.10.0.0/24 europe-west1 Instancias del MIG del catálogo
sn-datos-euw1 (primario) 10.10.1.0/24 europe-west1 Backends internos y trabajos por lotes
sn-web-euw1 secundario gke-pods 10.20.0.0/16 — Pods de alpinashop-cluster
sn-web-euw1 secundario gke-servicios 10.21.0.0/20 — Services de alpinashop-cluster
Acceso privado a servicios (Cloud SQL) 10.30.0.0/20 — Rango cedido a Google para servicios gestionados
Reservado para futuras subredes 10.10.2.0/24 … 10.10.255.0/24 — No asignar sin actualizar este documento
Reservado para alpinashop-dev 10.60.0.0/16 — Otra VPC, otro proyecto
Oficina de AlpinaShop (VPN futura) 192.168.10.0/24 — No usar nunca dentro de la VPC

Reglas de oro al planificar:

  • Nunca uses 10.0.0.0/24 ni 192.168.1.0/24. Son los rangos que usa medio mundo, incluida la red doméstica de tus compañeros; el día que haya VPN, chocarán.
  • Deja huecos. Es más fácil pedir perdón que reorganizar direcciones.
  • Documenta el plan y versiónalo. El documento de direccionamiento vive en el repositorio, no en la memoria de Marta.
  • Los rangos de pods de Kubernetes son grandes. Un /16 para pods no es exagerado: GKE reserva un bloque por nodo.

  1. Creación de alpinashop-vpc y sus subredes

Con el plan cerrado, la implementación son tres órdenes:

# 1) La red, en modo personalizado y con enrutamiento global
gcloud compute networks create alpinashop-vpc \
  --project=alpinashop-prod \
  --subnet-mode=custom \
  --bgp-routing-mode=global \
  --description="Red principal de produccion de AlpinaShop"

Dos parámetros merecen explicación:

  • --subnet-mode=custom: no crea ninguna subred automáticamente. Es lo que queremos.
  • --bgp-routing-mode=global: cuando en el futuro haya un Cloud Router (VPN o Interconnect, tema de 07-03), las rutas aprendidas se propagarán a todas las regiones, no solo a la del router. Es el valor correcto para una red que puede crecer; cambiarlo después obliga a reconfigurar el router.
# 2) Subred de la capa web
gcloud compute networks subnets create sn-web-euw1 \
  --project=alpinashop-prod \
  --network=alpinashop-vpc \
  --region=europe-west1 \
  --range=10.10.0.0/24 \
  --enable-private-ip-google-access \
  --enable-flow-logs \
  --logging-aggregation-interval=interval-5-sec \
  --logging-flow-sampling=0.5 \
  --logging-metadata=include-all

# 3) Subred de datos y backends internos
gcloud compute networks subnets create sn-datos-euw1 \
  --project=alpinashop-prod \
  --network=alpinashop-vpc \
  --region=europe-west1 \
  --range=10.10.1.0/24 \
  --enable-private-ip-google-access \
  --enable-flow-logs \
  --logging-flow-sampling=0.5

Qué hace cada opción, en detalle:

  • --range: el rango primario. Es el que se reparte entre las interfaces de las VM.
  • --enable-private-ip-google-access: permite que una VM sin IP pública llegue a las APIs de Google (Cloud Storage, BigQuery, Secret Manager…). Lo desarrollamos en el apartado 11. Actívalo siempre; no tiene coste ni contraindicación.
  • --enable-flow-logs y sus parámetros: activa el registro de flujos. --logging-flow-sampling=0.5 muestrea la mitad de los flujos, lo que reduce el coste de logs a la mitad manteniendo utilidad de diagnóstico. Lo vemos en el apartado 13.

Comprobación:

gcloud compute networks subnets describe sn-web-euw1 \
  --region=europe-west1 \
  --format="table(name, ipCidrRange, gatewayAddress, privateIpGoogleAccess)"

Verás que gatewayAddress es 10.10.0.1: la puerta de enlace siempre es la segunda dirección del rango, y no es una máquina que puedas tocar, sino una función distribuida de la red.

  1. Rangos secundarios para GKE

El clúster alpinashop-cluster que creamos en 02-05 necesita direcciones para pods y para services, y esas direcciones no salen del rango primario de la subred, sino de rangos secundarios (lo que Google llama alias IP ranges). Este es el modelo de red nativo de VPC de GKE, y es el que se usa siempre en Autopilot.

gcloud compute networks subnets update sn-web-euw1 \
  --region=europe-west1 \
  --add-secondary-ranges=gke-pods=10.20.0.0/16,gke-servicios=10.21.0.0/20

Por qué rangos tan grandes:

Elemento Rango Capacidad aproximada
Nodos 10.10.0.0/24 (primario) 252 nodos
Pods 10.20.0.0/16 65 536 direcciones; GKE asigna un /24 por nodo por defecto
Services (ClusterIP) 10.21.0.0/20 4 096 servicios

La consecuencia de mayor calado: con red nativa de VPC, un pod tiene una IP real de la VPC. No hay NAT interno entre pods y VM. Eso significa que una regla de firewall puede referirse directamente al rango de pods, y que una VM de sn-datos-euw1 puede recibir tráfico de un pod y ver su IP de origen auténtica. Es más simple de razonar y más fácil de auditar.

Al crear un clúster nuevo se indican los rangos así (referencia para cuando recrees el clúster en la red nueva):

gcloud container clusters create-auto alpinashop-cluster \
  --region=europe-west1 \
  --network=alpinashop-vpc \
  --subnetwork=sn-web-euw1 \
  --cluster-secondary-range-name=gke-pods \
  --services-secondary-range-name=gke-servicios

  1. Direcciones IP internas y externas, efímeras y estáticas

Cada VM tiene siempre una IP interna (de la subred) y, opcionalmente, una IP externa. Y cada una puede ser efímera (se asigna al arrancar y se pierde al parar la máquina) o estática (reservada, sobrevive a la instancia).

Tipo Alcance Persistencia Coste orientativo Uso típico en AlpinaShop
Interna efímera Subred Mientras viva la VM Gratis Instancias del MIG
Interna estática Subred Reservada Gratis Backends internos con nombre fijo
Externa efímera Internet Mientras viva la VM Se factura mientras está en uso Nada en producción
Externa estática regional Internet Reservada Pequeño coste; más cara si está sin usar Balanceador regional, Cloud NAT
Externa estática global Internet (anycast) Reservada Pequeño coste IP del balanceador HTTPS global (03-02)

Reservamos ya la IP global que usará el balanceador de la próxima lección y las IP que necesitará Cloud NAT:

# IP global anycast para el balanceador HTTPS externo (la usaremos en 03-02 y 03-07)
gcloud compute addresses create alpinashop-lb-ip \
  --project=alpinashop-prod \
  --global \
  --ip-version=IPV4

gcloud compute addresses describe alpinashop-lb-ip --global --format="value(address)"

# IP regional estática para la salida de Cloud NAT
gcloud compute addresses create alpinashop-nat-ip \
  --project=alpinashop-prod \
  --region=europe-west1

Reservar la IP de salida de NAT tiene una ventaja concreta: la pasarela de pago de AlpinaShop exige una lista de IP autorizadas. Si la IP de salida fuese efímera, cambiaría al recrear el NAT y el cobro dejaría de funcionar un martes por la tarde sin motivo aparente.

Una advertencia de facturación: una IP externa estática reservada y no asociada a nada se factura igualmente, y a una tarifa mayor que si estuviera en uso. Es una de las fugas de coste más habituales. Revísalas periódicamente:

gcloud compute addresses list --filter="status=RESERVED" \
  --format="table(name, region, address, status)"

  1. Reglas de firewall VPC: el modelo completo

El firewall de VPC es stateful (si permites la conexión de entrada, la respuesta sale sin necesidad de regla) y distribuido (se aplica en cada interfaz). Una regla tiene estos elementos:

Elemento Valores Notas
Dirección INGRESS (entrada) o EGRESS (salida) Por defecto INGRESS
Acción allow o deny No hay "log only"; para eso está --enable-logging
Prioridad 0 – 65535, menor gana Por defecto 1000
Origen (ingress) Rangos CIDR, etiquetas de red, cuentas de servicio Se elige uno de los tres tipos
Destino (egress) Rangos CIDR
Destinatarios Toda la red, etiquetas de red, o cuentas de servicio El "a quién se aplica"
Protocolos y puertos tcp:80,443, udp:53, icmp, all

Además existen cuatro reglas implícitas que no ves en la lista y no puedes borrar:

Regla implícita Dirección Acción Prioridad
Permitir toda la salida EGRESS allow 65535
Denegar toda la entrada INGRESS deny 65535
Permitir DHCP, DNS y metadatos (169.254.169.254) EGRESS allow Siempre
Bloquear el puerto 25 saliente EGRESS deny Siempre (antispam)

La consecuencia práctica es la que define la disciplina de trabajo: por defecto no entra nada y sale todo. Todo lo que quieras permitir de entrada hay que escribirlo; todo lo que quieras impedir de salida, también.

Etiquetas de red frente a cuentas de servicio

Es la decisión de diseño más importante del firewall. Se puede seleccionar a qué máquinas se aplica una regla de dos maneras:

Criterio Etiquetas de red (--target-tags) Cuentas de servicio (--target-service-accounts)
Quién puede ponerlas Cualquiera con compute.instances.setTags Solo quien puede actuar como esa cuenta de servicio
Cambio en caliente Sí, al vuelo Requiere parar la instancia
Riesgo Un desarrollador puede etiquetar su VM como web y heredar sus permisos de red Controlado por IAM
Legibilidad Muy alta Alta
Recomendación Aceptable para entornos pequeños Preferible en producción

AlpinaShop usa un modelo mixto y explícito: etiquetas para las reglas de entrada desde internet (donde la legibilidad importa y las máquinas son del MIG) y cuentas de servicio para el acceso a datos. Y documenta esa decisión, porque mezclar los dos criterios sin criterio es una fuente clásica de agujeros.

  1. El conjunto real de reglas de AlpinaShop

Este es el conjunto completo. Léelo entero antes de ejecutarlo: cada regla responde a una necesidad concreta.

# ---------------------------------------------------------------
# 1) Tráfico HTTP/HTTPS SOLO desde los rangos del balanceador de Google
#    130.211.0.0/22 y 35.191.0.0/16 son los rangos desde los que
#    Google Cloud origina las peticiones del balanceador y las
#    comprobaciones de estado. NO son "internet".
# ---------------------------------------------------------------
gcloud compute firewall-rules create fw-allow-lb-health-web \
  --network=alpinashop-vpc \
  --direction=INGRESS \
  --action=allow \
  --priority=1000 \
  --source-ranges=130.211.0.0/22,35.191.0.0/16 \
  --target-tags=web-catalogo \
  --rules=tcp:80,tcp:8080 \
  --description="HTTP y health checks desde el balanceador global"

# ---------------------------------------------------------------
# 2) SSH SOLO desde el rango del Identity-Aware Proxy (IAP).
#    35.235.240.0/20 es el rango desde el que IAP abre el túnel TCP.
#    Con esto NO hace falta ninguna IP publica en las VM.
# ---------------------------------------------------------------
gcloud compute firewall-rules create fw-allow-ssh-iap \
  --network=alpinashop-vpc \
  --direction=INGRESS \
  --action=allow \
  --priority=1000 \
  --source-ranges=35.235.240.0/20 \
  --rules=tcp:22 \
  --description="SSH unicamente a traves de IAP (ver 03-04)"

# ---------------------------------------------------------------
# 3) La capa web puede hablar con la capa de datos por PostgreSQL.
#    Selector por cuenta de servicio: solo las maquinas que corren
#    como sa-catalogo-web pueden abrir el 5432.
# ---------------------------------------------------------------
gcloud compute firewall-rules create fw-allow-web-to-datos \
  --network=alpinashop-vpc \
  --direction=INGRESS \
  --action=allow \
  --priority=1000 \
  --source-service-accounts=sa-catalogo-web@alpinashop-prod.iam.gserviceaccount.com \
  --target-tags=capa-datos \
  --rules=tcp:5432 \
  --description="Acceso PostgreSQL desde la aplicacion del catalogo"

# ---------------------------------------------------------------
# 4) Denegar explicitamente el resto de entrada, con registro.
#    Prioridad 65000: por debajo de todas las reglas anteriores,
#    por encima de la regla implicita 65535. Sirve para VER en los
#    logs qué se está intentando y no se permite.
# ---------------------------------------------------------------
gcloud compute firewall-rules create fw-deny-all-ingress \
  --network=alpinashop-vpc \
  --direction=INGRESS \
  --action=deny \
  --priority=65000 \
  --source-ranges=0.0.0.0/0 \
  --rules=all \
  --enable-logging \
  --description="Denegacion explicita con log de todo lo no permitido"

Diagrama del resultado:

flowchart TB
    Internet([Internet])
    LB[Balanceador HTTPS global<br/>130.211.0.0/22 · 35.191.0.0/16]
    IAP[Identity-Aware Proxy<br/>35.235.240.0/20]

    subgraph VPC["alpinashop-vpc · global"]
      subgraph SNW["sn-web-euw1 · 10.10.0.0/24 · europe-west1"]
        MIG["alpinashop-web-mig<br/>tag: web-catalogo<br/>SA: sa-catalogo-web"]
        GKE["alpinashop-cluster<br/>pods 10.20.0.0/16"]
      end
      subgraph SND["sn-datos-euw1 · 10.10.1.0/24"]
        BATCH["Lotes / backends internos<br/>tag: capa-datos"]
      end
      NAT[Cloud NAT]
    end

    PSA["Acceso privado a servicios<br/>10.30.0.0/20"]
    SQL[(alpinashop-pedidos<br/>IP privada)]

    Internet -->|443| LB
    LB -->|80/8080 tag web-catalogo| MIG
    IAP -->|22| MIG
    MIG -->|5432 SA sa-catalogo-web| SQL
    MIG --> NAT --> Internet
    SQL --- PSA
    PSA --- VPC

Por qué 0.0.0.0/0 en el puerto 22 es un error

Es el patrón más extendido y el más peligroso. Abrir tcp:22 a 0.0.0.0/0 significa literalmente que cualquier máquina del planeta puede intentar autenticarse en tu servidor. Las consecuencias no son teóricas:

  • Un servidor con el 22 abierto recibe miles de intentos de autenticación al día desde el primer minuto. Los escáneres automáticos barren los rangos de todos los proveedores de nube continuamente.
  • Basta una credencial débil, una clave reutilizada o un fallo en sshd para que se convierta en una intrusión.
  • El ruido en los logs esconde los ataques reales.
  • Cierra la puerta a la trazabilidad: no sabes quién entró, solo qué clave se usó.

La alternativa correcta es IAP TCP forwarding, la regla número 2 del bloque anterior. Con ella:

  • Las VM no necesitan IP pública.
  • El acceso pasa por Google, que exige autenticación con la identidad corporativa (Marta, Dani) y aplica IAM: quien no tenga el rol roles/iap.tunnelResourceAccessor no llega ni al saludo de SSH.
  • Cada sesión queda registrada con la identidad real de la persona.
# Conexión SSH sin IP pública, a través de IAP
gcloud compute ssh alpinashop-web-1 \
  --zone=europe-west1-b \
  --tunnel-through-iap

Los detalles de IAP y sus permisos los desarrollamos en 03-04 (IAM); aquí basta con saber que la regla de firewall del 22 apunta a 35.235.240.0/20 y nunca a internet.

Comprobar reglas antes de que duelan

# Ver todas las reglas ordenadas por prioridad
gcloud compute firewall-rules list \
  --filter="network:alpinashop-vpc" \
  --sort-by=priority \
  --format="table(name, direction, priority, sourceRanges.list(), targetTags.list(), allowed[].map().firewall_rule().list())"

# Simular: ¿puede esta VM recibir tráfico en el 22 desde internet?
gcloud compute networks get-effective-firewalls alpinashop-web-1 \
  --zone=europe-west1-b 2>/dev/null || \
gcloud compute instances network-interfaces get-effective-firewalls alpinashop-web-1 \
  --zone=europe-west1-b

  1. Rutas y la puerta de enlace por defecto

Las rutas deciden por dónde sale un paquete. Cada VPC nace con dos tipos de ruta automáticas:

Ruta Destino Siguiente salto Se puede borrar
Ruta de subred El CIDR de cada subred La propia VPC No (se crea y borra con la subred)
Ruta por defecto 0.0.0.0/0 Puerta de enlace de internet Sí

Que la ruta por defecto se pueda borrar es una herramienta de seguridad muy potente: si eliminas la ruta 0.0.0.0/0 de una VPC, ninguna máquina de esa red puede salir a internet, ni siquiera con IP pública. Es el patrón de las redes aisladas para tratamiento de datos sensibles. AlpinaShop no llega ahí en producción, pero es útil para el proyecto de análisis de Lucía si algún día trata datos personales.

gcloud compute routes list --filter="network:alpinashop-vpc" \
  --format="table(name, destRange, nextHopGateway, priority)"

Las rutas personalizadas (por ejemplo, enviar cierto tráfico a un appliance de red) y las rutas aprendidas por BGP desde una VPN pertenecen a 07-03.

  1. Cloud NAT: salida a internet sin IP pública

Ya hemos dicho que las VM no deben tener IP pública. Pero necesitan salir a internet para actualizar paquetes del sistema, instalar dependencias de Python o llamar a la API de la pasarela de pago. La solución es Cloud NAT: un servicio gestionado, sin instancias que mantener, que traduce las direcciones privadas a una o varias IP públicas de salida.

Puntos clave:

  • Cloud NAT es solo de salida. Nadie puede iniciar una conexión desde internet hacia una VM a través de él. Es exactamente lo que queremos.
  • No es una máquina: no hay cuello de botella que dimensionar ni parches que aplicar.
  • Se configura por región y se asocia a un Cloud Router, que en este caso no hace BGP: es solo el punto de anclaje del servicio.
# 1) Cloud Router (obligatorio para NAT, aunque aquí no anuncie rutas)
gcloud compute routers create alpinashop-router-euw1 \
  --network=alpinashop-vpc \
  --region=europe-west1

# 2) La pasarela NAT, con la IP estática reservada en el apartado 6
gcloud compute routers nats create alpinashop-nat-euw1 \
  --router=alpinashop-router-euw1 \
  --region=europe-west1 \
  --nat-custom-subnet-ip-ranges=sn-web-euw1,sn-datos-euw1 \
  --nat-external-ip-pool=alpinashop-nat-ip \
  --enable-logging \
  --log-filter=ERRORS_ONLY \
  --min-ports-per-vm=128

Detalle de las opciones:

  • --nat-custom-subnet-ip-ranges: solo estas dos subredes salen por NAT. La alternativa (--nat-all-subnet-ip-ranges) es cómoda pero menos explícita.
  • --nat-external-ip-pool=alpinashop-nat-ip: usa nuestra IP estática. Esa es la dirección que AlpinaShop comunica a la pasarela de pago para su lista de autorizados. La alternativa --auto-allocate-nat-external-ips deja que Google elija, y las IP pueden cambiar.
  • --min-ports-per-vm=128: cada VM reserva 128 puertos de origen. Es el parámetro que causa el fallo más típico de Cloud NAT: si una máquina abre muchas conexiones salientes simultáneas (por ejemplo, un trabajo que descarga miles de ficheros) y se queda sin puertos, las conexiones nuevas fallan con un error confuso. El síntoma se ve en las métricas como nat_allocation_failed.
  • --log-filter=ERRORS_ONLY: registra solo los fallos. ALL genera un volumen de logs considerable y coste asociado.

Verificación desde una VM sin IP pública:

gcloud compute ssh alpinashop-web-1 --zone=europe-west1-b --tunnel-through-iap

# Dentro de la VM: la IP de salida debe ser la de alpinashop-nat-ip
curl -s https://ifconfig.me

  1. Private Google Access: llegar a las APIs sin salir a internet

Cuando una VM sin IP pública llama a storage.googleapis.com para leer del bucket alpinashop-catalogo, ¿por dónde va ese tráfico? Hay dos respuestas posibles, y la buena no es la evidente.

  • Sin Private Google Access: la petición sale por Cloud NAT, va a internet, resuelve una IP pública de Google y vuelve. Funciona, pero sale de la red de Google, consume capacidad de NAT y se factura como tráfico de salida.
  • Con Private Google Access activado en la subred: la petición se enruta internamente hacia los frontales de las APIs de Google. No pasa por internet, no consume puertos de NAT y no se factura como egress a internet.

Ya lo activamos al crear las subredes con --enable-private-ip-google-access. Si necesitas activarlo después:

gcloud compute networks subnets update sn-web-euw1 \
  --region=europe-west1 \
  --enable-private-ip-google-access

gcloud compute networks subnets describe sn-datos-euw1 \
  --region=europe-west1 \
  --format="value(privateIpGoogleAccess)"

Es una opción que debe estar activada siempre, en todas las subredes, en todos los entornos. No tiene coste, no tiene contraindicación y afecta a casi todo lo que hará AlpinaShop: leer el bucket, escribir en BigQuery (módulo 4), leer un secreto de Secret Manager (03-06) o descargar una imagen de Artifact Registry.

Existe una variante, Private Service Connect, que permite alcanzar APIs de Google o servicios de terceros por una IP privada tuya, con control de DNS. Es una pieza de arquitecturas más grandes y se estudia en 07-03.

  1. Acceso privado a servicios: Cloud SQL por IP privada

En 02-03 creamos alpinashop-pedidos con IP pública y nos conectamos con el Auth Proxy. Funcionaba, pero la instancia era alcanzable desde internet (aunque protegida por redes autorizadas y credenciales). Ahora la movemos a IP privada, que es la configuración correcta.

El mecanismo se llama acceso privado a servicios y consiste en esto: cedes un rango de tu VPC a Google, Google crea allí los recursos gestionados (la instancia de Cloud SQL) y establece un emparejamiento entre tu red y la suya. Desde tu VPC, la base de datos aparece con una IP normal de ese rango.

flowchart LR
    subgraph VPC["alpinashop-vpc"]
      VM["alpinashop-web-1<br/>10.10.0.5"]
      RES["Rango reservado<br/>10.30.0.0/20"]
    end
    subgraph GOOG["Red de servicios gestionados de Google"]
      SQL[(alpinashop-pedidos<br/>10.30.0.3)]
    end
    VM -->|5432 por IP privada| RES
    RES -.emparejamiento de redes.-> GOOG
    GOOG --> SQL

Implementación, paso a paso:

# 1) Habilitar la API de networking de servicios
gcloud services enable servicenetworking.googleapis.com --project=alpinashop-prod

# 2) Reservar el rango que cedemos a Google (el del plan: 10.30.0.0/20)
gcloud compute addresses create alpinashop-psa-range \
  --global \
  --purpose=VPC_PEERING \
  --addresses=10.30.0.0 \
  --prefix-length=20 \
  --network=alpinashop-vpc \
  --description="Rango para servicios gestionados (Cloud SQL)"

# 3) Establecer la conexión privada
gcloud services vpc-peerings connect \
  --service=servicenetworking.googleapis.com \
  --ranges=alpinashop-psa-range \
  --network=alpinashop-vpc \
  --project=alpinashop-prod

# 4) Comprobar
gcloud services vpc-peerings list \
  --network=alpinashop-vpc \
  --project=alpinashop-prod

Con la conexión establecida, se asigna la IP privada a la instancia y se retira la pública:

# Asignar IP privada dentro de alpinashop-vpc
gcloud sql instances patch alpinashop-pedidos \
  --project=alpinashop-prod \
  --network=projects/alpinashop-prod/global/networks/alpinashop-vpc \
  --no-assign-ip

# Ver la IP privada resultante
gcloud sql instances describe alpinashop-pedidos \
  --format="value(ipAddresses[].ipAddress, ipAddresses[].type)"

Detalles que evitan sustos:

  • --no-assign-ip retira la IP pública. Hazlo solo cuando hayas verificado que la conexión privada funciona desde una VM; si no, te quedas sin acceso hasta revertirlo.
  • El rango cedido no se puede reducir después sin recrear la conexión. Un /20 da margen para instancias futuras (la réplica alpinashop-pedidos-replica-informes también consume direcciones de ahí).
  • La cadena de conexión de la aplicación Flask cambia de la IP pública a la privada; el Auth Proxy sigue siendo válido y sigue siendo la opción recomendada porque añade cifrado y autenticación IAM, pero ya no es la única forma de llegar.
  • El cambio de red de una instancia de Cloud SQL implica reinicio. Planifícalo fuera de horario comercial.

  1. VPC Flow Logs: diagnóstico y auditoría

Los registros de flujo graban una muestra de las conexiones que atraviesan las interfaces de las VM: origen, destino, puertos, bytes, paquetes, y si fueron permitidas o denegadas. Sirven para tres cosas:

  1. Diagnosticar por qué algo no conecta (¿llegó el paquete? ¿lo denegó una regla?).
  2. Auditar: ver a qué destinos externos habla realmente tu infraestructura.
  3. Analizar coste de tráfico entre zonas y regiones.

Ya los activamos al crear las subredes. Una consulta típica en Cloud Logging (el servicio se estudia a fondo en 06-06; aquí lo usamos como herramienta):

# Conexiones denegadas hacia la capa web en la última hora
gcloud logging read '
  resource.type="gce_subnetwork"
  AND log_id("compute.googleapis.com/firewall")
  AND jsonPayload.disposition="DENIED"
' --project=alpinashop-prod --limit=20 --freshness=1h \
  --format="table(
    timestamp,
    jsonPayload.connection.src_ip,
    jsonPayload.connection.dest_ip,
    jsonPayload.connection.dest_port,
    jsonPayload.rule_details.reference
  )"
# Flujos con más bytes salientes: ¿quién está generando tráfico?
gcloud logging read '
  resource.type="gce_subnetwork"
  AND log_id("compute.googleapis.com/vpc_flows")
' --project=alpinashop-prod --limit=10 --freshness=30m \
  --format="table(
    jsonPayload.connection.src_ip,
    jsonPayload.connection.dest_ip,
    jsonPayload.bytes_sent
  )"

Consideraciones de coste y privacidad:

  • Los flow logs se facturan por volumen de logs ingeridos. En una red con tráfico intenso, el muestreo al 100 % puede ser caro. 0.5 es un punto de partida razonable; 0.1 para tráfico muy alto.
  • Contienen direcciones IP de clientes, que en la Unión Europea son dato personal. Fija una política de retención acorde con el RGPD y documenta el tratamiento. Esta es una de esas decisiones que debe validar el responsable de cumplimiento antes de producción.

Además de los flow logs, para diagnóstico puntual está Network Intelligence Center y su herramienta de pruebas de conectividad, que simula un paquete y te dice exactamente qué regla lo permitió o denegó, sin generar tráfico real:

gcloud network-management connectivity-tests create prueba-web-a-sql \
  --source-instance=projects/alpinashop-prod/zones/europe-west1-b/instances/alpinashop-web-1 \
  --destination-cloud-sql-instance=projects/alpinashop-prod/instances/alpinashop-pedidos \
  --destination-port=5432 \
  --protocol=TCP

gcloud network-management connectivity-tests describe prueba-web-a-sql \
  --format="value(reachabilityDetails.result)"

  1. Qué queda fuera de esta lección

Para que no te quede la sensación de hueco, esto es lo que existe y no hemos tratado aquí:

Tema Dónde se ve
VPC compartida entre proyectos 07-03
Emparejamiento (peering) entre VPC propias 07-03
Cloud VPN, Interconnect, conectividad híbrida 07-03
Private Service Connect a fondo 07-03
Balanceo de carga 03-02 (la siguiente)
Cloud Armor y protección DDoS 03-05
Permisos de IAP y de red 03-04
Cloud Logging a fondo 06-06
Organization policies de red (p. ej. prohibir IP públicas) 07-07

Errores Comunes y Consejos

  • Usar la red default en producción. Trae subredes en todas las regiones y reglas que abren SSH y RDP a internet. Crea una red personalizada desde el principio y borra la default cuando ya no queden recursos dentro.
  • Abrir tcp:22 a 0.0.0.0/0. El error clásico. Usa IAP con la regla desde 35.235.240.0/20 y quita las IP públicas de las VM.
  • Olvidar los rangos del balanceador. Si no permites 130.211.0.0/22 y 35.191.0.0/16, las comprobaciones de estado fallan, el backend se marca no saludable y el balanceador devuelve 502 sin explicación clara. Es, con diferencia, el fallo número uno de la próxima lección.
  • Elegir rangos "cómodos" como 10.0.0.0/16 o 192.168.1.0/24. Chocarán el día de la VPN. Planifica y documenta el direccionamiento antes de crear nada.
  • Creer que las subredes son zonales. Son regionales. No necesitas una subred por zona; una subred regional sirve a un MIG regional en tres zonas.
  • Confundir prioridad alta con número alto. En el firewall de VPC gana el número menor. Una regla allow con prioridad 100 vence a una deny con prioridad 1000.
  • Dejar IP externas estáticas reservadas sin usar. Se facturan, y a tarifa superior. Audita gcloud compute addresses list --filter="status=RESERVED" cada mes.
  • No activar Private Google Access. Sin él, todo el tráfico a las APIs de Google sale por NAT a internet: más coste, más latencia y agotamiento de puertos de NAT.
  • Retirar la IP pública de Cloud SQL antes de comprobar la privada. Verifica primero la conectividad privada desde una VM y después aplica --no-assign-ip.
  • Dimensionar mal min-ports-per-vm en Cloud NAT. Un trabajo por lotes con muchas conexiones salientes agota los puertos y provoca fallos intermitentes difíciles de atribuir. Vigila la métrica de fallos de asignación.
  • Confiar solo en el firewall. El firewall de VPC controla el nivel de red. La autenticación, la autorización y la validación de entrada siguen siendo responsabilidad de la aplicación.

Ejercicios

Ejercicio 1 — Plan de direccionamiento para alpinashop-dev

Marta necesita replicar la red en el proyecto alpinashop-dev. El requisito es que los rangos no solapen con producción (10.10.0.0/16, secundarios 10.20.0.0/16 y 10.21.0.0/20, acceso privado a servicios 10.30.0.0/20) ni con la oficina (192.168.10.0/24), porque en el futuro se conectarán ambos entornos.

Diseña el plan (red, dos subredes en europe-west1, rangos secundarios de GKE y rango para servicios gestionados) y escribe los comandos gcloud que lo crean, con Private Google Access activado.

Ejercicio 2 — Reglas de firewall para el servicio interno de informes

Lucía necesita una VM de informes llamada alpinashop-informes-1 en sn-datos-euw1, con la etiqueta informes, que cumpla:

  1. Solo Lucía y Marta pueden entrar por SSH, y únicamente a través de IAP.
  2. La VM debe poder consultar la réplica alpinashop-pedidos-replica-informes por el puerto 5432.
  3. La VM no debe recibir tráfico desde internet ni desde la capa web.
  4. La VM debe poder descargar paquetes de PyPI (salida a internet) sin tener IP pública.

Escribe las reglas de firewall necesarias y explica qué componente resuelve el punto 4.

Ejercicio 3 — Diagnóstico de un 502

Tras crear el balanceador (adelanto de la próxima lección), el catálogo devuelve 502 y en la consola los backends del MIG aparecen como UNHEALTHY. La aplicación responde correctamente si haces curl http://10.10.0.5:8080/salud desde otra VM de la misma subred.

Enumera las tres causas más probables por orden de probabilidad y el comando que usarías para confirmar o descartar cada una.


Soluciones

Solución 1

Plan propuesto (bloque 10.60.0.0/16 reservado en el documento de direccionamiento para desarrollo):

Uso Rango
sn-web-euw1-dev (primario) 10.60.0.0/24
sn-datos-euw1-dev (primario) 10.60.1.0/24
Secundario gke-pods-dev 10.70.0.0/16
Secundario gke-servicios-dev 10.71.0.0/20
Acceso privado a servicios 10.80.0.0/20

Ninguno solapa con 10.10/16, 10.20/16, 10.21/20, 10.30/20 ni con 192.168.10.0/24.

gcloud config set project alpinashop-dev

gcloud compute networks create alpinashop-vpc-dev \
  --subnet-mode=custom \
  --bgp-routing-mode=global

gcloud compute networks subnets create sn-web-euw1-dev \
  --network=alpinashop-vpc-dev \
  --region=europe-west1 \
  --range=10.60.0.0/24 \
  --secondary-range=gke-pods-dev=10.70.0.0/16,gke-servicios-dev=10.71.0.0/20 \
  --enable-private-ip-google-access

gcloud compute networks subnets create sn-datos-euw1-dev \
  --network=alpinashop-vpc-dev \
  --region=europe-west1 \
  --range=10.60.1.0/24 \
  --enable-private-ip-google-access

gcloud compute addresses create alpinashop-psa-range-dev \
  --global --purpose=VPC_PEERING \
  --addresses=10.80.0.0 --prefix-length=20 \
  --network=alpinashop-vpc-dev

Nota: los rangos secundarios se pueden dar en la creación con --secondary-range o añadirse después con --add-secondary-ranges.

Solución 2

# (1) SSH solo por IAP, aplicado a la VM de informes
gcloud compute firewall-rules create fw-allow-ssh-iap-informes \
  --network=alpinashop-vpc \
  --direction=INGRESS --action=allow --priority=1000 \
  --source-ranges=35.235.240.0/20 \
  --target-tags=informes \
  --rules=tcp:22

# (2) La replica de Cloud SQL vive en el rango de acceso privado a
#     servicios; el trafico saliente esta permitido por la regla
#     implicita de EGRESS, asi que no hace falta regla de entrada.
#     Si se quisiera restringir la salida de forma explicita:
gcloud compute firewall-rules create fw-egress-informes-sql \
  --network=alpinashop-vpc \
  --direction=EGRESS --action=allow --priority=900 \
  --destination-ranges=10.30.0.0/20 \
  --target-tags=informes \
  --rules=tcp:5432

# (3) No se crea ninguna regla de entrada desde internet ni desde la
#     capa web. La regla implicita "denegar toda la entrada" y la
#     explicita fw-deny-all-ingress (prioridad 65000) ya lo cubren.
#     Verificacion de que ninguna regla existente aplica a la etiqueta:
gcloud compute firewall-rules list \
  --filter="network:alpinashop-vpc AND targetTags:informes" \
  --format="table(name, direction, priority, sourceRanges.list())"

Punto 4: lo resuelve Cloud NAT. La pasarela alpinashop-nat-euw1 ya cubre sn-datos-euw1, de modo que la VM, sin IP pública, sale a internet para instalar desde PyPI. Además, para descargar del bucket alpinashop-catalogo o escribir en BigQuery no usará NAT, sino Private Google Access, que está activo en la subred.

En cuanto a "solo Lucía y Marta": el firewall no distingue personas. Esa parte se resuelve con IAM dando roles/iap.tunnelResourceAccessor y roles/compute.osLogin únicamente a los grupos correspondientes; se ve en 03-04.

Solución 3

Por orden de probabilidad:

  1. Falta la regla de firewall para los rangos de comprobación de estado. Es la causa más frecuente con diferencia. La comprobación se origina en 130.211.0.0/22 y 35.191.0.0/16, no en internet ni en la subred.
gcloud compute firewall-rules list \
  --filter="network:alpinashop-vpc AND sourceRanges:(130.211.0.0/22 OR 35.191.0.0/16)" \
  --format="table(name, allowed[].map().firewall_rule().list(), targetTags.list())"
  1. La etiqueta de red no coincide. La regla apunta a web-catalogo pero la plantilla de instancia del MIG no aplica esa etiqueta a las máquinas nuevas.
gcloud compute instances list --filter="name~alpinashop-web-mig" \
  --format="table(name, zone, tags.items.list(), status)"
  1. La comprobación apunta a un puerto o ruta equivocados. La aplicación escucha en 8080 y expone /salud, pero la comprobación está configurada en el 80 o en /.
gcloud compute health-checks describe hc-catalogo \
  --format="yaml(type, httpHealthCheck.port, httpHealthCheck.requestPath)"

Herramienta transversal: los logs de firewall de fw-deny-all-ingress mostrarán los intentos denegados desde 35.191.0.0/16 si la causa es la primera, y la prueba de conectividad de Network Intelligence Center confirma la regla exacta que bloquea.

Conclusión

AlpinaShop ya tiene una red de verdad. Has entendido la propiedad que define la red de Google —la VPC es global, las subredes son regionales— y por qué eso simplifica arquitecturas que en otros proveedores exigen malabares. Has descartado la red default con argumentos y has creado alpinashop-vpc en modo personalizado, con enrutamiento global y un plan de direccionamiento documentado que reserva 10.10.0.0/16 para producción, deja hueco para crecer y no choca con la oficina ni con alpinashop-dev.

Sobre esa red has creado sn-web-euw1 (10.10.0.0/24) y sn-datos-euw1 (10.10.1.0/24), con rangos secundarios gke-pods (10.20.0.0/16) y gke-servicios (10.21.0.0/20) para que alpinashop-cluster use red nativa de VPC y sus pods tengan direcciones reales. Has reservado la IP global alpinashop-lb-ip para el balanceador que construimos ahora mismo y la IP alpinashop-nat-ip para que la pasarela de pago pueda autorizar una dirección de salida estable.

Has aprendido el modelo de firewall completo —stateful, distribuido, prioridad donde gana el número menor, cuatro reglas implícitas, selección por etiqueta o por cuenta de servicio— y has escrito el conjunto real de AlpinaShop: HTTP y comprobaciones de estado solo desde 130.211.0.0/22 y 35.191.0.0/16, SSH solo desde el rango de IAP 35.235.240.0/20, PostgreSQL solo desde la cuenta de servicio sa-catalogo-web, y una denegación explícita con registro que te enseña qué está llamando a la puerta. Y sabes por qué 0.0.0.0/0 en el 22 no es una comodidad sino un incidente pendiente.

Por último, las máquinas ya no necesitan IP pública: Cloud NAT les da salida controlada con una IP estática conocida, Private Google Access las lleva a las APIs de Google sin pisar internet, el acceso privado a servicios ha puesto alpinashop-pedidos en una IP privada del rango 10.30.0.0/20, y los VPC Flow Logs convierten los problemas de red en consultas de log en lugar de conjeturas.

La red está lista, pero el catálogo todavía se sirve por la IP de una máquina. En la siguiente lección, 03-02, Balanceo de carga en la nube, ponemos delante un balanceador HTTP(S) externo global: una única dirección anycast que atiende desde el punto de presencia más cercano a cada cliente europeo, reparte el tráfico entre las instancias del MIG alpinashop-web-mig, sirve las imágenes directamente desde el bucket alpinashop-catalogo mediante un mapa de URL, y comprueba la salud de los backends por esa ruta /salud que ya hemos autorizado en el firewall. Todas las piezas de red que acabamos de colocar existen precisamente para que ese balanceador funcione a la primera.

Curso de Google Cloud Platform (GCP)

Módulo 1: Introducción a Google Cloud Platform

Módulo 2: Servicios principales de GCP

Módulo 3: Redes y seguridad

Módulo 4: Datos y análisis

Módulo 5: Aprendizaje automático e IA

Módulo 6: DevOps y monitoreo

Módulo 7: Temas avanzados de GCP

Módulo 8: Proyecto final

© Copyright 2026. Todos los derechos reservados