Heroku va posar CicloUrbana a Internet en una tarda, i també ens va ensenyar on són els seus límits: sense xarxa privada pròpia, sense control de l'entorn d'execució, amb un cost per unitat de capacitat que creix malament i amb la base de dades en una infraestructura que no és nostra. L'ajuntament de Ribalta ha decidit que la xarxa municipal de bicicletes és un servei públic permanent, amb dades personals subjectes al RGPD, i demana una infraestructura seriosa: xarxa privada, base de dades inaccessible des d'Internet, secrets auditats, alta disponibilitat i capacitat de créixer.

Aquesta lliçó construeix aquest entorn a AWS. No cobreix AWS sencer —és un proveïdor amb més de dos-cents serveis— sinó el camí mínim i complet per portar la imatge de 07-04 a producció: un repositori d'imatges a ECR, PostgreSQL gestionat a RDS dins de subxarxes privades, els secrets a Secrets Manager, un servei d'ECS Fargate que executa la imatge sense que administrem cap servidor, i un balancejador amb certificat gestionat que comprova la salut contra les sondes de 07-01. I acaba, com sempre, amb la llista exacta de recursos que cal destruir, perquè aquí la factura és real i arriba puntual.

Contingut

  1. Panorama: quin servei d'AWS executa una aplicació Spring Boot
  2. Conceptes base imprescindibles
  3. Preparar la imatge: ECR
  4. La base de dades: RDS PostgreSQL
  5. Secrets: Secrets Manager i Parameter Store
  6. Desplegar a ECS Fargate
  7. El balancejador, la salut i el TLS
  8. Migracions de Flyway a AWS
  9. Autoescalat
  10. Observabilitat: CloudWatch
  11. Camí curt: Elastic Beanstalk
  12. Infraestructura com a codi
  13. Costos i neteja
  14. Seguretat: les regles que no es negocien
  15. Errors Comuns i Consells
  16. Exercicis

  1. Panorama: quin servei d'AWS executa una aplicació Spring Boot

Servei Què gestiones tu Esforç Cost típic (mes) Quan triar-lo
EC2 Màquina virtual completa: SO, Java, arrencada, pedaços, TLS Molt alt 15-80 € Requisits molt específics o cost mínim amb algú que administri Linux
Elastic Beanstalk Puges un JAR; AWS crea EC2, balancejador i autoescalat Baix 40-120 € Camí curt des d'un JAR, sense aprendre ECS
ECS + Fargate Una imatge i la seva definició de tasca; sense servidors Mitjà-baix 60-200 € L'opció del curs: producció real sense operar un orquestrador
EKS (Kubernetes gestionat) Manifestos, complements, versions del clúster Alt 200 € + clúster Molts serveis i equips, portabilitat (08-04)
App Runner Només la imatge o el repositori Molt baix 30-90 € Un PaaS dins d'AWS; menys control de xarxa
Lambda + SnapStart Només el codi de la funció Baix Per invocació Càrregues esporàdiques; encaixa malament amb JPA i un pool

La decisió raonada del curs és ECS Fargate + RDS, per quatre motius en ordre de pes. No hi ha servidors per administrar: amb Fargate no existeixen instàncies EC2 que calgui apedaçar, dimensionar ni vigilar, només es declara quanta CPU i memòria necessita cada tasca. És la destinació natural de la imatge de 07-04, sense canviar l'artefacte, sense buildpacks i sense Procfile. Dona xarxa privada de debò, amb la base de dades en subxarxes sense sortida a Internet, que és el requisit que ens va treure de Heroku. I és notablement més simple que Kubernetes per a una sola aplicació: si CicloUrbana fos l'única cosa que cal desplegar, muntar EKS seria pagar la complexitat d'un orquestrador sense l'escala que la justifica.

Elastic Beanstalk queda com a camí curt documentat a l'apartat 11: si l'objectiu és només «tenir el JAR corrent a AWS aquesta tarda», és més ràpid. Lambda es descarta per disseny: una aplicació amb Hibernate, un pool de connexions i un context de Spring encaixa malament en un model d'invocacions efímeres, i encara que SnapStart mitigui l'arrencada en fred, la multiplicació de connexions a PostgreSQL continua sent un problema real.

  1. Conceptes base imprescindibles

Concepte Què és A CicloUrbana
Regió Zona geogràfica amb centres de dades independents eu-west-1 (Irlanda): dades a la UE, requisit del RGPD
Zona de disponibilitat (AZ) Centre de dades aïllat dins d'una regió Almenys dues, per tolerar la caiguda d'una
VPC Xarxa privada virtual pròpia, amb el seu rang d'adreces 10.20.0.0/16
Subxarxa pública Té ruta a Internet a través d'un Internet Gateway On viu el balancejador
Subxarxa privada Sense ruta d'entrada des d'Internet On viuen les tasques i la base de dades
Grup de seguretat Tallafocs amb estat, a nivell de recurs Un per a l'ALB, un altre per a l'app, un altre per a RDS
Rol IAM Identitat que assumeix un recurs per obtenir permisos temporals La tasca assumeix un rol; mai no hi ha claus al codi
Política IAM Document JSON que concedeix o denega accions Permetre llegir un secret concret
ALB Balancejador d'aplicació (capa 7): encamina HTTP i comprova salut Terminació TLS i /actuator/health/readiness
Route 53 DNS gestionat ciclourbana.ribalta.example → ALB
ACM Certificats TLS gratuïts i de renovació automàtica El certificat del domini de l'ajuntament

L'arquitectura completa que construirem:

