A 02-07 es va escriure una decisió i es va deixar pendent. DA-001: el catàleg web d'AlpinaShop ha de passar a Cloud Run. Es va justificar amb una taula, es va signar, i es va desar al README d'alpinashop-catalogo. Des de llavors el curs hi ha tornat una dotzena de vegades —al balancejador de 03-02, als secrets de 03-06, a Pub/Sub push de 04-04, a Cloud Functions de 06-03, a l'agent d'operacions de 06-04— sempre amb la mateixa cantarella: «a 07-02».
Aquesta és 07-02. Aquí es compleix.
No és un caprici de coherència narrativa: és que fins ara no hi havia les condicions. Moure el catàleg de producció a una altra plataforma sense un pipeline que construeixi imatges reproduïbles, sense observabilitat per saber si ha anat bé, sense infraestructura com a codi per revertir i sense un balancejador al davant que absorbeixi el canvi, és una temeritat. Amb les quatre coses fetes —mòduls 3 i 6— el moviment és una tarda de feina amb xarxa de seguretat.
En acabar la lliçó, el catàleg d'AlpinaShop servirà des de Cloud Run, darrere de la mateixa IP, el mateix domini, el mateix certificat, la mateixa CDN i el mateix WAF que tenia; haurà passat per un desplegament canari al 10 %; el MIG alpinashop-web-mig estarà apagat; i la factura de còmput del catàleg haurà baixat d'uns 50 € al mes a menys de 10. I sabràs per què la concurrència és el paràmetre que més gent configura malament.
Contingut
- Què és Cloud Run i per què importa el contracte del contenidor
- Serveis i treballs (jobs): el criteri per triar
- Desplegar el catàleg:
gcloud run deploybandera a bandera - El mateix servei en Terraform
- Revisions i repartiment de trànsit: canari, etiquetes i reversió
- Concurrència: el concepte que més es malinterpreta
- Escalat: zero, mínim, màxim i el model de facturació de CPU
- Connectar Cloud Run amb la resta de la plataforma
- Identitat i seguretat del servei
- Darrere del balancejador global: el NEG sense servidor
- La migració des del MIG, pas a pas i sense tall
- Cost: el model i el càlcul comparat
- Límits i quan Cloud Run no encaixa
- La taula madura: Cloud Run vs Functions vs GKE vs Compute Engine
- Apagar
alpinashop-web-migi el que això simplifica
- Què és Cloud Run i per què importa el contracte del contenidor
Cloud Run executa contenidors sense servidor. Li dones una imatge, et dona una URL HTTPS, i escala de zero a moltes instàncies segons el trànsit, cobrant pel que facis servir.
Dit així sona a App Engine amb un altre nom, i no ho és. La diferència és a la unitat de desplegament:
| Plataforma | Li lliures | Conseqüència |
|---|---|---|
| App Engine estàndard | Codi font + app.yaml |
Runtimes fixos, format propietari, poc portable |
| Cloud Functions | Una funció i la seva signatura | Un punt d'entrada; el runtime el posa Google |
| Cloud Run | Una imatge de contenidor | Qualsevol llenguatge, qualsevol binari, qualsevol dependència del sistema |
| GKE | Manifestos de Kubernetes | Control total i complexitat total |
Que la unitat sigui un contenidor estàndard és el que fa Cloud Run diferent i el que va resoldre el punt més delicat de DA-001: la portabilitat. La mateixa imatge que corre avui a alpinashop-cluster correrà demà a Cloud Run sense tocar-ne una línia. I si algun dia calgués sortir, aquesta imatge corre a qualsevol lloc on hi hagi un runtime de contenidors.
El contracte del contenidor
Cloud Run no executa qualsevol imatge de qualsevol manera: exigeix complir un contracte, i és deliberadament curt.
| Regla | Detall | Per què |
|---|---|---|
Escoltar a $PORT |
La variable d'entorn PORT (per defecte 8080), a 0.0.0.0 |
La plataforma tria el port i hi encamina |
| Respondre a HTTP/gRPC/WebSocket | El trànsit entra per la xarxa, no per esdeveniments locals | És una plataforma de peticions |
| Arrencar ràpid | Escoltar al port en segons, no en minuts | Cada arrencada en fred és latència per a un usuari |
| Sense estat al disc | El sistema de fitxers és efímer i viu a la memòria | Les instàncies neixen i moren sense avisar |
| Sense processos en segon pla fora de la petició | Tret que hi hagi CPU sempre activa | Per defecte la CPU es congela entre peticions |
| Contenidor Linux x86-64 o arm64 | Sense privilegis, sense accés al nucli | Aïllament multiinquilí |
Aquestes sis línies són tot. No cal aprendre cap format de desplegament nou, i aquesta és la raó per la qual Cloud Run s'ha convertit en l'opció per defecte per a aplicacions web a Google Cloud.
Dos errors freqüents contra el contracte, dits ja:
# MALAMENT: port fix i nomes escolta a localhost
app.run(host="127.0.0.1", port=5000)
# BE: el port el diu la plataforma i s escolta a totes les interficies
import os
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))El primer produeix l'error més comú dels principiants a Cloud Run: «The user-provided container failed to start and listen on the port defined provided by the PORT environment variable». Quan el vegis, ja saps què has de mirar.
Com funciona per dins
flowchart TB
U["Usuaris"] --> LB["Frontal HTTPS gestionat<br/>o balancejador global"]
LB --> CR["Cloud Run - servei alpinashop-web"]
subgraph CR
direction LR
R1["Revisió 0042<br/>90% del trànsit"]
R2["Revisió 0043<br/>10% del trànsit"]
end
R1 --> I1["Instància 1<br/>concurrència 80"]
R1 --> I2["Instància 2"]
R2 --> I3["Instància 3"]
I1 -.-> SQL["Cloud SQL<br/>alpinashop-pedidos"]
I1 -.-> SM["Secret Manager"]
I1 -.-> GCS["Bucket<br/>alpinashop-catalogo"]
Tres conceptes que cal fixar des d'ara:
- Servei: l'entitat estable, amb nom i URL.
alpinashop-web. - Revisió: una versió immutable del servei (imatge + configuració).
alpinashop-web-00042-abc. No es modifica mai: cada canvi crea una revisió nova. - Instància: un contenidor en execució d'una revisió. Neixen i moren soles.
La immutabilitat de les revisions és el que fa possible el repartiment de trànsit i la reversió instantània de l'apartat 5.
- Serveis i treballs (jobs): el criteri per triar
Cloud Run té dues maneres d'executar un contenidor, i triar malament és una font clàssica d'arquitectures rares.
| Servei (service) | Treball (job) | |
|---|---|---|
| Es dispara per | Una petició HTTP entrant | Una execució explícita (jobs execute, Scheduler, Workflows) |
Ha d'escoltar a $PORT |
Sí | No |
| Acaba | Mai (viu mentre hi hagi trànsit) | Quan el procés acaba amb codi 0 |
| Escala per | Peticions concurrents | --tasks: N tasques en paral·lel |
| Durada màxima | Fins a 60 minuts per petició | Fins a 24 hores per tasca |
| Reintents | Els fa el client | Integrats: --max-retries |
| Facturació | Per temps d'instància | Per temps de tasca |
La regla de decisió, en una frase:
Si alguna cosa respon a algú, és un servei. Si alguna cosa fa una cosa i acaba, és un treball.
Aplicat a AlpinaShop:
| Càrrega | Tipus | Per què |
|---|---|---|
| Catàleg web | Servei | Respon peticions de clients |
| API interna d'estoc | Servei | Respon al procés de sincronització |
| Informe nocturn de vendes | Treball | S'executa, produeix un fitxer, acaba |
| Reindexació del cercador | Treball | Procés per lots, 20 minuts, sense client esperant |
| Migració d'esquema de BD | Treball | Una execució, amb reintents i codi de sortida |
| Processar imatge pujada | Cloud Function (06-03) | Esdeveniment únic, ja resolt i funcionant |
L'antipatró clàssic —i es veu molt— és muntar l'informe nocturn com un servei amb un endpoint /ejecutar-informe que crida Cloud Scheduler. Funciona, però: tens un endpoint HTTP que cal protegir, l'execució està limitada pel temps màxim de petició, no hi ha reintents integrats, i si triga massa el procés es talla a mitges. Un treball elimina els quatre problemes.
Un treball real d'AlpinaShop, l'informe nocturn de 04-06:
gcloud run jobs create informes-nocturnos \
--image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/informes:v3 \
--region=europe-west1 \
--service-account=sa-informes-nocturnos@alpinashop-prod.iam.gserviceaccount.com \
--tasks=1 \
--max-retries=2 \
--task-timeout=30m \
--memory=1Gi \
--set-env-vars=DATASET=alpinashop_analiticaI la seva execució des de Cloud Scheduler, sense exposar cap HTTP propi:
gcloud scheduler jobs create http disparar-informes-nocturnos \
--location=europe-west1 \
--schedule="30 3 * * *" \
--time-zone="Europe/Madrid" \
--uri="https://run.googleapis.com/v2/projects/alpinashop-prod/locations/europe-west1/jobs/informes-nocturnos:run" \
--http-method=POST \
--oauth-service-account-email=sa-informes-nocturnos@alpinashop-prod.iam.gserviceaccount.comFixa't en --time-zone="Europe/Madrid": sense això, el 30 3 * * * és UTC i l'informe s'executarà a les 5:30 a l'estiu. És un error que es descobreix al març o a l'octubre i desconcerta durant hores.
- Desplegar el catàleg:
gcloud run deploy bandera a bandera
gcloud run deploy bandera a banderaLa imatge del catàleg ja és a Artifact Registry des de 06-01, construïda per Cloud Build i etiquetada amb $COMMIT_SHA. Només falta desplegar-la. Aquesta és la comanda completa d'AlpinaShop, i tot seguit cada bandera explicada.
gcloud run deploy alpinashop-web \
--image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
--region=europe-west1 \
--platform=managed \
--service-account=sa-catalogo-web@alpinashop-prod.iam.gserviceaccount.com \
--no-allow-unauthenticated \
--port=8080 \
--cpu=1 \
--memory=512Mi \
--concurrency=80 \
--min-instances=1 \
--max-instances=30 \
--timeout=60s \
--cpu-throttling \
--execution-environment=gen2 \
--set-env-vars=ENTORNO=prod,REGION=europe-west1 \
--set-secrets=DB_PASSWORD=db-password-catalogo:latest \
--add-cloudsql-instances=alpinashop-prod:europe-west1:alpinashop-pedidos \
--labels=entorno=prod,equipo=desarrollo,centro-coste=tienda,aplicacion=catalogo \
--no-traffic \
--tag=candidata| Bandera | Què fa | Per què aquest valor a AlpinaShop |
|---|---|---|
--image |
Imatge a desplegar | Etiquetada amb el hash del commit, mai latest (06-01). Amb latest no saps què hi ha corrent |
--region |
Regió del servei | europe-west1, on és tota la resta. Cloud Run és regional |
--platform=managed |
Cloud Run gestionat | L'alternativa era gke, en desús (07-01) |
--service-account |
Identitat del servei | El seu propi compte amb mínim privilegi (03-04). Sense això fa servir el compte per defecte de Compute, que és massa permissiu |
--no-allow-unauthenticated |
Exigeix IAM per invocar | El servei no s'exposa a internet directament: entrarà pel balancejador (apartat 10) |
--port |
Port del contenidor | 8080, el mateix del contracte i de la sonda hc-catalogo |
--cpu |
vCPU per instància | 1 vCPU. Es pot fraccionar (0.5, 0.25) o arribar a 8 |
--memory |
Memòria per instància | 512 MiB. Mesurada, no endevinada: veure apartat 6 |
--concurrency |
Peticions simultànies per instància | 80, el valor per defecte, validat mesurant (apartat 6) |
--min-instances |
Instàncies sempre enceses | 1, perquè cap client pateixi arrencada en fred |
--max-instances |
Sostre d'instàncies | 30, com a tallafoc de cost i de connexions a Cloud SQL |
--timeout |
Temps màxim per petició | 60 s. El màxim és 60 min, però un catàleg que trigui 60 s ja està trencat |
--cpu-throttling |
CPU només durant la petició | Mode per defecte i el més barat |
--execution-environment=gen2 |
Entorn d'execució | Segona generació: compatibilitat completa amb Linux, arrencada una mica més lenta, necessari per muntar volums |
--set-env-vars |
Variables d'entorn | Configuració no sensible |
--set-secrets |
Secrets muntats | La contrasenya mai com a variable en clar (03-06) |
--add-cloudsql-instances |
Connector a Cloud SQL | Habilita el sòcol Unix cap a alpinashop-pedidos |
--labels |
Etiquetes | Les quatre d'AlpinaShop des de 01-04, imprescindibles per a 07-05 |
--no-traffic |
Desplega sense enviar-li trànsit | Crea la revisió però no l'activa. Clau per al canari |
--tag=candidata |
Etiqueta de revisió | Li dona una URL pròpia per provar-la abans de donar-li trànsit |
Les dues últimes són les que converteixen un desplegament en un desplegament segur, i mereixen el seu propi apartat.
- El mateix servei en Terraform
AlpinaShop gestiona la seva infraestructura amb Terraform des de 06-07, així que la comanda anterior és per experimentar: el que va a main és HCL.
# entornos/prod/cloud_run_catalogo.tf
resource "google_cloud_run_v2_service" "catalogo" {
name = "alpinashop-web"
location = var.region # europe-west1
project = var.proyecto_prod
# Nomes transit intern i del balancejador: ningu no arriba per la URL run.app
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
template {
service_account = google_service_account.sa_catalogo_web.email
scaling {
min_instance_count = 1
max_instance_count = 30
}
max_instance_request_concurrency = 80
timeout = "60s"
containers {
image = "europe-west1-docker.pkg.dev/${var.proyecto_prod}/alpinashop/catalogo:${var.version_catalogo}"
ports {
container_port = 8080
}
resources {
limits = {
cpu = "1"
memory = "512Mi"
}
cpu_idle = true # CPU nomes durant la peticio
startup_cpu_boost = true # CPU extra durant l arrencada
}
env {
name = "ENTORNO"
value = "prod"
}
env {
name = "DB_PASSWORD"
value_source {
secret_key_ref {
secret = "db-password-catalogo"
version = "latest"
}
}
}
volume_mounts {
name = "cloudsql"
mount_path = "/cloudsql"
}
}
volumes {
name = "cloudsql"
cloud_sql_instance {
instances = [google_sql_database_instance.pedidos.connection_name]
}
}
}
# Repartiment de transit explicit: 100% a la ultima revisio estable
traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}
labels = {
entorno = "prod"
equipo = "desarrollo"
centro-coste = "tienda"
aplicacion = "catalogo"
}
}Quatre observacions importants sobre aquest HCL:
- Fes servir
google_cloud_run_v2_service, nogoogle_cloud_run_service. El recursv1continua existint per compatibilitat i la seva sintaxi (basada en anotacions de Knative) és molt pitjor. Tot el que és nou va env2. cpu_idle = trueés l'equivalent a--cpu-throttling: la CPU només es factura durant les peticions.falsesignifica CPU sempre activa, i és fins a quatre vegades més car.startup_cpu_boost = truedona CPU addicional durant l'arrencada. Redueix l'arrencada en fred entre un 20 % i un 50 % en aplicacions amb inicialització pesada (JVM, frameworks grans), i només es paga aquell mig segon.ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"és la peça de seguretat: ningú no pot arribar al servei per la seva URL*.run.app. Només hi entra trànsit des del balancejador global.
I hi ha una tensió que convé resoldre explícitament, perquè és la pregunta que tothom es fa en arribar aquí: si el desplegament el fa el pipeline, què gestiona Terraform?
| Aspecte | Qui ho gestiona | Motiu |
|---|---|---|
| Existència del servei, compte de servei, ingress, secrets, escalat, VPC | Terraform | És la forma; canvia per decisió humana |
| Versió de la imatge desplegada | Cloud Build | Canvia diverses vegades al dia; és contingut |
| Repartiment de trànsit entre revisions | Cloud Build durant el canari, Terraform en repòs | És una operació, no una configuració |
És exactament la regla de 06-07: Terraform gestiona la forma, no el contingut. A la pràctica es resol amb lifecycle { ignore_changes = [template[0].containers[0].image, traffic] } al recurs, perquè Terraform no intenti tornar la imatge a la versió del seu fitxer cada vegada que corre.
- Revisions i repartiment de trànsit: canari, etiquetes i reversió
Aquí hi ha una de les millors capacitats de Cloud Run, i és pràcticament gratis.
Etiquetes de revisió: provar sense arriscar
En desplegar amb --no-traffic --tag=candidata, Cloud Run crea la revisió i li assigna una URL pròpia:
Aquesta URL apunta només a aquella revisió, amb zero trànsit de producció. Serveix per a:
- Provar la versió nova contra la base de dades real, sense que cap client la vegi.
- Passar les proves de fum automàtiques del pipeline.
- Ensenyar-la a algú abans de publicar-la.
# Prova de fum contra la revisio candidata, amb testimoni perque el servei es privat
URL=$(gcloud run services describe alpinashop-web --region=europe-west1 \
--format='value(status.traffic[?tag==`candidata`].url)')
curl -sf -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"$URL/salud" || { echo "FALLA LA SONDA"; exit 1; }El canari al 10 %
Si la prova passa, se li dona un percentatge petit de trànsit real:
A partir d'aquell moment, una de cada deu peticions va a la revisió nova. S'observa durant vint o trenta minuts al tauler de 06-04 —taxa d'error, latència p95, errors a Error Reporting— i es decideix.
Progressió completa recomanada:
| Pas | Trànsit | Què s'observa | Durada |
|---|---|---|---|
| 1 | 0 % (només etiqueta) | Proves de fum | 2 min |
| 2 | 10 % | Taxa d'error 5xx, latència p95 | 20-30 min |
| 3 | 50 % | El mateix, amb volum | 30-60 min |
| 4 | 100 % | Estabilització | — |
gcloud run services update-traffic alpinashop-web --region=europe-west1 --to-tags=candidata=50
gcloud run services update-traffic alpinashop-web --region=europe-west1 --to-latestLa reversió instantània
I aquesta és la part que canvia com es dorm a les nits. Si la revisió nova falla:
# Veure les revisions i el seu repartiment actual
gcloud run revisions list --service=alpinashop-web --region=europe-west1
# Tornar TOT el transit a la revisio anterior coneguda com a bona
gcloud run services update-traffic alpinashop-web \
--region=europe-west1 \
--to-revisions=alpinashop-web-00041-xyz=100Triga segons i no reconstrueix ni redesplega res, perquè la revisió anterior continua existint íntegra. Compara-ho amb el MIG: calia crear una plantilla d'instància nova, llançar una actualització progressiva i esperar que les VM se substituïssin una a una — entre cinc i quinze minuts, amb el lloc degradat mentrestant.
Consell operatiu: tingues la comanda de reversió escrita al runbook (07-06) amb el número de revisió emplenat com a variable. A les tres de la matinada ningú no recorda la sintaxi de
--to-revisions.
Dues advertències sobre el canari
- El canari no protegeix contra canvis d'esquema de base de dades. Si la revisió nova fa
ALTER TABLE, el 90 % que continua a la vella també veu la taula canviada. Les migracions d'esquema han de ser compatibles cap enrere (afegir columnes, mai reanomenar-les al mateix desplegament), i executar-se com un treball a part. - Amb poc trànsit, el 10 % no significa res estadísticament. Si AlpinaShop rep 30 peticions per minut fora de campanya, el 10 % són 3 peticions. Un canari que no veu errors durant 20 minuts amb 60 peticions no ha demostrat gran cosa. En volums baixos convé pujar el percentatge i allargar el temps, o fer el canari en hores de més trànsit.
- Concurrència: el concepte que més es malinterpreta
Si t'emportes un sol apartat d'aquesta lliçó, que sigui aquest.
Concurrència és el nombre màxim de peticions que Cloud Run envia simultàniament a una mateixa instància abans d'arrencar-ne una altra.
El valor per defecte és 80. I aquí hi ha la font de tots els malentesos: molta gent arriba a Cloud Run des de Cloud Functions o des d'AWS Lambda, on el model és una petició per instància, i assumeix que Cloud Run funciona igual. No.
Comparació honesta dels dos models
Una petició per instància (--concurrency=1) |
Concurrència alta (--concurrency=80) |
|
|---|---|---|
| Aïllament | Total: cada petició té el seu contenidor | Compartit: 80 peticions comparteixen memòria i CPU |
| Raonament | Trivial: no hi ha concurrència per gestionar | Cal pensar en variables globals i estat compartit |
| Cost | Molt alt: una instància per petició | Baix: 80 peticions paguen una instància |
| Arrencades en fred | Moltíssimes: cada pic crea instàncies | Poques |
| Rendiment en E/S | Dolentíssim: la CPU està ociosa esperant la BD | Excel·lent: mentre una petició espera, una altra fa servir la CPU |
| Aplica bé a | Càrregues de CPU intensiva, codi no segur per a fils, ML | Aplicacions web i API normals |
La clau és a la penúltima fila. Una petició típica del catàleg d'AlpinaShop passa així els seus 200 ms:
|-- 5 ms --|------------ 170 ms ------------|-- 25 ms --| analitzar esperant a Cloud SQL renderitzar (CPU) (CPU OCIOSA) (CPU)
El 85 % del temps la CPU no fa res: espera la base de dades. Amb --concurrency=1, pagues una vCPU sencera perquè estigui ociosa 170 ms de cada 200. Amb --concurrency=80, aquesta mateixa vCPU atén altres peticions durant aquesta espera.
Com triar-la mesurant
La concurrència correcta no s'endevina, es mesura. El procediment:
Pas 1 — Esbrinar el model de concurrència de la teva aplicació. És el primer i gairebé ningú no ho comprova:
| Servidor | Model | Concurrència útil |
|---|---|---|
Flask amb app.run() (desenvolupament) |
Un fil | 1. No posis això en producció |
| Gunicorn amb workers síncrons | workers × 1 |
Igual al nombre de workers |
Gunicorn amb gthread |
workers × threads |
Aquest producte |
| Node.js / Express | Bucle d'esdeveniments | Alta: 80-250 |
| Go / Java amb fils | Alta | 80-1000 |
Python amb asyncio (FastAPI + Uvicorn) |
Bucle d'esdeveniments | Alta: 80-250 |
L'error més car de tots: posar
--concurrency=80en un servidor que només processa una petició alhora. Cloud Run n'hi envia 80, el servidor les encua, i les peticions 2 a 80 esperen el seu torn. La latència es dispara i sembla un problema de la plataforma. És un problema de configuració del servidor d'aplicacions.
Per al catàleg d'AlpinaShop, que és Flask, el Dockerfile acaba així:
# 4 processos x 20 fils = 80 peticions simultanies reals
CMD exec gunicorn --bind :$PORT \
--workers 4 \
--threads 20 \
--worker-class gthread \
--timeout 0 \
main:app--timeout 0 desactiva el temporitzador de Gunicorn perquè el temps d'espera el governa Cloud Run amb --timeout=60s. Tenir dos temporitzadors competint produeix talls inexplicables.
Pas 2 — Prova de càrrega amb valors diferents. Es despleguen revisions amb etiqueta i es mesura cadascuna:
for C in 1 10 40 80 160; do
gcloud run deploy alpinashop-web \
--image=$IMAGEN --region=europe-west1 \
--concurrency=$C --no-traffic --tag=c$C
doneI amb una eina de càrrega (hey, k6, vegeta) contra cada URL etiquetada. Resultats reals del catàleg, amb 50 peticions per segon sostingudes durant 5 minuts:
| Concurrència | Instàncies usades | Latència p50 | Latència p95 | Cost relatiu |
|---|---|---|---|---|
| 1 | 12 | 190 ms | 240 ms | 12× |
| 10 | 5 | 195 ms | 260 ms | 5× |
| 40 | 2 | 200 ms | 290 ms | 2× |
| 80 | 1 | 210 ms | 340 ms | 1× |
| 160 | 1 | 380 ms | 1200 ms | 1× |
Lectura de la taula, que és on hi ha l'aprenentatge:
- D'1 a 80, la latència p50 amb prou feines puja (190 → 210 ms) i el cost es divideix per dotze. Regal evident.
- De 80 a 160 la latència es dispara: s'han superat els 80 fils reals de Gunicorn i les peticions s'encuen. El sostre no el posa Cloud Run, el posa la teva aplicació.
- El p95 es degrada abans que el p50. Per això es mesura el p95: és on es veu el patiment abans que sigui evident.
Pas 3 — Ajustar CPU i memòria a la concurrència triada. Són paràmetres acoblats:
- Amb concurrència 80, la instància necessita memòria per a 80 peticions simultànies. Si cadascuna consumeix 4 MiB d'estructures, són 320 MiB només de peticions, més el runtime.
- Si l'aplicació és intensiva en CPU, la concurrència alta fa que les peticions competeixin per la mateixa vCPU. Aquí es puja CPU o es baixa concurrència.
El senyal que la memòria es queda curta és inconfusible als registres:
Quan això passa, la instància mor i les peticions en curs es perden. És de les poques fallades de Cloud Run que es manifesten com a errors 5xx sense traça de l'aplicació.
- Escalat: zero, mínim, màxim i el model de facturació de CPU
Escalar a zero
Sense trànsit, Cloud Run apaga totes les instàncies i no cobra res. És la propietat que fa que un entorn de desenvolupament costi cèntims.
El preu d'aquesta propietat és l'arrencada en fred (cold start): la primera petició després d'un període d'inactivitat ha d'esperar que es creï una instància, es descarregui la imatge i arrenqui l'aplicació.
| Fase | Temps típic | Com es redueix |
|---|---|---|
| Aprovisionar el sandbox | 100-300 ms | No està a la teva mà |
| Descarregar la imatge | 200 ms - 5 s | Imatge petita. És la palanca principal |
| Arrencar l'aplicació | 100 ms - 10 s | Menys dependències, càrrega mandrosa, startup_cpu_boost |
Números orientatius d'arrencada en fred completa: un binari de Go en una imatge distroless de 15 MB arrenca en uns 300 ms; una aplicació Python amb moltes dependències, en 1-3 s; una JVM amb Spring Boot sense ajustar, entre 8 i 20 s. La diferència la marca la imatge i el runtime, no Cloud Run.
--min-instances: pagar per no tenir fred
Manté N instàncies sempre vives. La instància inactiva es factura a una tarifa reduïda —de l'ordre de la desena part de l'activa—, així que una instància mínima és barata: uns pocs euros al mes.
Criteri per a AlpinaShop:
| Servei | --min-instances |
Raó |
|---|---|---|
alpinashop-web (prod) |
1 | Un client que arriba a les 7:00 no ha d'esperar 2 s |
alpinashop-web (dev) |
0 | Ningú no pateix una arrencada en fred en desenvolupament |
| Serveis interns | 0 | Els crida programari, que pot esperar |
| Durant la campanya de tardor | 3 | Esmorteir l'arrencada del matí |
Advertiment important: --min-instances redueix molt les arrencades en fred, però no les elimina. Si el trànsit puja de cop, cal crear instàncies noves i aquestes arrenquen en fred igualment. I hi ha un detall contraintuïtiu: amb CPU limitada a les peticions (cpu_idle=true), les instàncies mínimes tenen la CPU congelada entre peticions, així que si la teva aplicació fa alguna cosa periòdica en segon pla, no s'executarà.
--max-instances: el tallafoc de cost
Això és protecció, i cal posar-ho sempre. Dos motius:
- Cost. Un bucle infinit al codi, un rastrejador agressiu o un atac poden llançar centenars d'instàncies. Sense sostre, la factura del dia és un incident.
- Dependències aigües avall. Cloud SQL té un límit de connexions. Si cada instància obre 5 connexions i arrenquen 200 instàncies, són 1000 connexions contra una base de dades que admet 100: Cloud Run escala perfectament i tomba la base de dades.
La fórmula per triar-lo:
max-instances = min(
capacitat_de_la_dependencia_mes_feble / connexions_per_instancia,
pic_de_transit_esperat / concurrencia * marge
)Per a AlpinaShop: Cloud SQL admet ~100 connexions útils, el pool per instància és de 3 → 33 instàncies. El pic esperat en campanya són 20 req/s, amb concurrència 80 → menys d'1 instància, amb marge ×5 per a pics bruscos → 5. Es tria 30: còmode per al trànsit, per sota del límit de la base de dades.
Quan s'assoleix el màxim, Cloud Run no rebutja: encua les peticions i les va servint, i només retorna 429 si la cua se satura. És un comportament amable, però convé tenir una alerta:
gcloud monitoring channels list # el canal de la Marta, de 06-04
# Alerta quan se supera el 80% del maxim durant 5 minutsCPU sempre activa davant de CPU durant la petició
--cpu-throttling (per defecte) |
--no-cpu-throttling |
|
|---|---|---|
| CPU entre peticions | Congelada | Disponible |
| Preu de la vCPU | Base | De l'ordre de 4 vegades menor per segon, però es paga tot el temps de vida de la instància |
| Processos en segon pla | No funcionen | Funcionen |
| Casos d'ús | Aplicacions web normals | Consumidors de streaming, serveis amb memòria cau que es refresca sola, WebSockets amb feina entre missatges |
Per al catàleg, --cpu-throttling és el correcte: entre peticions no hi ha res a fer. El parany clàssic és llançar un fil en segon pla per «enviar mètriques cada 30 segons» i descobrir que no s'executa mai. Amb CPU congelada, aquest fil s'atura.
- Connectar Cloud Run amb la resta de la plataforma
Un servei aïllat no val res. Aquestes són les cinc connexions d'AlpinaShop.
Cloud SQL: per connector, no per IP
Cloud Run munta un sòcol Unix a /cloudsql/<nom-de-connexió> que parla amb la instància per un canal xifrat, sense IP pública i sense obrir tallafoc. La cadena de connexió de l'aplicació:
import os, sqlalchemy
CONEXION = "alpinashop-prod:europe-west1:alpinashop-pedidos"
engine = sqlalchemy.create_engine(
sqlalchemy.engine.url.URL.create(
drivername="postgresql+pg8000",
username="catalogo",
password=os.environ["DB_PASSWORD"], # injectada des de Secret Manager
database="tienda",
query={"unix_sock": f"/cloudsql/{CONEXION}/.s.PGSQL.5432"},
),
pool_size=3, # baix: multiplica pel nombre d instancies
max_overflow=0, # sense connexions extra sorpresa
pool_pre_ping=True, # detecta connexions mortes despres d una pausa
pool_recycle=1800,
)Les quatre opcions del pool no són decoratives:
pool_size=3: recorda la fórmula de--max-instances. Un pool de 20 per instància és la recepta per esgotar la base de dades.max_overflow=0: impedeix que el pool obri connexions addicionals sota càrrega, que és justament quan no t'ho pots permetre.pool_pre_ping=True: imprescindible a Cloud Run. Una instància amb la CPU congelada durant minuts torna amb connexions mortes; sense pre-ping, la primera petició després de la pausa falla.pool_recycle=1800: recicla connexions cada mitja hora abans que les talli el servidor.
L'alternativa moderna és connectar per IP privada a través de la VPC (punt següent), que evita el connector i dona menys latència. És el que AlpinaShop farà quan la xarxa estigui ordenada a 07-03.
Secret Manager: muntat, no copiat
L'aplicació llegeix os.environ["DB_PASSWORD"] i no sap que ve de Secret Manager. Alternativa, muntar-lo com a fitxer:
Diferència pràctica que convé conèixer: una variable d'entorn es llegeix una vegada en arrencar la instància; un fitxer muntat es pot rellegir. Si rotes un secret, les instàncies vives amb variable d'entorn continuen fent servir el valor vell fins que se substitueixin. Amb :latest i fitxers, la rotació es propaga sola.
I la contrapartida: fer servir :latest significa que una rotació mal feta trenca el servei sense que ningú desplegui res. Per a secrets crítics, fixar la versió (:7) i actualitzar-la en un desplegament controlat és més segur.
VPC directa: sortir per la xarxa privada
Per defecte, el trànsit sortint de Cloud Run va a internet. Per arribar a recursos amb IP privada —una rèplica de Cloud SQL, Memorystore, o l'ERP de Sabadell a l'altra banda del túnel de 07-03— cal connectar el servei a la VPC:
gcloud run services update alpinashop-web \
--region=europe-west1 \
--network=alpinashop-vpc \
--subnet=sn-web-euw1 \
--vpc-egress=private-ranges-onlyAixò és VPC directa (Direct VPC egress), que substitueix l'antic connector d'accés a VPC sense servidor (Serverless VPC Access connector):
| Connector (antic) | VPC directa (actual) | |
|---|---|---|
| Infraestructura | VM gestionades que cal dimensionar i pagar | Cap |
| Cost | De l'ordre de desenes de € al mes | Sense cost addicional |
| Latència | Un salt extra | Directa |
| Escalat | Manual (nombre d'instàncies del connector) | Automàtic |
Si trobes documentació que parla de connectors, és anterior. VPC directa és el que cal fer servir avui.
El valor de --vpc-egress decideix què surt per la VPC:
| Valor | Efecte |
|---|---|
private-ranges-only |
Només els rangs RFC 1918 van per la VPC; internet surt directe. L'habitual |
all-traffic |
Tot surt per la VPC, inclòs internet. Necessari si vols que surti pel Cloud NAT alpinashop-nat-euw1 amb IP fixa (03-01), per exemple perquè la passarel·la de pagament té llista blanca d'IP |
Pub/Sub push amb OIDC
A 04-04 es va dir que el model push encaixa amb Cloud Run. Així es connecta, sense obrir el servei a ningú:
gcloud pubsub subscriptions create pedidos-nuevos-a-catalogo \
--topic=pedidos-nuevos \
--push-endpoint=https://alpinashop-web-x7fq2abc-ew.a.run.app/eventos/pedido \
--push-auth-service-account=sa-pubsub-invoker@alpinashop-prod.iam.gserviceaccount.com \
--ack-deadline=60 \
--dead-letter-topic=pedidos-nuevos-dlq \
--max-delivery-attempts=5L'essencial és --push-auth-service-account: Pub/Sub signa cada lliurament amb un testimoni OIDC d'aquell compte. El servei, que està en --no-allow-unauthenticated, només accepta la petició si aquell compte té roles/run.invoker:
gcloud run services add-iam-policy-binding alpinashop-web \
--region=europe-west1 \
--member=serviceAccount:[email protected] \
--role=roles/run.invokerCap secret compartit, cap capçalera màgica, cap comprovació al codi. La plataforma valida el testimoni abans que la petició arribi al teu contenidor.
Eventarc: reaccionar al que passa a GCP
Eventarc lliura a Cloud Run esdeveniments de més d'un centenar de serveis de Google Cloud, amb el format estàndard CloudEvents:
gcloud eventarc triggers create catalogo-imagen-nueva \
--location=europe-west1 \
--destination-run-service=alpinashop-web \
--destination-run-path=/eventos/imagen \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=alpinashop-catalogo" \
--service-account=sa-eventarc@alpinashop-prod.iam.gserviceaccount.comLa relació amb el que ja s'ha vist: Cloud Functions 2a generació (06-03) està construïda sobre Cloud Run i Eventarc. La funció procesar-imagen-producto és, per sota, un servei de Cloud Run amb un disparador d'Eventarc. Saber-ho explica per què comparteixen límits, opcions d'escalat i model de facturació.
- Identitat i seguretat del servei
Quatre decisions, totes coherents amb el mòdul 3.
1. Compte de servei propi i mínim. sa-catalogo-web té exactament el que necessita i res més:
# Llegir el secret de la contrasenya
gcloud secrets add-iam-policy-binding db-password-catalogo \
--member=serviceAccount:[email protected] \
--role=roles/secretmanager.secretAccessor
# Connectar-se a Cloud SQL
gcloud projects add-iam-policy-binding alpinashop-prod \
--member=serviceAccount:[email protected] \
--role=roles/cloudsql.client
# Llegir imatges del bucket del cataleg
gcloud storage buckets add-iam-policy-binding gs://alpinashop-catalogo \
--member=serviceAccount:[email protected] \
--role=roles/storage.objectViewerSi no s'especifica --service-account, Cloud Run fa servir el compte de servei per defecte de Compute Engine, que històricament té el rol Editor sobre tot el projecte. Desplegar així és donar a la teva aplicació web permís per esborrar la base de dades. És la fallada de seguretat més comuna de tot Cloud Run.
2. --no-allow-unauthenticated. El servei exigeix un testimoni d'identitat vàlid d'algú amb roles/run.invoker. Qui pot invocar-lo:
| Invocador | Com |
|---|---|
| El balancejador global | El NEG sense servidor invoca sense testimoni; es controla amb ingress |
| Pub/Sub | Testimoni OIDC de sa-pubsub-invoker |
| Un altre servei de Cloud Run | Testimoni OIDC del seu propi compte |
| Una persona (per depurar) | curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" |
| Un usuari final autenticat amb Google | IAP davant del balancejador |
3. Control d'entrada (ingress). És diferent de l'autenticació i complementari:
| Valor | Qui pot arribar |
|---|---|
all |
Tot internet (la URL run.app és pública) |
internal |
Només des de la VPC, VPC Service Controls i altres serveis del projecte |
internal-and-cloud-load-balancing |
L'anterior més el balancejador global. El d'AlpinaShop |
Amb internal-and-cloud-load-balancing, la URL *.run.app deixa de respondre des de fora. Tot el trànsit de clients ha de passar per Cloud Armor i la CDN, sense excepció. Això és el que impedeix la drecera de «provaré directament contra run.app», que és el forat pel qual se salta el WAF.
4. IAP per a serveis interns. El tauler d'administració intern es publica amb el mateix patró de 03-04: balancejador + IAP + Cloud Run privat. Els empleats entren amb el seu compte de Google, sense VPN i sense usuaris propis.
- Darrere del balancejador global: el NEG sense servidor
A 03-02 es va muntar el balancejador global d'AlpinaShop i es va deixar dit que el dia que el catàleg anés a Cloud Run n'hi hauria prou amb canviar el backend. Aquell dia ha arribat.
Un NEG sense servidor (serverless network endpoint group) és un backend que apunta a un servei de Cloud Run, App Engine o Cloud Functions en lloc de a un grup d'instàncies.
flowchart LR
C["Clients"] --> IP["alpinashop-lb-ip<br/>IP anycast global"]
IP --> CERT["alpinashop-cert<br/>TLS gestionat"]
CERT --> ARM["Cloud Armor<br/>pol-catalogo-web"]
ARM --> UM["alpinashop-url-map"]
UM -->|"/*"| BS["bs-catalogo-web<br/>+ Cloud CDN"]
UM -->|"/img/*"| BB["bb-catalogo-imagenes<br/>bucket"]
BS --> NEG["NEG sense servidor<br/>neg-catalogo-run"]
NEG --> CR["Cloud Run<br/>alpinashop-web"]
style NEG fill:#e6f4ea,stroke:#34a853
style CR fill:#e6f4ea,stroke:#34a853
Tot el blau del diagrama —IP, certificat, WAF, mapa d'URL, CDN— no es toca. Només canvia el verd.
Creació del NEG:
gcloud compute network-endpoint-groups create neg-catalogo-run \
--region=europe-west1 \
--network-endpoint-type=serverless \
--cloud-run-service=alpinashop-webI la seva incorporació al servei de backend existent:
gcloud compute backend-services add-backend bs-catalogo-web \
--global \
--network-endpoint-group=neg-catalogo-run \
--network-endpoint-group-region=europe-west1Quatre particularitats del NEG sense servidor que cal conèixer, perquè són diferents de tot el que s'ha vist a 03-02:
- No hi ha comprovacions d'estat. El servei de backend amb NEG sense servidor no fa servir
hc-catalogo. La salut la gestiona Cloud Run. Al principi desconcerta; és correcte. - No hi ha mode de balanceig ni capacitat. No existeixen
max-rate-per-instancenicapacity-scaler: Cloud Run escala sol. - El NEG és regional, encara que el balancejador sigui global. Per servir des de diverses regions es creen diversos NEG, un per regió, i s'afegeixen al mateix servei de backend — que és exactament com es fa l'alta disponibilitat multiregió de 07-06.
- La CDN continua funcionant igual. Les capçaleres
Cache-Controlque emet Cloud Run governen la memòria cau exactament com ho feien les del MIG (03-03).
- La migració des del MIG, pas a pas i sense tall
Aquest és el procediment real que va executar la Marta. Està escrit per poder seguir-lo, i la seva virtut és que en cap moment hi ha un punt sense retorn.
Fase 0 — Preparació (dies abans)
# 1. Verificar que la imatge compleix el contracte, en local
docker run -e PORT=8080 -p 8080:8080 \
europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2
curl -sf localhost:8080/salud && echo "CONTRACTE OK"
# 2. Desplegar en desenvolupament primer. SEMPRE.
gcloud run deploy alpinashop-web --project=alpinashop-dev \
--image=... --region=europe-west1 --min-instances=0I una llista de comprovació prèvia que evita el 90 % dels ensurts:
- [ ] L'aplicació escolta a
$PORTi a0.0.0.0. - [ ] No escriu res important al disc local.
- [ ] No desa sessions a la memòria (les d'AlpinaShop són a Firestore des de 02-06).
- [ ] Els registres van a
stdout/stderren JSON estructurat (06-06). - [ ] Gunicorn està configurat amb fils suficients per a la concurrència triada.
- [ ] El pool de connexions és petit i té
pool_pre_ping. - [ ] No hi ha fils de fons que depenguin de CPU entre peticions.
Fase 1 — Desplegar en producció sense trànsit
gcloud run deploy alpinashop-web \
--project=alpinashop-prod \
--image=europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo:a3f91c2 \
--region=europe-west1 \
--no-traffic --tag=candidata \
[... la resta de banderes de l apartat 3 ...]El servei existeix, funciona contra la base de dades real i cap client no el veu. Aquí es fan les proves de fum i es compara la latència amb la del MIG.
Fase 2 — Afegir el NEG al balancejador amb pes zero
Es crea el NEG i s'afegeix al servei de backend bs-catalogo-web, que ara té dos backends: el MIG i Cloud Run. El balancejador reparteix entre ells segons capacitat.
Per controlar el repartiment amb precisió, AlpinaShop fa servir la tècnica del capacity-scaler sobre el backend del MIG:
# Estat inicial: tot al MIG
gcloud compute backend-services update-backend bs-catalogo-web --global \
--instance-group=alpinashop-web-mig \
--instance-group-zone=europe-west1-b \
--capacity-scaler=1.0Fase 3 — El transvasament progressiu
Es va reduint la capacitat del MIG, cosa que empeny trànsit cap a Cloud Run:
| Moment | capacity-scaler del MIG |
Trànsit aproximat a Cloud Run | Què es vigila |
|---|---|---|---|
| T+0 | 1.0 | ~0 % | — |
| T+30 min | 0.5 | ~30 % | 5xx, p95, connexions a Cloud SQL |
| T+2 h | 0.2 | ~70 % | El mateix, més cost |
| T+1 dia | 0.0 | 100 % | Tot, durant 24 h |
A cada esglaó es miren les mateixes quatre coses al tauler de 06-04, i es comparen per backend:
# A l explorador de metriques, filtrant per backend_target_name loadbalancing.googleapis.com/https/request_count -> repartiment real loadbalancing.googleapis.com/https/backend_latencies -> p50 i p95 per backend run.googleapis.com/container/instance_count -> instancies creades cloudsql.googleapis.com/database/postgresql/num_backends -> connexions obertes
La mètrica de connexions a Cloud SQL és la que més sorpreses dona, i és el motiu pel qual el transvasament es fa en hores i no en minuts.
Fase 4 — Convivència vigilada
Durant 24-72 hores el MIG continua existint amb capacity-scaler=0.0: no rep trànsit però està encès. La reversió és una sola comanda:
gcloud compute backend-services update-backend bs-catalogo-web --global \
--instance-group=alpinashop-web-mig \
--instance-group-zone=europe-west1-b \
--capacity-scaler=1.0I en segons torna tot al MIG. Pagar dos dies de VM apagada de trànsit és el preu de dormir tranquil, i és un preu ridícul.
Fase 5 — Retirada
Només quan han passat els tres dies i s'ha travessat almenys un pic de trànsit:
# 1. Treure el MIG del balancejador
gcloud compute backend-services remove-backend bs-catalogo-web --global \
--instance-group=alpinashop-web-mig \
--instance-group-zone=europe-west1-b
# 2. Reduir el MIG a zero abans d esborrar-lo (permet tornar rapid)
gcloud compute instance-groups managed resize alpinashop-web-mig \
--size=0 --zone=europe-west1-b
# 3. Una setmana despres, esborrar de debo, des de Terraform
# (eliminar el recurs del .tf i fer plan/apply, mai a ma)El pas 2 és important: reduir a zero abans d'esborrar. Un MIG de mida zero no costa còmput, conserva la seva plantilla, i tornar-lo a aixecar és un resize. L'esborrat definitiu es fa des de Terraform, perquè si es fa a mà el plan següent intentarà recrear-lo.
- Cost: el model i el càlcul comparat
El model de facturació
Cloud Run cobra tres coses, i només mentre hi ha instàncies vives:
| Concepte | Unitat | Ordre de magnitud a europe-west1 |
|---|---|---|
| CPU | vCPU-segon | ~0,000024 $ per vCPU-s activa; la instància mínima ociosa, al voltant d'una desena part |
| Memòria | GiB-segon | ~0,0000025 $ per GiB-s |
| Peticions | Per milió | ~0,40 $ per milió |
| Sortida a internet | GB | Tarifa estàndard d'egress (07-05) |
I hi ha un nivell gratuït mensual generós —de l'ordre de 180.000 vCPU-s, 360.000 GiB-s i 2 milions de peticions— que fa que molts projectes petits costin literalment zero.
Els preus canvien i varien per regió. Consulta sempre la pàgina de preus oficial i la calculadora. El que segueix són ordres de magnitud per raonar, no un pressupost.
El càlcul d'AlpinaShop
Escenari: 1,5 milions de peticions al mes, latència mitjana 200 ms, 1 vCPU, 512 MiB, concurrència 80, min-instances=1, CPU limitada a la petició.
Temps d'instància activa. Amb concurrència 80, les peticions s'encavalquen. Una estimació conservadora del temps amb almenys una petició en vol, a partir de la distribució horària real, dona unes 28 hores al mes d'activitat ≈ 100.000 vCPU-s.
| Concepte | Càlcul | Cost |
|---|---|---|
| CPU activa | 100.000 vCPU-s × 0,000024 $ | ~2,40 $ |
| CPU ociosa (min-instances=1) | 2.592.000 s × ~0,0000025 $ | ~6,50 $ |
| Memòria activa | 50.000 GiB-s × 0,0000025 $ | ~0,13 $ |
| Memòria ociosa | 1.296.000 GiB-s × ~0,00000025 $ | ~0,32 $ |
| Peticions | 1,5 M × 0,40 $/M | ~0,60 $ |
| Menys nivell gratuït | −2 a −3 $ | |
| Total | ≈ 7-8 $/mes ≈ 7 €/mes |
Comparació amb el que es venia pagant, fent servir les xifres de 02-07:
| Concepte | MIG (abans) | Cloud Run (després) |
|---|---|---|
| Còmput | 2 × e2-medium 24×7 ≈ 50 €/mes |
≈ 7 €/mes |
| Balancejador global + IP | ~20 €/mes | ~20 €/mes (igual) |
| Discos persistents | ~4 €/mes | 0 € |
| Agent d'operacions | Inclòs, però cal instal·lar-lo | No cal |
| Total infraestructura | ≈ 74 €/mes | ≈ 27 €/mes |
| Apedaçament del SO, plantilles, actualitzacions progressives | ~2-4 h/mes de la Marta | 0 h |
Estalvi: uns 47 €/mes, un 63 % del cost de la botiga. I aquesta és la part petita. Les 2-4 hores mensuals de la Marta valen més que els 47 €, i el temps de reversió passa de 10 minuts a 5 segons.
Però cal dir la veritat completa, perquè aquest curs no ven:
- Si el trànsit fos constant i alt les 24 hores, el MIG amb descompte per ús compromès seria més barat. Cloud Run guanya amb trànsit irregular, que és el cas d'AlpinaShop, no tots els casos.
- En campanya de tardor la factura de Cloud Run puja, perquè es paga l'ús. És el correcte —es paga quan es ven— però significa que la factura ja no és plana. Es planifica a 07-05.
- El balancejador global continua costant el mateix i ara representa el 74 % de la factura del catàleg. És el que passa quan s'optimitza bé: el major cost es desplaça a una altra banda.
- Límits i quan Cloud Run no encaixa
Els límits que cal conèixer abans de decidir (verifica'ls a la documentació, canvien a millor amb el temps):
| Límit | Valor orientatiu |
|---|---|
| Temps màxim per petició | 60 minuts |
| Memòria per instància | Fins a 32 GiB |
| CPU per instància | Fins a 8 vCPU |
| Concurrència màxima | 1.000 |
| Instàncies màximes | 1.000 per defecte (ampliable) |
| Mida de petició i resposta | 32 MiB (sense streaming) |
| GPU | Disponible en regions seleccionades |
| Emmagatzematge local | A la memòria (compta contra el límit) o volums muntats |
I els escenaris on Cloud Run no és la resposta:
| Escenari | Per què no | Alternativa |
|---|---|---|
| Processos que han de córrer permanentment sense peticions | La CPU es congela; amb min-instances i CPU sempre activa es pot, però pagues per una VM cara |
Compute Engine o GKE |
| Bases de dades, cues, Elasticsearch | Necessiten estat persistent i disc | Serveis gestionats |
| Aplicacions que necessiten disc local ràpid i gran | El sistema de fitxers viu a la memòria | Compute Engine amb SSD local |
| Tasques de més de 60 minuts | Límit dur | Cloud Run jobs (24 h) o Batch |
| Programari amb llicència lligada al maquinari | No hi ha maquinari per llicenciar | Compute Engine amb llicència pròpia |
| Protocols que no siguin HTTP/gRPC/WebSocket (per exemple, un servidor de joc UDP) | Només hi entra trànsit HTTP | Compute Engine amb balancejador de xarxa |
| Necessitat d'accés al nucli, mòduls o privilegis | Sandbox sense privilegis | GKE amb nodes propis |
- La taula madura: Cloud Run vs Functions vs GKE vs Compute Engine
A 02-07 es va fer aquesta comparació amb la informació d'aleshores. Ara, amb sis mòduls més de recorregut, es pot fer amb criteri de debò.
| Criteri | Cloud Run | Cloud Functions 2a gen | GKE Autopilot | Compute Engine |
|---|---|---|---|---|
| Unitat de desplegament | Contenidor | Funció | Contenidor + manifestos | VM |
| Escala a zero | Sí | Sí | No (pla de control fix) | No |
| Facturació | Per ús, al segon | Per ús | Per recursos dels pods | Per VM encesa |
| Arrencada en fred | Segons, mitigable | Segons | No aplica | No aplica |
| Feina operativa | Mínima | Mínima | Mitjana | Alta |
| Portabilitat | Alta (contenidor estàndard) | Mitjana | Molt alta | Mitjana |
| Corba d'aprenentatge | Baixa | Molt baixa | Alta | Mitjana |
| Serveis que es criden entre si | Bé, fins a certa escala | Regular | Excel·lent | Manual |
| Càrregues per lots llargues | Jobs, fins a 24 h | No (límit curt) | Sí | Sí |
| Estat al disc | No | No | Sí (volums) | Sí |
| Control del sistema operatiu | No | No | Parcial | Total |
| Cost amb trànsit irregular | El millor | El millor per a esdeveniments | Dolent (cost base) | Dolent |
| Cost amb trànsit constant alt | Bo | Dolent | Bo | El millor amb compromís |
| Elecció d'AlpinaShop | Catàleg web | procesar-imagen-producto |
Es retira | Es retira |
Les quatre preguntes que resolen gairebé totes les decisions, en aquest ordre:
- És un contenidor HTTP sense estat amb trànsit irregular? → Cloud Run. És el cas majoritari.
- És una reacció a un esdeveniment únic, de codi breu? → Cloud Functions (que per sota és Cloud Run).
- Hi ha molts serveis que es criden entre si, amb estat, DaemonSets o operadors? → GKE.
- Necessites el sistema operatiu, disc local, llicències lligades o protocols no HTTP? → Compute Engine.
I l'observació que tanca el continu d'abstracció de 02-07: la resposta correcta per a AlpinaShop va resultar ser l'esglaó que no s'havia provat. Es va passar per VM, per App Engine i per Kubernetes abans d'arribar a l'opció que encaixava. Això no va ser temps perdut: va ser com es va adquirir el criteri per decidir.
- Apagar
alpinashop-web-mig i el que això simplifica
alpinashop-web-mig i el que això simplificaLa llista de coses que deixen d'existir en retirar el MIG és més llarga del que sembla, i és l'argument més fort a favor del moviment:
| Desapareix | Què implicava |
|---|---|
| La plantilla d'instància | Un recurs més a Terraform, amb la seva imatge base i la seva versió |
| L'startup script | Cent línies de bash que instal·laven Docker i arrencaven el contenidor |
| L'apedaçament del sistema operatiu | Actualitzacions de seguretat mensuals de la Marta |
| L'agent d'operacions | Instal·lació, configuració i actualització per tenir mètriques de memòria |
La comprovació d'estat hc-catalogo |
Ja no cal per al backend (continua sent útil per a les uptime checks) |
| La política d'autoescalat del MIG | CPU objectiu, període d'estabilització, retard d'inicialització |
| Les actualitzacions progressives | max-surge, max-unavailable i esperar deu minuts |
| Les claus SSH i l'accés per IAP a les VM | Una superfície d'atac sencera |
| Els discos persistents i les seves instantànies | Recursos que s'obliden i es paguen (07-05) |
| La reserva de capacitat per a la campanya | Cloud Run escala sol |
Deu elements menys per mantenir, entendre, documentar i explicar a qui vingui després. Per a una empresa amb una persona d'infraestructura, aquesta reducció val més que l'estalvi econòmic.
El que sí que cal continuar fent, perquè ningú no es confongui:
- Actualitzar la imatge base del
Dockerfilei reconstruir. Cloud Run apedaça el sandbox, no el teu contenidor. - Vigilar les vulnerabilitats de la imatge a Artifact Registry (07-04).
- Gestionar migracions d'esquema de la base de dades.
- Tot el relacionat amb l'aplicació en si.
L'arquitectura de còmput d'AlpinaShop queda, per fi, així:
flowchart TB
subgraph BORDE["Vora global"]
IP["alpinashop-lb-ip"] --> ARM["Cloud Armor pol-catalogo-web"] --> CDN["Cloud CDN"]
end
CDN --> NEG["NEG sense servidor"]
NEG --> RUN["Cloud Run alpinashop-web<br/>0 a 30 instàncies"]
RUN --> SQL["Cloud SQL alpinashop-pedidos<br/>HA regional"]
RUN --> FS["Firestore - sessions"]
RUN --> SEC["Secret Manager"]
GCS["Bucket alpinashop-catalogo"] --> CDN
GCS -->|"esdeveniment"| FN["Cloud Function<br/>procesar-imagen-producto"]
RUN -->|"comanda"| PS["Pub/Sub pedidos-nuevos"]
PS --> DF["Dataflow"] --> BQ["BigQuery alpinashop_analitica"]
JOB["Cloud Run job<br/>informes-nocturnos"] --> BQ
style RUN fill:#e6f4ea,stroke:#34a853
style JOB fill:#e6f4ea,stroke:#34a853
No queda ni una màquina virtual a la ruta de servei de la botiga. I alpinashop-cluster queda sense càrregues de producció: es conserva com a entorn de proves de contenidors i per al que pugui venir, amb una nota a DA-004 per revisar si val la pena mantenir-lo.
Errors Habituals i Consells
- Escoltar en un port fix o a
127.0.0.1. És l'error número u. Llegeix$PORTi escolta a0.0.0.0. - Desplegar sense
--service-account. Heretes el compte per defecte de Compute Engine, que sol tenirEditorsobre el projecte. La teva web tindria permís per esborrar la base de dades. - Posar
--concurrency=80amb un servidor d'un sol fil. Les peticions s'encuen dins del contenidor i la latència es dispara. Configura Gunicorn, Uvicorn o el que facis servir abans de tocar la concurrència. - No posar
--max-instances. Un bucle infinit o un rastrejador et crea centenars d'instàncies, i a més tomba Cloud SQL per esgotament de connexions. - Pool de connexions gran.
pool_size=20× 30 instàncies = 600 connexions contra una base de dades que aguanta 100. Pool petit,max_overflow=0ipool_pre_ping=True. - Desar alguna cosa al sistema de fitxers i esperar trobar-la. El disc és efímer i viu a la memòria: escriure un fitxer de 300 MiB consumeix 300 MiB del teu límit de memòria.
- Llançar fils en segon pla amb CPU limitada a la petició. No s'executen. O fas servir CPU sempre activa, o mous aquesta feina a un job o a Cloud Scheduler.
- Fer servir la URL
*.run.appcom a URL pública del producte. Canvia si recrees el servei, se salta Cloud Armor i la CDN, i no és el teu domini. Balancejador sempre. - Confondre autenticació amb
ingress.--no-allow-unauthenticateddiu qui pot invocar;ingressdiu per on pot arribar. Calen tots dos. - Desplegar amb
latest. Perds la traçabilitat de quina versió corre i la reversió deixa de ser fiable. Etiqueta amb$COMMIT_SHA. - Creure que el canari protegeix de tot. No protegeix de canvis d'esquema de base de dades ni serveix de res amb trànsit baix.
- Oblidar
--timeout 0a Gunicorn. Dos temporitzadors competint produeixen talls que semblen aleatoris. - Consell: mesura la concurrència amb una prova de càrrega real abans de fixar-la. És el paràmetre amb major impacte conjunt en cost i latència.
- Consell: fes servir
--taga cada desplegament, encara que no facis canari. Tenir una URL per revisió és útil per depurar i no costa res. - Consell: activa
startup_cpu_boost. És gratis a la pràctica i redueix l'arrencada en fred de manera apreciable en runtimes pesats. - Consell: la imatge petita és la millor optimització d'arrencada en fred. Una imatge
distrolessoalpineben feta arrenca molt abans que unaubuntuamb mig sistema a dins.
Exercicis
Exercici 1 — Triar concurrència, CPU, memòria i instàncies
AlpinaShop desplegarà a Cloud Run un servei nou: alpinashop-buscador, una API de cerca del catàleg.
Dades mesurades a l'entorn de desenvolupament:
- Escrit en Python amb FastAPI + Uvicorn (asíncron, un sol procés).
- Cada petició: 15 ms de CPU (tokenitzar i puntuar) + 120 ms esperant Elasticsearch.
- Consum de memòria: 180 MiB en repòs + ~2 MiB per petició en vol.
- Trànsit: 5 peticions/segon de mitjana, 40 peticions/segon en campanya.
- Elasticsearch admet 200 connexions concurrents en total i el comparteix amb dos serveis més.
- Ningú no tolera més de 800 ms de latència p95.
Determina --concurrency, --cpu, --memory, --min-instances i --max-instances, justificant cada valor amb les dades. Indica a més què canviaries a l'arrencada de l'aplicació.
Exercici 2 — Dissenyar el desplegament segur al pipeline
Escriu els passos de Cloud Build (cloudbuild.yaml) que implementen el desplegament canari del catàleg amb aquestes regles:
- Construir la imatge etiquetada amb
$COMMIT_SHA. - Desplegar sense trànsit, amb l'etiqueta
candidata. - Executar una prova de fum contra la URL de l'etiqueta; si falla, avortar.
- Donar el 10 % del trànsit.
- Esperar 10 minuts i comprovar automàticament que la taxa d'error 5xx de la revisió nova està per sota de l'1 %; si no, revertir a 0 % i fer fallar la compilació.
- Només llavors, esperar l'aprovació manual de la Marta per passar al 100 %.
Indica també quins permisos necessita el compte de servei del pipeline i per què el pas 5 és el que més gent omet.
Exercici 3 — Diagnosticar una migració que va malament
Tres hores després de moure el 70 % del trànsit a Cloud Run, la Marta observa això:
- La latència p50 del backend de Cloud Run és de 210 ms; la del MIG, 195 ms. Acceptable.
- La latència p99 de Cloud Run és de 4,2 segons; la del MIG, 380 ms.
- A Cloud Logging apareixen uns 40 errors per hora:
sqlalchemy.exc.OperationalError: server closed the connection unexpectedly. - El nombre d'instàncies oscil·la entre 1 i 9 constantment, amb pujades i baixades cada pocs minuts.
cloudsql.googleapis.com/database/postgresql/num_backendsha passat de 12 a 34.- Error Reporting mostra 6 casos de
Memory limit of 512 MiB exceeded.
Diagnostica cada símptoma, indica la causa arrel probable de cadascun, i escriu el pla de correcció ordenat per prioritat. Decideix a més si cal revertir al MIG o es pot corregir en calent.
Solucions
Solució 1 — Triar concurrència, CPU, memòria i instàncies
Punt de partida: el perfil de la petició. 15 ms de CPU davant de 120 ms d'espera. La CPU treballa l'11 % del temps. És un servei dominat per l'E/S, exactament el cas on la concurrència alta és rendible.
--concurrency. Tres restriccions alhora, i cal respectar la més estricta:
- Per CPU: amb 15 ms de CPU cada 135 ms totals, una vCPU podria sostenir teòricament unes 9 peticions simultànies abans de saturar-se. Aquest és el límit dur.
- Per Elasticsearch: 200 connexions compartides entre tres serveis, diguem-ne 60 per al cercador. Si cada petició obre una connexió,
concurrència × instàncies ≤ 60. - Per latència: el pressupost p95 és de 800 ms, i la petició base són 135 ms. Hi ha marge per a una mica de cua, però no gaire.
Amb 1 vCPU, posar concurrència per damunt de 9-10 fa que les peticions esperin CPU. Es tria --concurrency=10. I si es puja a --cpu=2, es podria anar a 20; amb el trànsit d'aquest servei no compensa.
--cpu=1. Suficient per a la concurrència triada. Pujar a 2 vCPU només té sentit si es puja també la concurrència, i el trànsit no ho justifica.
--memory=512Mi. Càlcul: 180 MiB de base + 10 peticions × 2 MiB = 200 MiB, més el runtime de Python i marge. 512 MiB deixa folgança de més del doble. Posar 256 MiB seria jugar amb foc, perquè quedar-se sense memòria mata la instància, no l'alenteix.
--max-instances. El pic són 40 req/s. Amb 135 ms per petició i concurrència 10, cada instància sosté unes 74 req/s en teoria; a la pràctica, amb marge, diguem-ne 40-50. N'hi hauria prou amb 1-2 instàncies. Però el límit real el posa Elasticsearch: 60 connexions / 10 per instància = 6 instàncies. Es tria --max-instances=6, que cobreix el pic amb marge ampli i protegeix el backend compartit, que és el recurs escàs.
--min-instances=1. Un cercador és de cara al client i una arrencada en fred d'1-2 s a la primera cerca del matí és visible i molesta. Una instància mínima costa uns pocs euros al mes. A alpinashop-dev, 0.
Què canviar a l'arrencada. FastAPI amb Uvicorn en un sol procés és asíncron, així que pot amb diverses peticions simultànies —però només si el codi és realment asíncron. La comprovació crítica:
# MALAMENT: client sincron dins d una funcio async. Bloqueja el bucle d esdeveniments
# i la concurrencia efectiva passa a ser 1, per molt async que sigui l endpoint.
@app.get("/buscar")
async def buscar(q: str):
r = requests.get(f"{ES}/catalogo/_search", json=consulta(q)) # BLOQUEJA
return r.json()
# BE: client asincron
@app.get("/buscar")
async def buscar(q: str):
async with httpx.AsyncClient() as cli:
r = await cli.post(f"{ES}/catalogo/_search", json=consulta(q))
return r.json()Aquest és exactament l'error de l'apartat 6 amb una altra cara: un async def que crida una biblioteca síncrona no és concurrent, i la concurrència 10 configurada a Cloud Run no serveix de res.
A més: crear el client HTTP una vegada en arrencar i reutilitzar-lo (no a cada petició, com a l'exemple simplificat), i arrencar amb uvicorn main:app --host 0.0.0.0 --port $PORT --workers 1 — un sol worker, perquè el paral·lelisme el dona el bucle d'esdeveniments i Cloud Run afegeix instàncies.
Solució 2 — Dissenyar el desplegament segur al pipeline
# cloudbuild.yaml — desplegament canari d alpinashop-web
substitutions:
_REGION: europe-west1
_SERVICIO: alpinashop-web
_IMAGEN: europe-west1-docker.pkg.dev/alpinashop-prod/alpinashop/catalogo
steps:
# 1. Construir amb el hash del commit
- id: construir
name: gcr.io/cloud-builders/docker
args: ['build', '-t', '${_IMAGEN}:$COMMIT_SHA', '.']
- id: publicar
name: gcr.io/cloud-builders/docker
args: ['push', '${_IMAGEN}:$COMMIT_SHA']
# 2. Desplegar SENSE transit, amb etiqueta
- id: desplegar-candidata
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: gcloud
args:
- run
- deploy
- ${_SERVICIO}
- --image=${_IMAGEN}:$COMMIT_SHA
- --region=${_REGION}
- --no-traffic
- --tag=candidata
- --revision-suffix=$SHORT_SHA
# 3. Prova de fum contra la URL de l etiqueta
- id: humo
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: bash
args:
- -c
- |
set -e
URL=$(gcloud run services describe ${_SERVICIO} --region=${_REGION} \
--format='value(status.traffic[?tag==`candidata`].url)')
TOKEN=$(gcloud auth print-identity-token)
for RUTA in /salud /producto/1 /api/categorias; do
CODIGO=$(curl -s -o /dev/null -w '%{http_code}' \
-H "Authorization: Bearer $$TOKEN" "$$URL$$RUTA")
echo "$$RUTA -> $$CODIGO"
[ "$$CODIGO" = "200" ] || { echo "FALLADA A $$RUTA"; exit 1; }
done
# 4. Canari al 10%
- id: canario-10
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: gcloud
args:
- run
- services
- update-traffic
- ${_SERVICIO}
- --region=${_REGION}
- --to-tags=candidata=10
# 5. Esperar i verificar la taxa d error de la revisio nova
- id: verificar-canario
name: gcr.io/google.com/cloudsdktool/cloud-sdk
entrypoint: bash
args:
- -c
- |
set -e
REV=${_SERVICIO}-$SHORT_SHA
sleep 600 # 10 minuts d observacio
FILTRO="metric.type=\"run.googleapis.com/request_count\" \
AND resource.labels.service_name=\"${_SERVICIO}\" \
AND resource.labels.revision_name=\"$$REV\""
TOTAL=$(gcloud monitoring time-series list \
--filter="$$FILTRO" --interval-end-time=now --interval-start-time=-10m \
--format='value(points.value.int64Value)' | paste -sd+ | bc)
ERR=$(gcloud monitoring time-series list \
--filter="$$FILTRO AND metric.labels.response_code_class=\"5xx\"" \
--interval-end-time=now --interval-start-time=-10m \
--format='value(points.value.int64Value)' | paste -sd+ | bc)
TOTAL=$${TOTAL:-0}; ERR=$${ERR:-0}
echo "Peticions: $$TOTAL, errors 5xx: $$ERR"
if [ "$$TOTAL" -lt 100 ]; then
echo "AVIS: mostra insuficient ($$TOTAL peticions). No es pot concloure."
exit 1
fi
RATIO=$(echo "scale=4; $$ERR / $$TOTAL * 100" | bc)
echo "Taxa d error: $$RATIO %"
if [ $(echo "$$RATIO > 1" | bc) -eq 1 ]; then
echo "REVERTINT: taxa d error per damunt de l 1%"
gcloud run services update-traffic ${_SERVICIO} \
--region=${_REGION} --to-tags=candidata=0
exit 1
fi
options:
logging: CLOUD_LOGGING_ONLYEl pas 6, l'aprovació manual, no és un pas del cloudbuild.yaml: es configura al disparador de Cloud Build amb --require-approval, tal com es va muntar a 06-01 per a producció. Quan la compilació arriba al final, queda a l'espera que la Marta aprovi, i el pas al 100 % es fa en un disparador separat o manualment amb --to-latest.
Permisos del compte de servei del pipeline (sa-deploy-prod):
| Rol | Per a què |
|---|---|
roles/run.developer |
Desplegar serveis i canviar el repartiment de trànsit |
roles/iam.serviceAccountUser sobre sa-catalogo-web |
Poder assignar aquell compte al servei. Aquest és el que tothom oblida i produeix un PERMISSION_DENIED desconcertant |
roles/artifactregistry.writer |
Publicar la imatge |
roles/monitoring.viewer |
Llegir les mètriques del pas 5 |
roles/logging.logWriter |
Escriure els registres de la compilació |
Fixa't que no té roles/editor ni permisos sobre Cloud SQL o Secret Manager: desplega, no administra.
Per què el pas 5 és el que gairebé tothom omet. Perquè és l'únic que requereix pensar. Els passos 1-4 són mecànics i surten a qualsevol tutorial; el 5 exigeix decidir quina mètrica, quin llindar, quant de temps i què fer si no hi ha dades suficients. Sense ell, el canari és teatre: es desplega al 10 %, ningú no ho mira, i al cap de deu minuts algú prem «aprovar» perquè no ha saltat cap alarma — que és diferent de que tot estigui bé.
La comprovació de mostra mínima (TOTAL -lt 100) és la subtilesa que separa un canari real d'un de decoratiu: amb poques peticions, l'absència d'errors no demostra res, i fer fallar la compilació obliga a mirar-ho en lloc d'aprovar a cegues.
Solució 3 — Diagnosticar una migració que va malament
Diagnòstic símptoma a símptoma.
(1) p50 correcte però p99 de 4,2 s. El p50 sa descarta un problema general de rendiment: la majoria de peticions van bé. Un p99 vint vegades pitjor amb un p50 normal és la signatura inconfusible de l'arrencada en fred. El servei es va desplegar amb --min-instances=0, o el trànsit és prou irregular com per crear instàncies constantment. La confirmació és al símptoma (4).
(2) server closed the connection unexpectedly, ~40/hora. Connexions del pool que feia temps que estaven inactives i que Cloud SQL, o la mateixa congelació de CPU de la instància, ha donat per mortes. És el cas exacte de pool_pre_ping: sense ell, la primera consulta després d'un període d'inactivitat fa servir una connexió zombi i falla. La causa arrel és manca de pool_pre_ping=True i de pool_recycle a la configuració de SQLAlchemy, agreujada per la congelació de CPU entre peticions que Cloud Run aplica i el MIG no aplicava.
(3) Instàncies oscil·lant entre 1 i 9 cada pocs minuts. És la causa d'(1), i al seu torn té la seva pròpia causa. Amb concurrència 80 i el trànsit d'AlpinaShop, una sola instància hauria de bastar de sobres. Que pugi a 9 significa que les instàncies moren i es recreen: això ho explica el símptoma (6). És un cicle viciós —mor per memòria, arrenca en fred, atén, torna a morir— que a més genera part d'(2), perquè cada mort s'emporta per davant les connexions del pool.
(4) Connexions a Cloud SQL de 12 a 34. Coherent amb 9 instàncies × pool de 3-4. No és un problema per si mateix —34 està lluny del límit— però confirma l'aritmètica i avisa: si el màxim d'instàncies estigués mal posat, aquí és on es veuria el desastre. És un símptoma per vigilar, no per corregir.
(5) Sis casos de Memory limit exceeded. Aquesta és la causa arrel principal. 512 MiB no basten per a la concurrència configurada. Les hipòtesis, per probabilitat: la concurrència és 80 i cada petició del catàleg consumeix més memòria de l'estimada (renderitza plantilles, carrega imatges a la memòria); o hi ha una memòria cau en procés que creix sense límit; o l'aplicació escriu fitxers temporals, que a Cloud Run compten com a memòria.
Cadena causal completa:
Memoria insuficient (512 MiB amb concurrencia 80)
|
v
La instancia mor en superar el limit
|
+--> Es recreen instancies -> oscil.lacio 1 a 9 (simptoma 3)
| |
| v
| arrencades en fred -> p99 de 4,2 s (simptoma 1)
| |
| v
| mes instancies -> 34 connexions (simptoma 4)
|
+--> Es perden connexions del pool
|
v
(agreujat per manca de pool_pre_ping)
|
v
OperationalError, 40/hora (simptoma 2)Revertir o corregir en calent?
No cal revertir. El raonament: el 30 % del trànsit continua al MIG, el p50 és correcte, els errors són 40 a l'hora sobre milers de peticions (~0,5 %) i totes les causes són de configuració, no de codi. Revertir costaria la resta del dia i no aportaria informació nova. Ara bé, sí que cal deixar de pujar trànsit fins que estigui corregit, i tenir la comanda de reversió preparada per si alguna cosa empitjora.
Si en lloc d'això els errors fossin del 10 %, o hi hagués pèrdua de comandes, o el p50 estigués degradat, la decisió seria la contrària: revertir primer, diagnosticar després. En un incident, primer s'atura el dany.
Pla de correcció per prioritat:
| Prio | Acció | Comanda / canvi | Efecte esperat |
|---|---|---|---|
| 1 | Pujar memòria a 1 GiB | gcloud run services update alpinashop-web --region=europe-west1 --memory=1Gi |
Talla la causa arrel; desapareixen (3), (1) i bona part de (2) |
| 2 | Baixar concurrència a 40 mentre s'investiga | --concurrency=40 |
Redueix la memòria simultània a la meitat. Cost una mica major, acceptable durant el diagnòstic |
| 3 | Afegir pool_pre_ping=True i pool_recycle=1800 |
Canvi de codi, desplegament canari | Elimina el residu de (2) |
| 4 | Posar --min-instances=1 |
--min-instances=1 |
Elimina l'arrencada en fred del primer usuari |
| 5 | Alerta de memòria | Política sobre run.googleapis.com/container/memory/utilizations > 80 % |
Que la propera vegada avisi abans de morir |
| 6 | Perfilar la memòria de debò | Cloud Profiler o anàlisi local amb càrrega | Saber si 1 GiB és la solució o només un pedaç sobre una fuita |
El pas 6 és el que no cal saltar-se. Pujar la memòria és l'acció correcta ara, perquè atura l'hemorràgia. Però si hi ha una fuita de memòria real —una memòria cau que creix sense límit, per exemple— amb 1 GiB simplement trigarà el doble a morir, i el problema reapareixerà en campanya amb el pitjor moment possible. La diferència entre resoldre un incident i ajornar-lo és exactament aquest últim pas, i és el que es cancel·la quan tot torna a estar verd.
Conclusió
DA-001 està complerta. El catàleg d'AlpinaShop corre a Cloud Run, darrere de la mateixa IP, el mateix domini, el mateix certificat, la mateixa CDN i el mateix WAF; el MIG està apagat; i no queda una sola màquina virtual a la ruta de servei de la botiga.
Saps què és Cloud Run —contenidors sense servidor— i per què la unitat de desplegament ho canvia tot: el contracte són sis regles (escoltar a $PORT i a 0.0.0.0, parlar HTTP, arrencar ràpid, no desar estat, no fer res entre peticions, ser un contenidor Linux sense privilegis) i complir-les fa que la mateixa imatge corri a Cloud Run, a Kubernetes o al teu portàtil. Distingeixes servei, revisió i instància, i saps que les revisions són immutables — que és el que fa possible tota la resta.
Tens el criteri entre serveis i treballs: si respon a algú, servei; si fa alguna cosa i acaba, treball. Amb l'antipatró identificat: l'informe nocturn com a endpoint HTTP disparat per Scheduler, que hereta quatre problemes que un job no té.
Has desplegat el catàleg amb gcloud run deploy bandera a bandera, sabent què fa cadascuna i per què té aquell valor, i ho has escrit en Terraform amb google_cloud_run_v2_service, amb cpu_idle, startup_cpu_boost i ingress restringit al balancejador, i amb la frontera clara de què gestiona Terraform (la forma) i què gestiona el pipeline (la imatge i el trànsit).
Domines les revisions i el repartiment de trànsit: l'etiqueta que dona una URL pròpia per provar sense arriscar, la progressió 0 → 10 → 50 → 100 amb el que s'observa a cada esglaó, i la reversió instantània que triga segons perquè la revisió anterior mai no va deixar d'existir. Amb les dues advertències honestes: el canari no protegeix de canvis d'esquema, i amb trànsit baix el 10 % no demostra res.
Entens la concurrència millor que la majoria: què és, per què el model d'una petició per instància és dotze vegades més car per a una càrrega dominada per E/S, com es mesura amb una prova de càrrega real, i —el més important— que el sostre no el posa Cloud Run sinó el teu servidor d'aplicacions. Un --concurrency=80 sobre un Gunicorn d'un worker síncron, o un async def que crida una biblioteca bloquejant, converteixen la concurrència en una cua invisible.
Saps governar l'escalat: l'escalat a zero i el seu preu en arrencades en fred, --min-instances que les mitiga però no les elimina, --max-instances com a tallafoc de cost i de connexions a la base de dades amb la seva fórmula, i l'elecció entre CPU durant la petició i CPU sempre activa, amb el parany dels fils en segon pla que no s'executen.
Has connectat el servei amb tota la plataforma: Cloud SQL per connector amb un pool petit i pool_pre_ping, Secret Manager muntat com a variable o com a fitxer amb la diferència pràctica en la rotació, VPC directa —que substitueix el vell connector— amb els seus dos modes d'egress, Pub/Sub push amb OIDC sense ni un sol secret compartit, i Eventarc, amb la revelació que Cloud Functions 2a generació és Cloud Run per sota.
Has assegurat el servei amb les quatre decisions: compte de servei propi i mínim —evitant la fallada més comuna, heretar el compte per defecte amb Editor—, --no-allow-unauthenticated, ingress restringit al balancejador perquè ningú no pugui saltar-se el WAF per la URL run.app, i IAP per al que és intern.
Has fet la migració sense tall: preparació amb llista de comprovació, desplegament sense trànsit, NEG sense servidor afegit al balancejador existent, transvasament progressiu amb capacity-scaler vigilant quatre mètriques, tres dies de convivència amb reversió d'una comanda, i retirada en dos passos amb resize 0 abans de l'esborrat des de Terraform. I coneixes les quatre particularitats del NEG sense servidor: sense comprovacions d'estat, sense capacitat, regional encara que el balancejador sigui global, i amb la CDN funcionant igual.
Tens el cost desglossat: vCPU-segon, GiB-segon i peticions, amb nivell gratuït, i el càlcul comparat que baixa el còmput del catàleg d'uns 50 € a uns 7 € al mes — amb l'honestedat d'assenyalar que amb trànsit constant alt el MIG amb compromís guanyaria, que en campanya la factura puja perquè es paga l'ús, i que ara el balancejador és el 74 % del que costa la botiga. Coneixes els límits i els set escenaris on Cloud Run no encaixa. I t'emportes la taula madura amb les quatre preguntes que resolen gairebé qualsevol decisió de còmput.
Finalment, has vist el que desapareix en apagar el MIG: deu elements —plantilla, script d'arrencada, apedaçament, agent, autoescalat, actualitzacions progressives, claus SSH, discos, instantànies, reserva de campanya— que ja no cal mantenir, entendre ni explicar. Per a un equip d'una persona, això val més que l'estalvi.
I tanmateix, dos apartats d'aquesta lliçó han deixat un serrell que apunta al mateix lloc. La connexió a Cloud SQL seria millor per IP privada a través de la VPC. Sortir cap a la passarel·la de pagament amb IP fixa exigeix passar pel Cloud NAT. I l'ERP de Sabadell, que DA-004 va deixar a l'altra banda d'un túnel que encara no existeix, necessita que Cloud Run pugui abastar-lo per la xarxa privada.
Els tres són el mateix problema: la xarxa d'AlpinaShop es va quedar en el bàsic al mòdul 3 i ja no basta. Una sola VPC en un sol projecte, tres projectes que s'ignoren entre si, cap connectivitat amb la botiga física i cap perímetre que impedeixi que les dades d'alpinashop-datos surtin per la porta. La lliçó següent ho resol: VPC compartida, aparellament, Private Service Connect, Cloud VPN HA cap a Sabadell i controls de servei de VPC.
Curs de Google Cloud Platform (GCP)
Mòdul 1: Introducció a Google Cloud Platform
- Què és Google Cloud Platform?
- Configuració del teu compte de GCP
- Descripció general de la consola de GCP
- Projectes, jerarquia de recursos i facturació
- Regions, zones i model de responsabilitat compartida
- Cloud Shell i la CLI de gcloud
Mòdul 2: Serveis principals de GCP
- Compute Engine: màquines virtuals a Google Cloud
- Cloud Storage: emmagatzematge d'objectes
- Cloud SQL: bases de dades relacionals gestionades
- App Engine: plataforma com a servei
- Google Kubernetes Engine (GKE)
- Bases de dades NoSQL: Firestore, Bigtable i Spanner
- Com triar el servei de còmput adequat
Mòdul 3: Xarxes i seguretat
- Xarxes VPC
- Balanceig de càrrega al núvol
- Cloud CDN
- Gestió d'identitat i accés (IAM)
- Cloud Armor
- Secrets i xifratge: Secret Manager i Cloud KMS
- Cloud DNS, certificats TLS i publicació segura de serveis
Mòdul 4: Dades i anàlisi
- BigQuery: el magatzem de dades analític
- Cloud Dataflow: processament de dades per lots i en temps real
- Cloud Dataproc: Spark i Hadoop gestionats
- Cloud Pub/Sub: missatgeria asíncrona
- Cloud Data Fusion: integració de dades sense codi
- Orquestració de pipelines amb Cloud Composer i Workflows
- Govern de les dades i taulers amb Dataplex i Looker Studio
Mòdul 5: Aprenentatge automàtic i IA
- Vertex AI: la plataforma d'aprenentatge automàtic de GCP
- AutoML: models a mida sense escriure codi
- TensorFlow a GCP: entrenament i servei de models
- API de llenguatge natural
- API de visió
- IA generativa a Vertex AI: models Gemini i incrustacions
- MLOps: del model al producte amb Vertex AI Pipelines
Mòdul 6: DevOps i monitoratge
- Cloud Build: integració contínua a GCP
- Cloud Source Repositories i gestió del codi font
- Cloud Functions: funcions sense servidor
- Cloud Monitoring (abans Stackdriver): mètriques, taulers i alertes
- Cloud Deployment Manager i infraestructura com a codi nativa
- Cloud Logging i Cloud Trace: registres, traces i diagnòstic
- Terraform a GCP: infraestructura com a codi a la pràctica
Mòdul 7: Temes avançats de GCP
- Híbrid i multinúvol amb Anthos
- Computació sense servidor amb Cloud Run
- Xarxes avançades: VPC compartida, aparellament i connectivitat híbrida
- Bones pràctiques de seguretat
- Gestió i optimització de costos
- Fiabilitat: SLO, alta disponibilitat i recuperació de desastres
- Govern a escala: organització, polítiques i auditoria