flowchart TD
    U[Ciutadans] --> R53[Route 53<br/>ciclourbana.ribalta.example]
    R53 --> ALB[ALB · subxarxes publiques<br/>TLS amb certificat d'ACM]
    subgraph VPC["VPC 10.20.0.0/16 · eu-west-1"]
      subgraph PUB[Subxarxes publiques · AZ a i b]
        ALB
      end
      subgraph PRIV[Subxarxes privades · AZ a i b]
        T1[Tasca Fargate 1]
        T2[Tasca Fargate 2]
        RDS[(RDS PostgreSQL 16<br/>Multi-AZ)]
      end
    end
    ALB -->|8080| T1
    ALB -->|8080| T2
    T1 --> RDS
    T2 --> RDS
    ECR[(ECR<br/>ciclourbana:2.4.0)] -.imatge.-> T1
    SM[Secrets Manager] -.credencials.-> T1
    T1 -.logs.-> CW[CloudWatch Logs]

Tres regles d'aquesta arquitectura, que són les que separen un desplegament seriós d'un d'improvisat: només l'ALB és a subxarxes públiques; RDS no té adreça pública ni la tindrà mai; i cada grup de seguretat només accepta trànsit del grup anterior, no de rangs d'adreces solts.

  1. Preparar la imatge: ECR

Elastic Container Registry és el registre d'imatges d'AWS. Es podria fer servir GHCR (07-04), però ECR és al mateix compte i regió, les tasques de Fargate hi accedeixen amb permisos d'IAM sense credencials de llarga durada, i la descàrrega no surt de la xarxa d'AWS.

export COMPTE=111122223333          # identificador fictici de compte
export REGIO=eu-west-1
export REGISTRE=$COMPTE.dkr.ecr.$REGIO.amazonaws.com

# 1. Crear el repositori amb escaneig de vulnerabilitats i immutabilitat
aws ecr create-repository \
  --repository-name ciclourbana \
  --region $REGIO \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE

# 2. Autenticar Docker contra ECR (el testimoni dura 12 hores)
aws ecr get-login-password --region $REGIO \
  | docker login --username AWS --password-stdin $REGISTRE

# 3. Etiquetar i publicar la imatge de 07-04
docker build -t ciclourbana:2.4.0 .
docker tag ciclourbana:2.4.0 $REGISTRE/ciclourbana:2.4.0
docker push $REGISTRE/ciclourbana:2.4.0

Dues opcions de la primera ordre mereixen explicació. scanOnPush=true analitza cada imatge publicada a la recerca de vulnerabilitats conegudes, complementant el docker scout de 07-04 amb una segona xarxa de seguretat automàtica. IMMUTABLE impedeix sobreescriure una etiqueta ja publicada: ciclourbana:2.4.0 significarà sempre la mateixa imatge, byte a byte. És el que fa que la reversió de 08-01 sigui exacta i elimina l'error clàssic de latest apuntant a una cosa diferent de la que es va provar.

Convé a més afegir una política de cicle de vida que conservi només les últimes 15 imatges: el registre es factura per GB emmagatzemat i creix sense límit si ningú no el poda.

Important per a Fargate: la imatge ha de ser d'arquitectura linux/amd64 llevat que es configuri Fargate amb Graviton (ARM). Si construeixes en un portàtil amb Apple Silicon, docker build produeix arm64 i la tasca fallarà amb exec format error. La solució és docker buildx build --platform linux/amd64 ... o construir a la canalització (08-05).

  1. La base de dades: RDS PostgreSQL

# Grup de subxarxes: RDS ha de viure en subxarxes PRIVADES d'almenys dues AZ
aws rds create-db-subnet-group \
  --db-subnet-group-name ciclourbana-privades \
  --db-subnet-group-description "Subxarxes privades de CicloUrbana" \
  --subnet-ids subnet-0aa11bb22cc33dd44 subnet-0ee55ff66gg77hh88

aws rds create-db-instance \
  --db-instance-identifier ciclourbana-prod \
  --engine postgres --engine-version 16.4 \
  --db-instance-class db.t4g.micro \
  --allocated-storage 20 --storage-type gp3 --storage-encrypted \
  --db-name ciclourbana \
  --master-username ciclourbana_admin \
  --manage-master-user-password \
  --db-subnet-group-name ciclourbana-privades \
  --vpc-security-group-ids sg-0bd99ee88ff77aa66 \
  --no-publicly-accessible \
  --multi-az \
  --backup-retention-period 7 \
  --preferred-backup-window "02:00-03:00" \
  --preferred-maintenance-window "sun:04:00-sun:05:00" \
  --deletion-protection

Paràmetre a paràmetre, i per què cadascun importa:

Paràmetre Què fa Per què així
--engine-version 16.4 Fixa PostgreSQL 16 Paritat d'entorns (08-01): la mateixa versió que Testcontainers a 06-05
--db-instance-class db.t4g.micro Mida de la instància Suficient per començar; es canvia després amb una aturada breu
--storage-encrypted Xifrat en repòs amb KMS Obligatori per a dades personals; no es pot activar després
--manage-master-user-password AWS genera la contrasenya i la desa a Secrets Manager Ningú no veu mai la contrasenya; es pot rotar
--no-publicly-accessible Sense adreça pública La regla que no es negocia
--multi-az Rèplica en espera en una altra AZ amb commutació automàtica Tolera la caiguda d'una zona; duplica el cost
--backup-retention-period 7 Còpies automàtiques durant 7 dies amb PITR RPO de segons, RTO de minuts (08-01)
--preferred-maintenance-window Finestra d'actualitzacions menors Diumenge de matinada, quan Ribalta dorm
--deletion-protection Impedeix esborrar-la per accident Cal desactivar-la explícitament per eliminar-la

El grup de seguretat és la peça crítica. No ha de permetre el port 5432 des de 0.0.0.0/0 ni des de la teva IP de casa: només des del grup de seguretat de l'aplicació.

aws ec2 authorize-security-group-ingress \
  --group-id sg-0bd99ee88ff77aa66 \
  --protocol tcp --port 5432 \
  --source-group sg-0cc44dd55ee66ff77        # el grup de les tasques, no un CIDR

Referenciar grup a grup en lloc de rangs d'adreces té un avantatge decisiu: les tasques de Fargate reben adreces IP noves cada vegada que es despleguen, i amb aquesta regla l'autorització continua sent vàlida sense tocar res.

Per què RDS mai no és accessible des d'Internet. Un PostgreSQL amb adreça pública rep intents d'autenticació automatitzats al cap de pocs minuts d'existir, i conté les dades personals dels ciutadans de Ribalta: n'hi ha prou amb una contrasenya feble, una versió amb una fallada coneguda o una regla temporal que algú va oblidar tancar. Quan calgui consultar-la des d'un portàtil, la manera correcta és un bastió amb Session Manager (sense obrir ports ni gestionar claus SSH), mai obrir el grup de seguretat «un moment».

  1. Secrets: Secrets Manager i Parameter Store

Secrets Manager SSM Parameter Store (SecureString)
Cost ~0,40 $/secret/mes + peticions Estàndard gratuït; avançat de pagament
Rotació automàtica Sí, integrada amb RDS No, manual
Mida 64 KB 4 KB (8 KB avançat)
Ús recomanat Credencials de base de dades, claus d'API Configuració no crítica, URL, paràmetres

Amb --manage-master-user-password, la contrasenya d'RDS ja és a Secrets Manager. Falta el secret del JWT:

aws secretsmanager create-secret \
  --name ciclourbana/prod/jwt \
  --description "Secret HS256 de signatura del JWT de CicloUrbana" \
  --secret-string "$(openssl rand -base64 48)"
# {"ARN": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:ciclourbana/prod/jwt-Ab3xYz"}

Hi ha dues formes que aquest valor arribi a l'aplicació.

Opció A (recomanada): que ECS l'injecti com a variable d'entorn. A la definició de tasca es fa servir el bloc secrets en lloc d'environment; l'agent resol l'ARN en arrencar el contenidor i el posa com a variable. L'aplicació no sap que existeix AWS: continua llegint ${JWT_SECRET} com a 07-02, així que el mateix artefacte funciona a Heroku, a Kubernetes o al portàtil — l'opció coherent amb 08-01.

Opció B: que l'aplicació el llegeixi directament amb spring-cloud-aws-starter-parameter-store o -secrets-manager:

spring:
  config:
    import:
      - aws-secretsmanager:ciclourbana/prod/jwt
      - aws-parameterstore:/ciclourbana/prod/

És útil quan hi ha molts paràmetres o quan es vol refrescar en calent, però acobla l'artefacte a AWS i requereix permisos IAM addicionals.

El rol d'execució de la tasca necessita permís per llegir aquest secret i cap més — mínim privilegi en la seva forma més concreta:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["secretsmanager:GetSecretValue"],
    "Resource": [
      "arn:aws:secretsmanager:eu-west-1:111122223333:secret:ciclourbana/prod/jwt-Ab3xYz",
      "arn:aws:secretsmanager:eu-west-1:111122223333:secret:rds!db-9f3a2b1c-Kd7pQr"
    ]
  }]
}

Fixa't que Resource enumera ARN concrets. Un "Resource": "*" donaria a l'aplicació accés a tots els secrets del compte, inclosos els d'altres sistemes de l'ajuntament.

Advertiment. Mai no es posen secrets com a environment en text pla a la definició de tasca: aquest JSON es versiona al repositori d'infraestructura i es consulta amb aws ecs describe-task-definition, que qualsevol amb permisos de lectura pot executar. I mai no es creen claus d'accés (AWS_ACCESS_KEY_ID) per a l'aplicació: els rols IAM donen credencials temporals i rotades automàticament.

  1. Desplegar a ECS Fargate

ECS organitza el desplegament en tres peces: un clúster (agrupació lògica), una definició de tasca (la plantilla de quin contenidor executar i com) i un servei (manté N tasques vives i les connecta al balancejador).

aws ecs create-cluster --cluster-name ciclourbana \
  --capacity-providers FARGATE --region eu-west-1

La definició de tasca, comentada camp a camp:

{
  "family": "ciclourbana",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" },
  "executionRoleArn": "arn:aws:iam::111122223333:role/ciclourbanaExecucioRol",
  "taskRoleArn": "arn:aws:iam::111122223333:role/ciclourbanaTascaRol",
  "containerDefinitions": [{
    "name": "ciclourbana",
    "image": "111122223333.dkr.ecr.eu-west-1.amazonaws.com/ciclourbana:2.4.0",
    "essential": true,
    "portMappings": [{ "containerPort": 8080, "protocol": "tcp" }],
    "environment": [
      { "name": "SPRING_PROFILES_ACTIVE", "value": "prod" },
      { "name": "TZ", "value": "Europe/Madrid" },
      { "name": "JAVA_TOOL_OPTIONS", "value": "-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError" },
      { "name": "SPRING_DATASOURCE_URL",
        "value": "jdbc:postgresql://ciclourbana-prod.abc123xyz.eu-west-1.rds.amazonaws.com:5432/ciclourbana?sslmode=require" },
      { "name": "SPRING_FLYWAY_ENABLED", "value": "false" },
      { "name": "MANAGEMENT_SERVER_PORT", "value": "8080" }
    ],
    "secrets": [
      { "name": "JWT_SECRET",
        "valueFrom": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:ciclourbana/prod/jwt-Ab3xYz" },
      { "name": "SPRING_DATASOURCE_PASSWORD",
        "valueFrom": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:rds!db-9f3a2b1c-Kd7pQr:password::" }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/ciclourbana",
        "awslogs-region": "eu-west-1",
        "awslogs-stream-prefix": "app",
        "awslogs-create-group": "true"
      }
    },
    "stopTimeout": 60
  }]
}

El que cal entendre d'aquest JSON:

  • cpu: 512 i memory: 1024 són 0,5 vCPU i 1 GB. Fargate només admet combinacions concretes (0,5 vCPU admet 1, 2, 3 o 4 GB). Amb MaxRAMPercentage=75, el heap queda en 768 MB i els 256 MB restants cobreixen metaespai, piles i fils: el dimensionament de 08-01 aplicat.
  • Dos rols diferents, i no és un detall burocràtic. El rol d'execució el fa servir l'agent d'ECS abans d'arrencar el contenidor: descarregar la imatge d'ECR, resoldre els secrets, escriure a CloudWatch. El rol de tasca el fa servir l'aplicació en execució per cridar altres serveis d'AWS (S3, SQS). Separar-los significa que l'aplicació no pot llegir secrets pel seu compte encara que la comprometin.
  • secrets davant d'environment: el valor mai no apareix a la definició. El sufix :password:: extreu un camp concret del JSON del secret d'RDS, que conté username i password.
  • SPRING_FLYWAY_ENABLED=false: les migracions s'executen a part (apartat 8).
  • MANAGEMENT_SERVER_PORT=8080: a 07-01 vam separar Actuator al 8081; aquí el tornem al port principal perquè l'ALB pugui consultar /actuator/health/readiness amb una sola destinació. L'alternativa —dos target groups— afegeix complexitat sense guany. La protecció continua sent la cadena de seguretat de 07-01, no la separació de ports.
  • stopTimeout: 60: segons entre SIGTERM i SIGKILL. Ha de superar el timeout-per-shutdown-phase de l'aturada ordenada (01-05), o ECS matarà el procés a mitges.
aws ecs register-task-definition --cli-input-json file://tasca-ciclourbana.json

aws ecs create-service \
  --cluster ciclourbana --service-name ciclourbana-web \
  --task-definition ciclourbana:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0aa11bb22cc33dd44,subnet-0ee55ff66gg77hh88],securityGroups=[sg-0cc44dd55ee66ff77],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/ciclourbana-tg/1a2b3c4d5e6f7g8h,containerName=ciclourbana,containerPort=8080" \
  --health-check-grace-period-seconds 90 \
  --deployment-configuration "minimumHealthyPercent=100,maximumPercent=200"
  • desired-count 2 reparteix les tasques entre les dues AZ: si una zona cau, el servei continua.
  • assignPublicIp=DISABLED manté les tasques fora d'Internet. Necessiten sortida per descarregar la imatge i parlar amb Secrets Manager, cosa que exigeix un NAT Gateway (uns 35 €/mes) o, més barat i més segur, VPC endpoints per a ECR, S3, Secrets Manager i CloudWatch Logs.
  • health-check-grace-period-seconds 90 dona marge a l'arrencada de la JVM abans que l'ALB comenci a penalitzar. Sense això, l'ALB marca la tasca com a no sana al cap de pocs segons, ECS la mata i n'arrenca una altra: un bucle infinit de tasques que mai no arriben a estar llestes.
  • minimumHealthyPercent=100, maximumPercent=200 defineix el rolling update de 08-01: amb 2 tasques desitjades, ECS pot arribar a 4 i mai no baixa de 2. Arrenca les noves, espera que estiguin sanes, i només llavors retira les velles. Sense tall de servei, amb convivència de versions — d'aquí l'exigència de compatibilitat cap enrere de l'esquema.

  1. El balancejador, la salut i el TLS

aws elbv2 create-target-group \
  --name ciclourbana-tg --protocol HTTP --port 8080 \
  --vpc-id vpc-0f1e2d3c4b5a69788 --target-type ip \
  --health-check-path /actuator/health/readiness \
  --health-check-interval-seconds 15 --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 --unhealthy-threshold-count 3 \
  --matcher HttpCode=200

Aquest és el moment en què 07-01 encaixa al seu lloc. La comprovació d'estat apunta a /actuator/health/readiness, no a /actuator/health ni a /, i la diferència és substancial:

Ruta Què agrega Conseqüència
/ Res; és un 404 o el controlador arrel No diu res sobre si l'app pot treballar
/actuator/health Tots els indicadors, inclosos els no crítics Una passarel·la de pagaments caiguda trauria totes les tasques de rotació
/actuator/health/readiness Només db i l'estat de disponibilitat (07-01) El correcte: surt de rotació qui no pot atendre, sense efectes col·laterals

I el registre es tanca amb deregistration_delay, la peça que evita els 502 de l'aturada:

aws elbv2 modify-target-group-attributes \
  --target-group-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/ciclourbana-tg/1a2b3c4d5e6f7g8h \
  --attributes Key=deregistration_delay.timeout_seconds,Value=30

Durant aquests 30 segons l'ALB deixa d'enviar peticions noves a la tasca que es retira però acaba les que hi ha en curs. És l'equivalent del preStop de 08-01 gestionat pel balancejador.

El certificat i els oients:

# Certificat gratuit, validat per DNS i renovat automaticament
aws acm request-certificate \
  --domain-name ciclourbana.ribalta.example \
  --validation-method DNS --region eu-west-1

# Oient 443 amb TLS
aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/ciclourbana-alb/9z8y7x6w5v4u3t2s \
  --protocol HTTPS --port 443 \
  --certificates CertificateArn=arn:aws:acm:eu-west-1:111122223333:certificate/1234abcd-56ef-78gh-90ij-klmnopqrstuv \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/ciclourbana-tg/1a2b3c4d5e6f7g8h

# Oient 80 que nomes redirigeix a 443
aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/ciclourbana-alb/9z8y7x6w5v4u3t2s \
  --protocol HTTP --port 80 \
  --default-actions '[{"Type":"redirect","RedirectConfig":{"Protocol":"HTTPS","Port":"443","StatusCode":"HTTP_301"}}]'

Com que l'ALB termina el TLS i parla HTTP amb la tasca, cal allò de 08-01 a application-prod.yml:

server:
  forward-headers-strategy: framework

Sense això, el Location d'un 201 Created sortiria amb http:// i una adreça privada. I amb això, X-Forwarded-Proto: https arriba des d'un proxy de confiança —el grup de seguretat garanteix que només l'ALB arriba al port 8080—, així que és segur confiar en la capçalera.

Finalment, Route 53 amb un àlies (no un CNAME) cap a l'ALB, que resol directament i no costa consultes addicionals.

  1. Migracions de Flyway a AWS

La definició de tasca va desactivar Flyway (SPRING_FLYWAY_ENABLED=false). Les migracions s'executen com una tasca puntual d'ECS amb la mateixa imatge, abans d'actualitzar el servei:

aws ecs run-task \
  --cluster ciclourbana \
  --task-definition ciclourbana-migracions:1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[subnet-0aa11bb22cc33dd44],securityGroups=[sg-0cc44dd55ee66ff77],assignPublicIp=DISABLED}" \
  --overrides '{"containerOverrides":[{"name":"ciclourbana","command":["java","-Dspring.flyway.enabled=true","-Dspring.main.web-application-type=none","-jar","/app/ciclourbana.jar"]}]}'

web-application-type=none aixeca el context sense Tomcat: Flyway migra i el procés acaba. La canalització de 08-05 espera que la tasca acabi i només continua si el codi de sortida és 0.

Tasca puntual abans del desplegament En arrencar cada tasca
Amb diverses tasques Ja està migrat quan arrenquen Totes competeixen pel bloqueig de Flyway
Si falla El desplegament s'atura; el servei continua amb la versió anterior Totes les tasques noves fallen i ECS les reinicia en bucle
Migració llarga Temps propi, sense sondes de salut a sobre Pot esgotar el període de gràcia i provocar reinicis
Visibilitat Un pas amb el seu codi de sortida Enterrada al log d'arrencada
Permisos La tasca de migració necessita DDL; l'aplicació, no L'aplicació necessita DDL sempre
Complexitat Una definició de tasca més i un pas a la canalització Cap

La tasca puntual és l'opció correcta així que hi ha més d'una rèplica, que és el nostre cas des de desired-count 2. I amb ddl-auto: validate (04-08) intacte: si l'esquema no correspon, la tasca no arrenca en lloc de fallar consulta a consulta.

Recordatori de 08-01: durant el rolling update conviuen la versió antiga i la nova contra l'esquema ja migrat, així que cada migració ha de ser compatible cap enrere (expand/contract, 04-08). És la condició perquè revertir l'aplicació funcioni.

  1. Autoescalat

aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/ciclourbana/ciclourbana-web \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 --max-capacity 6

aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/ciclourbana/ciclourbana-web \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name cpu-70 --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 70.0,
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ECSServiceAverageCPUUtilization"},
    "ScaleInCooldown": 300, "ScaleOutCooldown": 60
  }'

El seguiment d'objectiu afegeix o treu tasques per mantenir la CPU mitjana al 70 %. Els refredaments són asimètrics a propòsit: créixer ràpid (60 s), perquè el cost d'anar curt és servei degradat, i decréixer a poc a poc (300 s) per no oscil·lar davant d'un pic passatger. L'alternativa ALBRequestCountPerTarget escala per peticions per tasca i sol ajustar millor en una API: és un senyal més directe de la càrrega real que la CPU, sobretot quan el temps se'n va esperant la base de dades i la CPU no puja.

I el límit que cal respectar abans d'escalar (08-01): max-capacity × maximum-pool-size + marge < max_connections. Una db.t4g.micro admet de l'ordre de 80-100 connexions; amb 6 tasques i un pool de 10 són 60, més les migracions i l'administració: hi entra, però sense folgança. Pujar max-capacity a 12 sense tocar el pool provocaria too many clients just al pic de trànsit — el pitjor moment possible.

  1. Observabilitat: CloudWatch

El driver awslogs de la definició de tasca envia tot el que l'aplicació escriu a stdout a un grup de CloudWatch Logs. És el factor 11 de 08-01 amb la plataforma fent la seva part.

aws logs tail /ecs/ciclourbana --follow --since 10m
aws logs tail /ecs/ciclourbana --filter-pattern '"ERROR"' --since 1h

Dos ajustos que convé fer des del principi:

# Retencio: sense aixo, els logs es desen per sempre i es facturen per sempre
aws logs put-retention-policy --log-group-name /ecs/ciclourbana --retention-in-days 30

I logs estructurats en JSON, perquè CloudWatch Logs Insights pugui consultar per camps (filter nivell = "ERROR" and estacioId = 3) en lloc de per text. Amb el FiltreRastreig amb MDC de 03-06, cada línia porta el seu identificador de rastreig i reconstruir una petició completa entre diverses tasques és una consulta, no una cerca a ull.

El tema complet —format, correlació, agregació, retenció i cost— és el de 09-05. En mètriques, CloudWatch Container Insights aporta CPU, memòria i recomptes de tasques, i les alarmes es defineixen sobre HTTPCode_Target_5XX_Count, TargetResponseTime o UnHealthyHostCount. Les mètriques de negoci de CicloUrbana —lloguers per minut, estacions sense bicicletes— arriben a 09-03 amb Micrometer.

  1. Camí curt: Elastic Beanstalk

Si l'objectiu és només tenir el JAR corrent a AWS sense aprendre ECS, Beanstalk crea per tu EC2, balancejador, autoescalat i desplegaments:

eb init ciclourbana --platform "Corretto 21 running on 64bit Amazon Linux 2023" --region eu-west-1
eb create ciclourbana-prod --instance-type t3.small --envvars SPRING_PROFILES_ACTIVE=prod
./mvnw clean package -DskipTests
eb deploy
eb logs && eb open

Tres coses que cal saber. El port és el 5000, no el 8080: el proxy nginx de la plataforma hi envia, i es resol amb SERVER_PORT=5000. Per sota hi ha instàncies EC2 reals que cal apedaçar (Beanstalk ho automatitza amb actualitzacions gestionades, però el model mental és diferent del de Fargate). I .ebextensions/ conté fitxers YAML que ajusten la plataforma —variables, opcions del balancejador, comprovació d'estat—:

# .ebextensions/01-ciclourbana.config
option_settings:
  aws:elasticbeanstalk:application:environment:
    SERVER_PORT: 5000
    SPRING_PROFILES_ACTIVE: prod
    JAVA_TOOL_OPTIONS: "-XX:MaxRAMPercentage=75"
  aws:elasticbeanstalk:environment:process:default:
    HealthCheckPath: /actuator/health/readiness

Davant de Fargate, Beanstalk desplega un JAR en lloc d'una imatge, manté instàncies EC2 per sota, dona menys paritat amb el portàtil i menys control de xarxa, i no encaixa amb el que ve a 08-04 i 08-05. Per a CicloUrbana és un bon segon pas des de Heroku i una mala destinació final: conserva la dependència de l'artefacte JAR i d'una plataforma propietària, just el que la imatge de 07-04 va venir a eliminar.

  1. Infraestructura com a codi

Tot l'anterior s'ha fet amb ordres. Funciona una vegada; no és sostenible. Els motius són concrets: ningú no recorda d'aquí a sis mesos per què un grup de seguretat té aquella regla, no hi ha revisió per parells d'un clic, no es pot recrear l'entorn de pre idèntic al de prod, i no hi ha manera de saber què va canviar quan alguna cosa va deixar de funcionar.

La resposta és infraestructura com a codi: declarar els recursos en fitxers versionats a Git.

# infra/ecs.tf — fragment il·lustratiu amb Terraform
resource "aws_ecs_service" "ciclourbana" {
  name            = "ciclourbana-web"
  cluster         = aws_ecs_cluster.principal.id
  task_definition = aws_ecs_task_definition.ciclourbana.arn
  desired_count   = var.repliques         # 1 a pre, 2 a prod
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = aws_subnet.privades[*].id
    security_groups  = [aws_security_group.aplicacio.id]
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.ciclourbana.arn
    container_name   = "ciclourbana"
    container_port   = 8080
  }

  deployment_minimum_healthy_percent = 100
  deployment_maximum_percent         = 200
  health_check_grace_period_seconds  = 90
}
terraform plan     # mostra exactament que canviara, abans de canviar-ho
terraform apply
terraform destroy  # elimina TOT el declarat: la neteja en una ordre

Les tres opcions habituals: CloudFormation (YAML/JSON, només AWS, sense eines externes ni estat que calgui gestionar), CDK (TypeScript, Java o Python, per a qui prefereixi programar la infraestructura; genera CloudFormation per sota) i Terraform/OpenTofu (HCL, el més estès, multiproveïdor i amb un ecosistema enorme).

Aquest terraform plan és l'argument decisiu: veure el canvi abans d'aplicar-lo, revisar-lo en un pull request i tenir l'historial a Git. I terraform destroy converteix la neteja de l'apartat següent en una sola ordre fiable en lloc d'una llista de recursos que cal recordar.

  1. Costos i neteja

Advertiment destacat: RDS i l'ALB es facturen per hora des que existeixen, hi hagi trànsit o no. No hi ha «mode repòs». Un entorn de pràctica oblidat un mes són desenes d'euros reals.

Recurs Configuració Cost aprox./mes
ECS Fargate 2 tasques × 0,5 vCPU + 1 GB ~30 €
RDS db.t4g.micro Una AZ, 20 GB gp3 ~18 €
RDS db.t4g.micro Multi-AZ Rèplica en espera ~36 €
ALB Un, trànsit baix ~18 € + LCU
NAT Gateway Un ~35 € + transferència
VPC endpoints 4 interfícies ~28 €
ECR 5 GB ~0,50 €
Secrets Manager 2 secrets ~0,80 €
CloudWatch Logs 5 GB amb 30 dies ~3 €
Total realista de producció ~110-140 €/mes

El NAT Gateway sorprèn tothom: costa més que les tasques que serveix. Alternatives: VPC endpoints per als serveis que es fan servir (més barat i a més el trànsit no surt de la xarxa d'AWS), o subxarxes públiques amb assignPublicIp=ENABLED només en entorns de pràctica i mai per a la base de dades.

Neteja obligatòria en acabar la pràctica, en aquest ordre exacte:

# 1. Buidar el servei i eliminar-lo
aws ecs update-service --cluster ciclourbana --service ciclourbana-web --desired-count 0
aws ecs delete-service  --cluster ciclourbana --service ciclourbana-web --force

# 2. Balancejador: primer els oients, despres l'ALB, despres el target group
aws elbv2 delete-listener     --listener-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:listener/app/ciclourbana-alb/9z8y7x6w5v4u3t2s/aa11bb22cc33
aws elbv2 delete-load-balancer --load-balancer-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/ciclourbana-alb/9z8y7x6w5v4u3t2s
aws elbv2 delete-target-group  --target-group-arn  arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/ciclourbana-tg/1a2b3c4d5e6f7g8h

# 3. RDS: desactivar la proteccio i esborrar (amb instantania final si interessa)
aws rds modify-db-instance --db-instance-identifier ciclourbana-prod --no-deletion-protection --apply-immediately
aws rds delete-db-instance --db-instance-identifier ciclourbana-prod --final-db-snapshot-identifier ciclourbana-final

# 4. La resta
aws ecs delete-cluster --cluster ciclourbana
aws ecr delete-repository --repository-name ciclourbana --force
aws secretsmanager delete-secret --secret-id ciclourbana/prod/jwt --force-delete-without-recovery
aws logs delete-log-group --log-group-name /ecs/ciclourbana
aws ec2 delete-nat-gateway --nat-gateway-id nat-0a1b2c3d4e5f60718
# i alliberar la IP elastica associada, esborrar VPC endpoints, subxarxes i VPC

Verificació final —el que de debò evita sorpreses—: entra a Billing → Cost Explorer l'endemà i comprova que la despesa diària ha caigut a zero. I configura un pressupost amb alerta de 20 $ (aws budgets create-budget) abans de començar qualsevol pràctica, no després.

Els recursos que més s'obliden i continuen facturant: NAT Gateway, IP elàstiques sense associar, instantànies manuals d'RDS, volums EBS orfes i grups de logs sense retenció.

  1. Seguretat: les regles que no es negocien

  • Mai claus d'accés al codi, a la imatge ni a variables. Els rols IAM donen credencials temporals i rotades. Si trobes un AWS_SECRET_ACCESS_KEY en un repositori, rota-la abans d'esborrar-la: l'historial de Git la conserva.
  • Mínim privilegi, sempre amb ARN concrets. "Resource": "*" és gairebé sempre un error. Fes servir IAM Access Analyzer per detectar permisos concedits i mai utilitzats.
  • El compte arrel no es fa servir: MFA activat, desat, i es treballa amb identitats federades amb MFA obligatori. I CloudTrail activat des del primer dia: el dia que passi alguna cosa estranya, és l'única font de veritat sobre qui va fer què.
  • Xifrat en repòs i en trànsit. --storage-encrypted a RDS (irreversible després), sslmode=require a l'URL JDBC, ELBSecurityPolicy-TLS13-1-2-2021-06 a l'oient i IMMUTABLE a ECR.
  • Res exposat que no ho hagi d'estar. Només l'ALB a subxarxes públiques; grups de seguretat referenciats entre si, no rangs d'adreces; el port 5432 mai obert a Internet.
  • Rotació. El secret del JWT i la contrasenya d'RDS són rotables des de Secrets Manager; convé programar-ho, i fer-ho obligatòriament quan algú amb accés deixa el projecte.

Errors Comuns i Consells

Construir la imatge en Apple Silicon i desplegar-la a Fargate x86. La tasca mor amb exec format error. Fes servir docker buildx build --platform linux/amd64 o construeix a la canalització.

Comprovació d'estat apuntant a /. Retorna 404, l'ALB marca la tasca com a no sana i ECS entra en un bucle d'arrencades. Ha de ser /actuator/health/readiness.

Sense període de gràcia. L'arrencada de la JVM triga 20-40 segons; l'ALB comença a comprovar immediatament i mata la tasca abans que arribi a estar llesta. --health-check-grace-period-seconds 90.

Oblidar la sortida a Internet de les subxarxes privades. La tasca no pot descarregar la imatge d'ECR i falla amb CannotPullContainerError. Calen VPC endpoints o un NAT Gateway.

Secrets com a environment. Queden llegibles a describe-task-definition per a qualsevol amb permisos de lectura. Fes servir secrets amb ARN. I RDS accessible públicament «per provar»: rep atacs automatitzats en minuts i conté dades personals. Mai.

Oblidar el NAT Gateway en netejar. Continua costant 35 €/mes amb zero trànsit.

Consell: aws ecs describe-services és el teu diagnòstic. El camp events explica en text clar per què una tasca no arrenca: imatge no descarregable, salut fallida, sense capacitat, secret inaccessible.

Consell: dos entorns, una plantilla. pre i prod han de sortir del mateix codi de Terraform amb variables diferents (repliques, db_instance_class). És la paritat de 08-01 feta infraestructura.

Consell: etiqueta-ho tot amb Projecte=CicloUrbana. Cost Explorer podrà desglossar la despesa per projecte i trobar recursos orfes serà trivial.

Exercicis

Exercici 1

Dissenya la xarxa completa de CicloUrbana a eu-west-1: VPC, subxarxes, taules de rutes i els tres grups de seguretat amb les seves regles exactes d'entrada i sortida. Justifica on va cada component (ALB, tasques, RDS) i explica com consultaries la base de dades des del teu portàtil sense obrir el port 5432 a Internet.

Exercici 2

Es registra la definició de tasca ciclourbana:1, es crea el servei amb desired-count 2 i cap tasca no arriba a l'estat RUNNING. aws ecs describe-services mostra, a events, aquesta seqüència repetida: service ciclourbana-web has started 1 tasks, després stopped 1 running tasks: Task failed ELB health checks in target-group ciclourbana-tg, i una altra vegada. Els logs de CloudWatch mostren que l'aplicació arrenca correctament i escriu Started CicloUrbanaApplication in 31.4 seconds. Enumera les causes possibles en ordre de probabilitat, digues com distingir entre elles i dona la correcció de cadascuna.

Exercici 3

CicloUrbana porta tres mesos a ECS Fargate amb desired-count 2 i db.t4g.micro. Arriba la Setmana de la Mobilitat de Ribalta i s'espera quadruplicar el trànsit durant cinc dies. Dissenya el pla complet: autoescalat, mida de la base de dades, pool d'HikariCP, estratègia de desplegament durant l'esdeveniment, què vigilar i quant costarà de més. Inclou el pla de tornada a la normalitat.

Solucions

Solució 1

Xarxa. VPC 10.20.0.0/16. Sis subxarxes en dues AZ (eu-west-1a i eu-west-1b):

Subxarxa CIDR AZ Ruta per defecte Conté
publica-a 10.20.0.0/24 a Internet Gateway ALB
publica-b 10.20.1.0/24 b Internet Gateway ALB
privada-app-a 10.20.10.0/24 a NAT o endpoints Tasques Fargate
privada-app-b 10.20.11.0/24 b NAT o endpoints Tasques Fargate
privada-bd-a 10.20.20.0/24 a Cap RDS primària
privada-bd-b 10.20.21.0/24 b Cap RDS en espera

Separar les subxarxes d'aplicació de les de base de dades no és imprescindible però sí bona pràctica: la seva taula de rutes no té cap sortida, així que encara que algú comprometés el motor no podria exfiltrar dades cap a fora.

Grups de seguretat, encadenats:

Grup Entrada Sortida
sg-alb 443 i 80 des de 0.0.0.0/0 8080 cap a sg-app
sg-app 8080 des de sg-alb 5432 cap a sg-rds; 443 cap a endpoints/Internet
sg-rds 5432 des de sg-app Cap

La clau és que cap grup no referencia rangs d'adreces per al trànsit intern: es referencien entre si. Com que les tasques de Fargate canvien d'IP a cada desplegament, amb regles per CIDR caldria actualitzar-les constantment; amb referències per grup, no hi ha res a mantenir.

Accés des del portàtil, sense obrir res. La manera correcta és una instància bastió mínima en subxarxa privada amb SSM Session Manager i reenviament de ports:

aws ssm start-session --target i-0a1b2c3d4e5f60718 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["ciclourbana-prod.abc123xyz.eu-west-1.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["55432"]}'
# Ara: psql -h localhost -p 55432 -U consulta_lectura ciclourbana

Avantatges: no s'obre cap port d'entrada (l'agent SSM inicia la connexió sortint), no hi ha claus SSH que calgui gestionar, i CloudTrail deixa constància de qui va obrir la sessió i quan. I una capa més de mínim privilegi: connectar-se amb un usuari de PostgreSQL de només lectura, no amb el de l'aplicació ni amb el mestre.

Solució 2

El símptoma és inequívoc: l'aplicació arrenca bé però l'ALB no la considera sana. Causes, de més a menys probable:

1. Falta el període de gràcia. L'app triga 31,4 s a arrencar. Sense --health-check-grace-period-seconds, l'ALB comença a comprovar tan bon punt la tasca es registra; amb unhealthy-threshold-count 3 i interval de 15 s, la declara no sana als ~45 s de vida de la tasca... però les comprovacions fallides comencen molt abans i ECS mata la tasca. Distingir: els esdeveniments passen en els primers 60 segons de cada tasca. Corregir: --health-check-grace-period-seconds 90.

2. La ruta de comprovació no respon 200. Si el target group apunta a / (que no existeix) o a /actuator/health amb un indicador no crític caigut, la resposta no és 200. Distingir: llançar una tasca puntual a la mateixa subxarxa i fer curl a la IP privada de la tasca. Corregir: --health-check-path /actuator/health/readiness i comprovar que la cadena de seguretat de 07-01 deixa aquest endpoint públic; si Spring Security el protegeix, retornarà 401 i l'ALB ho interpretarà com una fallada.

3. Port equivocat. El target group apunta al 8080 i l'app escolta al 8081 perquè MANAGEMENT_SERVER_PORT no es va ajustar, o el portMappings no coincideix. Distingir: als logs, la línia Tomcat started on port(s). Corregir: alinear containerPort, portMappings i el port real.

4. Grup de seguretat. Si sg-app no permet el 8080 des de sg-alb, la comprovació no arriba i expira. Distingir: al target group, el motiu és Request timed out en lloc d'un codi HTTP. Corregir: la regla d'entrada per referència de grup.

5. target-type incorrecte. Amb awsvpc, el target group ha de ser --target-type ip, no instance. Amb instance el registre directament no funciona. Distingir: no hi ha destinacions registrades al target group.

Mètode de diagnòstic general: mirar a la consola del target group la columna «Health status details», que diu literalment si va ser Request timed out (xarxa o port), Health checks failed with these codes: [404] (ruta), [401] (seguretat) o [503] (l'app respon però readiness està DOWN, típicament perquè no arriba a RDS).

Solució 3

Autoescalat. Pujar el sostre i baixar el llindar de reacció:

aws application-autoscaling register-scalable-target ... --min-capacity 4 --max-capacity 12

min-capacity 4 durant l'esdeveniment evita que el primer pic del matí trobi només 2 tasques i hagi d'escalar sota pressió. I afegir una política per peticions (ALBRequestCountPerTarget, objectiu 300) a més de la de CPU: en una API que espera la base de dades, les peticions són un senyal més primerenc que la CPU. Amb dues polítiques, ECS pren el màxim de totes dues. Reduir ScaleOutCooldown a 30 s.

Base de dades. db.t4g.micro no aguanta quatre vegades el trànsit: té crèdits de CPU en mode ràfega que s'esgoten i llavors el rendiment cau en picat, que és el pitjor mode de fallada possible. Pujar a db.t4g.medium o db.m6g.large una setmana abans (el canvi requereix una aturada breu, que cal fer en finestra nocturna, no durant l'esdeveniment). Verificar --multi-az actiu. I considerar una rèplica de lectura si les consultes de disponibilitat d'estacions dominen el trànsit, apuntant-hi les lectures.

Pool d'HikariCP. El càlcul de 08-01 amb els números nous: 12 tasques × pool + marge < max_connections. Una db.t4g.medium admet unes 340 connexions. Amb pool de 10 són 120 més marge: hi entra folgadament. Amb db.t4g.micro (unes 85) no hi entraria i aquest és un segon motiu, independent del rendiment, per pujar de classe. Deixar maximum-pool-size: 10 i connection-timeout: 3000 perquè, si hi hagués saturació, les peticions fallin ràpid en lloc d'acumular-se.

Estratègia de desplegament durant l'esdeveniment. Congelació de canvis: res a producció durant els cinc dies llevat de correccions crítiques. Si cal desplegar, minimumHealthyPercent=100 i maximumPercent=200 com sempre, fora de les hores punta (matinada), amb la tasca de migració executada abans i sense migracions destructives — només expand, mai contract (04-08).

Què vigilar, amb alarmes creades abans de l'esdeveniment:

Mètrica Llindar Per què
HTTPCode_Target_5XX_Count > 1 % de les peticions Senyal principal de degradació
TargetResponseTime p99 > 1,5 s Latència percebuda
UnHealthyHostCount > 0 durant 2 min Tasques caient
CPUUtilization d'RDS > 80 % La base de dades és el coll d'ampolla habitual
DatabaseConnections > 70 % del màxim Avís primerenc de too many clients
CPUCreditBalance (si continua en t4g) Baixant La fallada silenciosa de les instàncies en ràfega
hikaricp.connections.pending > 0 sostingut El pool es queda curt (mètriques de 09-03)

Cost addicional estimat: passar de 2 a una mitjana de ~7 tasques durant 5 dies són uns 12-15 € extra; pujar RDS de micro a medium durant dues setmanes, uns 25-30 €; més trànsit de l'ALB i logs, uns pocs euros. Total: 40-50 €, xifra perfectament defensable davant del cost reputacional que el servei caigui en la setmana estrella de la ciutat.

Tornada a la normalitat. Baixar min-capacity a 2 i max-capacity a 6; esperar una setmana amb la instància gran abans de reduir-la, perquè el trànsit sol quedar-se per sobre del punt de partida, i fer-ho en finestra nocturna; retirar la rèplica de lectura si se'n va crear una; revisar Cost Explorer per confirmar que tot allò temporal ha desaparegut; i escriure la retrospectiva —què es va saturar primer, quina alarma va avisar a temps i quina no—, que és el que fa que l'esdeveniment de l'any vinent sigui rutina.

Conclusió

CicloUrbana ja no viu en una plataforma prestada: corre sobre una infraestructura pròpia, en una regió europea, amb la base de dades on Internet no arriba. Saps quins serveis d'AWS poden executar una aplicació Spring Boot i per què el curs tria ECS Fargate + RDS —sense servidors per administrar, destinació natural de la imatge de 07-04, xarxa privada de debò i molt més simple que Kubernetes per a una sola aplicació—, amb Elastic Beanstalk documentat com a camí curt i Lambda descartada amb arguments.

Manegues els conceptes que sostenen tota la resta: regió i zones de disponibilitat, VPC amb subxarxes públiques i privades, grups de seguretat referenciats entre si en lloc de rangs d'adreces, rols i polítiques IAM amb ARN concrets, ALB, Route 53 i ACM. Has publicat la imatge a ECR amb escaneig automàtic i etiquetes immutables —cosa que fa exacta la reversió de 08-01—, i saps que Fargate espera linux/amd64. Has creat RDS PostgreSQL 16 xifrat, Multi-AZ, amb còpies de set dies, finestra de manteniment nocturna, protecció contra esborrat i, sobretot, sense adreça pública, accessible únicament des del grup de seguretat de l'aplicació i consultable des de fora només per Session Manager. Els secrets viuen a Secrets Manager, entren al contenidor pel bloc secrets de la definició de tasca —mai com a variables en text pla— i l'aplicació continua llegint ${JWT_SECRET} sense assabentar-se que existeix AWS.

La definició de tasca reuneix tot l'après: CPU i memòria dimensionades amb MaxRAMPercentage, dos rols IAM amb responsabilitats diferents, zona horària de Ribalta, logs a CloudWatch pel driver awslogs i stopTimeout folgat perquè hi càpiga l'aturada ordenada de 01-05. El servei manté dues tasques repartides entre zones, l'ALB comprova /actuator/health/readiness —el moment en què les sondes de 07-01 troben el seu lloc—, termina el TLS amb un certificat d'ACM que es renova sol, redirigeix el port 80 al 443 i espera 30 segons abans de desregistrar una tasca per no produir 502. El desplegament és rolling amb minimumHealthyPercent=100, les migracions de Flyway s'executen com a tasca puntual abans d'actualitzar el servei, i l'autoescalat creix ràpid i decreix a poc a poc, sempre dins del límit de connexions de PostgreSQL.

I saps què costa: uns 110-140 € al mes, amb el NAT Gateway costant més que les mateixes tasques i tot facturat per hora encara que ningú no faci servir l'aplicació. Per això tens la llista exacta de recursos a destruir en el seu ordre correcte, el pressupost amb alerta creat abans de començar, i les regles de seguretat que no es negocien —zero claus d'accés, mínim privilegi amb ARN concrets, xifrat en repòs i en trànsit, CloudTrail i rotació programada—, juntament amb la nota de fons que la consola web no és manera de gestionar producció: infraestructura com a codi, revisada en un pull request i destruïble amb una ordre.

Queda una peça del panorama de 08-01 per recórrer. Quan la xarxa de Ribalta deixi de ser una aplicació i en siguin cinc, amb tres equips desplegant pel seu compte i l'ajuntament preguntant si això es pot moure a un altre proveïdor, la resposta és un orquestrador. La lliçó següent, Desplegant a Kubernetes, ho aborda amb honestedat —començant per advertir que per a una sola aplicació com CicloUrbana sol ser complexitat no justificada— i amb els manifestos complets: Deployment, Service, Ingress, ConfigMap, Secret, un Job per a les migracions, HPA per escalar, un chart de Helm per no duplicar res, i les tres sondes —liveness, readiness i startup— apuntant per fi als grups de salut que vam definir a 07-01.

Curs de Spring Boot

Mòdul 1: Introducció a Spring Boot

Mòdul 2: Conceptes bàsics de Spring Boot

Mòdul 3: Construint serveis web RESTful

Mòdul 4: Accés a dades amb Spring Boot

Mòdul 5: Seguretat a Spring Boot

Mòdul 6: Proves a Spring Boot

Mòdul 7: Funcions avançades de Spring Boot

Mòdul 8: Desplegament d'aplicacions Spring Boot

Mòdul 9: Rendiment i monitoratge

Mòdul 10: Millors pràctiques i consells

© Copyright 2026. Tots els drets reservats