El mòdul 9 va deixar una infraestructura reproduïble, governada i repartida en cinc comptes, i una frase incòmoda al final: la botiga de MercadoFresco continua vivint en instàncies EC2 que cal apedaçar, sobre una AMI que cal reconstruir cada vegada que canvia una dependència, i que triguen dos minuts a arrencar. El divendres a les 19:00, amb 900 comandes per hora, aquests dos minuts són exactament el temps que els clients passen esperant mentre l'ASG fa la seva feina.

Aquesta lliçó ataca el problema per l'arrel: substituir la màquina pel contenidor. Veuràs què és un contenidor i en què es diferencia de debò d'una màquina virtual, com es construeix la imatge de la botiga amb un Dockerfile seriós, on es guarda aquesta imatge (Amazon ECR) i qui l'executa (Amazon ECS). Al final tindràs la botiga i els treballadors de la cua de comandes contenidoritzats, amb la seva definició de tasca, el seu servei darrere de l'ALB que ja existeix i el seu autoescalat.

Avís de cost. Amazon ECS no costa res per si mateix: és un pla de control gratuït, i només pagues el còmput que executa les tasques —les instàncies EC2 en aquesta lliçó, Fargate a 10-02—. Amazon ECR cobra 0,10 USD per GB i mes d'emmagatzematge i la transferència de dades cap a fora de la regió; la capa gratuïta inclou 500 MB durant 12 mesos. L'escaneig bàsic és gratuït; l'escaneig millorat amb Amazon Inspector es cobra per imatge escanejada. Segueix tots els exercicis amb etiquetes correctes i esborra al final el que hagis creat. Totes les dades, comptes i identificadors són ficticis.

Contingut

  1. Els tres problemes que va deixar el mòdul 9
  2. Què és un contenidor i en què es diferencia d'una màquina virtual
  3. Imatge, capes, registre i contenidor en execució
  4. El Dockerfile de la botiga de MercadoFresco
  5. .dockerignore, mida i memòria cau de capes
  6. Amazon ECR: el registre que substitueix les AMI
  7. Etiquetatge, immutabilitat i cicle de vida de les imatges
  8. Escaneig de vulnerabilitats: bàsic i millorat
  9. Replicació, xifratge i política de repositori
  10. Amazon ECS: clúster, definició de tasca, tasca i servei
  11. Tipus de llançament, agent d'ECS i proveïdors de capacitat
  12. La definició de tasca de la botiga, comentada
  13. Rol d'execució de la tasca enfront del rol de la tasca
  14. Mode de xarxa awsvpc i què implica
  15. El servei: ALB, comprovacions d'estat i col·locació
  16. Desplegaments: renovats, percentatges i interruptor de desplegament
  17. Autoescalat del servei i descobriment amb Cloud Map
  18. Observabilitat: Container Insights, registres i X-Ray
  19. La migració de MercadoFresco
  20. Cost i neteja
  21. Errors habituals i consells
  22. Exercicis
  23. Conclusió

Els tres problemes que va deixar el mòdul 9

Convé anomenar-los amb precisió abans de resoldre'ls, perquè cadascun té una causa diferent i el contenidor els resol per camins diferents.

Problema Què passa avui a MercadoFresco Cost real
Apedaçament Cada instància de l'ASG porta un sistema operatiu complet que rep CVE d'OpenSSL, del kernel i de glibc Una finestra de manteniment al mes, amb reinicis esglaonats
Reconstrucció d'AMI Afegir una llibreria de Python obliga a llançar una instància, instal·lar-la, crear l'AMI i actualitzar la plantilla de llançament 40-60 minuts per canvi de dependència
Arrencada de dos minuts Arrencar EC2, executar cloud-init, arrencar l'aplicació, superar les comprovacions del grup de destinació El pic dels divendres arriba abans que la capacitat

El contenidor no elimina el sistema operatiu del planeta —continua havent-hi un kernel a sota—, però canvia on viu la frontera entre allò que és teu i allò que és d'AWS. L'apedaçament del sistema operatiu base passa a ser una línia del Dockerfile i una reconstrucció de trenta segons, no una finestra de manteniment; amb Fargate (10-02), l'amfitrió deixa d'existir per a tu. L'AMI desapareix com a artefacte i el seu lloc l'ocupa la imatge, que es construeix a CodeBuild en dos minuts, es versiona per commit i es guarda en un registre. I l'arrencada deixa d'incloure l'arrencada d'una màquina: una tasca d'ECS sobre una instància existent arrenca en 5-15 segons, i sobre Fargate en 30-45 inclosa l'ENI. Enfront dels 120 segons actuals, és una altra categoria de resposta.

Què és un contenidor i en què es diferencia d'una màquina virtual

Un contenidor és un procés del sistema operatiu amfitrió a qui el kernel ha mentit sobre el món: veu el seu propi sistema de fitxers, la seva pròpia taula de processos, la seva pròpia xarxa i els seus propis límits de CPU i memòria. Aquesta mentida la construeixen dos mecanismes del kernel de Linux: els namespaces (aïllament del que es veu) i els cgroups (limitació del que es consumeix).

Una màquina virtual és una altra cosa: un hipervisor emula maquinari complet i a dins arrenca un kernel propi, amb el seu sistema d'arrencada, els seus serveis i el seu sistema de fitxers.

Aspecte Màquina virtual (EC2) Contenidor
Aïllament Maquinari virtualitzat; kernel propi. Frontera molt forta Namespaces i cgroups; kernel compartit. Frontera forta però menor
Arrencada 30 s - 2 min (BIOS, kernel, systemd, cloud-init) 0,1 - 2 s (és llançar un procés)
Mida AMI de 2-8 GB Imatge de 80-400 MB ben construïda
Densitat Unitats per servidor físic Desenes o centenars per servidor
Sobrecàrrega Un sistema operatiu complet per instància Només el procés i les seves llibreries
Portabilitat AMI lligada a la regió i al proveïdor La imatge corre igual al portàtil, a ECS, a EKS o en un altre proveïdor
Immutabilitat Possible, però costa: cal reconstruir l'AMI Natural: la imatge no es modifica, se substitueix
Estat Persisteix entre reinicis (EBS) Efímer per definició; l'estat va fora

La diferència pràctica que més importa per a MercadoFresco és a dues files: arrencada i portabilitat. L'arrencada resol el pic dels divendres. La portabilitat resol la frase que el Luis repeteix des del mòdul 8: «a la meva màquina funciona». Amb una imatge, a la seva màquina funciona exactament el mateix que a producció, byte a byte, perquè és el mateix artefacte.

I un advertiment honest sobre l'aïllament, perquè el màrqueting sol ometre'l: els contenidors comparteixen el kernel de l'amfitrió. Una vulnerabilitat d'escapada de contenidor permet, en teoria, saltar d'un contenidor a un altre a la mateixa màquina. Per això AWS no executa contenidors de clients diferents al mateix kernel: Fargate (10-02) dona a cada tasca la seva pròpia frontera de virtualització lleugera. Per a MercadoFresco, on tots els contenidors són seus, el risc és acceptable; per a una plataforma multiinquilí, no ho seria.

graph TB
  subgraph VM["Model de maquina virtual"]
    H1[Hardware] --> HV[Hipervisor]
    HV --> V1["VM 1<br/>SO complet<br/>+ app"]
    HV --> V2["VM 2<br/>SO complet<br/>+ app"]
  end
  subgraph CT["Model de contenidor"]
    H2[Hardware] --> SO["SO amfitrio<br/>+ kernel compartit"]
    SO --> RT[Runtime de contenidors]
    RT --> C1["Contenidor 1<br/>app + llibreries"]
    RT --> C2["Contenidor 2<br/>app + llibreries"]
    RT --> C3["Contenidor 3<br/>app + llibreries"]
  end

Imatge, capes, registre i contenidor en execució

Quatre paraules que es confonen contínuament i que convé fixar:

  • Imatge: un artefacte de només lectura que conté el sistema de fitxers i les metadades necessàries per arrencar el procés. És immutable. S'identifica per un digest (sha256:...) i, opcionalment, per una o diverses etiquetes.
  • Capa: una imatge no és un bloc monolític sinó una pila de capes, cadascuna amb les diferències que va introduir una instrucció del Dockerfile. Les capes es comparteixen entre imatges: si deu imatges fan servir python:3.12-slim, aquesta capa s'emmagatzema i es descarrega una sola vegada.
  • Registre: el magatzem d'imatges. Amazon ECR és el registre gestionat d'AWS; Docker Hub és el registre públic més conegut.
  • Contenidor: una imatge en execució, amb una capa d'escriptura efímera a sobre. En aturar el contenidor, aquesta capa desapareix.

La conseqüència operativa de les capes és enorme i defineix com s'escriu un Dockerfile: si una capa canvia, totes les de sota s'invaliden. Posar COPY . . abans de pip install significa que qualsevol canvi en qualsevol fitxer del projecte obliga a reinstal·lar totes les dependències. Posar primer el requirements.txt i després el codi significa que un canvi de codi reaprofita la capa de dependències i la construcció baixa de tres minuts a vint segons.

El Dockerfile de la botiga de MercadoFresco

La botiga és una aplicació Python amb Gunicorn darrere de l'ALB. Aquest és el seu Dockerfile complet, amb construcció multietapa, usuari sense privilegis, versions fixades i un HEALTHCHECK coherent amb el /salud que fa servir tg-mercadofresco-tienda des del mòdul 3.

# ===== Etapa 1: construccio. Aqui viu el compilador; no arriba a produccio. =====
FROM python:3.12.4-slim-bookworm AS constructor

# Eines que necessiten psycopg2 i algunes rodes natives: es queden en aquesta etapa.
RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential=12.9 libpq-dev=15.* \
    && rm -rf /var/lib/apt/lists/*

# Entorn virtual propi: es copia sencer a l'etapa final en un sol COPY.
RUN python -m venv /opt/entorno
ENV PATH="/opt/entorno/bin:$PATH"
WORKDIR /construccion

# --- Capa de dependencies, PRIMER i sola: es reutilitza mentre no canviin
# les dependencies, encara que canvii tot el codi de la botiga. ---
COPY requirements.txt requirements-bloqueo.txt ./
RUN pip install --no-cache-dir --require-hashes -r requirements-bloqueo.txt

# ===== Etapa 2: execucio. Minima, sense compiladors, sense root. =====
FROM python:3.12.4-slim-bookworm AS execucio

LABEL org.opencontainers.image.title="mercadofresco-tienda" \
      org.opencontainers.image.vendor="MercadoFresco" \
      org.opencontainers.image.source="https://github.com/mercadofresco/mercadofresco-tienda"

# Nomes la llibreria de client de PostgreSQL, no el -dev amb les capceleres.
RUN apt-get update && apt-get install -y --no-install-recommends libpq5=15.* curl=7.88.* \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd --gid 10001 tienda \
    && useradd --uid 10001 --gid tienda --no-create-home --shell /usr/sbin/nologin tienda

# L'entorn virtual complet arriba de l'etapa anterior: una sola capa.
COPY --from=constructor /opt/entorno /opt/entorno
ENV PATH="/opt/entorno/bin:$PATH" PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app

# El codi, al final: es el que canvia a cada commit.
COPY --chown=tienda:tienda ./tienda ./tienda
COPY --chown=tienda:tienda ./gunicorn.conf.py ./
USER 10001
EXPOSE 8080

# Coherent amb la comprovacio d'estat del grup de destinacio: mateix cami, mateix port.
HEALTHCHECK --interval=15s --timeout=3s --start-period=20s --retries=3 \
  CMD curl --fail --silent http://127.0.0.1:8080/salud || exit 1

ENTRYPOINT ["gunicorn"]
CMD ["--config", "gunicorn.conf.py", "tienda.wsgi:aplicacion"]

Cada decisió d'aquest fitxer respon a un problema concret:

  • python:3.12.4-slim-bookworm amb versió completa, no python:3.12 ni python:latest. Una etiqueta flotant converteix una construcció reproduïble en una loteria: la mateixa ordre, executada dues setmanes després, produeix una altra imatge. És la disciplina del bloqueig d'aws-cdk-lib a 09-02. I slim en lloc de la imatge completa baixa d'~1,0 GB a ~130 MB: la diferència és documentació i utilitats que a producció són superfície d'atac, no funcionalitat.
  • Multietapa. build-essential i libpq-dev pesen més de 300 MB i només calen per compilar. Amb dues etapes es queden a la primera. Una imatge de producció amb compilador és una imatge on un atacant pot compilar.
  • --require-hashes amb un fitxer de bloqueig. Fixa no només la versió sinó el hash de cada roda descarregada. Protegeix de l'atac de substitució de paquet a l'índex.
  • USER 10001 amb un UID numèric explícit. ECS i Kubernetes poden imposar «no executar com a root»; un UID numèric permet verificar la regla sense resoldre noms, i el --no-create-home --shell /usr/sbin/nologin evita que aquest usuari serveixi per a res més.
  • HEALTHCHECK amb --start-period. Els 20 segons de gràcia eviten que el contenidor es marqui com a no sa mentre Gunicorn aixeca els seus processos fills. A ECS és complementari al del grup de destinació: el del contenidor decideix si ECS reinicia la tasca; el de l'ALB decideix si li envia trànsit.
  • ENTRYPOINT + CMD separats. L'ENTRYPOINT fixa l'executable i el CMD els arguments per omissió, de manera que la definició de tasca pot sobreescriure només els arguments (command) sense canviar el binari.

.dockerignore, mida i memòria cau de capes

El .dockerignore és el fitxer que més gent oblida i el que més problemes causa. Sense ell, COPY . . fica dins la imatge el directori .git complet —que pot pesar centenars de megues i conté l'historial, inclosos secrets esborrats—, l'entorn virtual local, la memòria cau de proves i els fitxers .env amb credencials de desenvolupament.

.git          .gitignore     .github
.venv         venv           __pycache__    *.pyc    *.pyo
.pytest_cache .mypy_cache    .coverage      htmlcov
.env          .env.*         *.log
infra-cdk     cdk.out        docs           README.md
Dockerfile    .dockerignore

Al fitxer real va una entrada per línia; aquí s'agrupen per família. Regles de mida i memòria cau que s'apliquen en aquest ordre:

Regla Efecte Aplicació a MercadoFresco
Base mínima -80 % de mida slim en comptes de la completa; distroless si no calgués curl
Multietapa Treu compiladors Etapa constructor amb build-essential
Ordenar d'estable a volàtil Màxima reutilització de memòria cau requirements.txt abans que el codi
Agrupar RUN amb && Menys capes, sense residus apt-get update && install && rm -rf /var/lib/apt/lists/*
Netejar a la mateixa capa El que s'esborra en una altra capa continua ocupant El rm -rf va enganxat a l'apt-get, no en un RUN a part
--no-cache-dir a pip -50-150 MB A l'etapa de construcció
.dockerignore complet Evita context enorme i fuites El de dalt

El punt que més sorprèn és el de «netejar a la mateixa capa». Com que cada capa guarda diferències, esborrar a la capa 7 un fitxer creat a la capa 5 no redueix la mida de la imatge: el fitxer continua a la capa 5 i es continua descarregant. Només desapareix de la vista. Un RUN apt-get install ... seguit d'un RUN rm -rf /var/lib/apt/lists/* en línia a part no estalvia res.

A CodeBuild (08-02), la memòria cau s'aprofita amb docker pull "$REPO:cache" || true seguit de docker build --cache-from "$REPO:cache" --build-arg BUILDKIT_INLINE_CACHE=1 ..., apuntant a la imatge anterior del registre. Amb aquest patró, la construcció de la botiga baixa d'uns 3 minuts a 35-50 segons quan només canvia el codi, que és el cas habitual.

Amazon ECR: el registre que substitueix les AMI

Amazon Elastic Container Registry és el registre d'imatges gestionat d'AWS: privat per omissió, integrat amb IAM, amb xifratge en repòs, escaneig de vulnerabilitats, replicació i polítiques de cicle de vida. Ocupa exactament el lloc que ocupava l'AMI a l'arquitectura anterior, amb dues diferències: és regional però replicable, i la unitat de versió és el digest, no un identificador opac. MercadoFresco crea dos repositoris al compte d'eines 555566667777, que és on viu el pipeline des de 09-04:

for REPO in mercadofresco/tienda mercadofresco/trabajadores; do
  aws ecr create-repository --repository-name "$REPO" --region eu-west-1 \
    --image-tag-mutability IMMUTABLE \
    --image-scanning-configuration scanOnPush=true \
    --encryption-configuration '{"encryptionType":"KMS","kmsKey":"alias/mercadofresco-datos"}' \
    --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=compartido \
           Key=Componente,Value=registro-imagenes Key=Propietario,Value=plataforma \
           Key=CentroCoste,Value=tecnologia
done

# L'autenticacio no fa servir contrasenyes guardades: testimoni temporal d'IAM lliurat a Docker.
aws ecr get-login-password --region eu-west-1 \
  | docker login --username AWS --password-stdin 555566667777.dkr.ecr.eu-west-1.amazonaws.com

El testimoni dura 12 hores i va associat al principal d'IAM que va fer la crida. A CodeBuild, la línia anterior és literalment la primera de la fase pre_build del buildspec.yml, i el rol del projecte de compilació necessita ecr:GetAuthorizationToken (que és a nivell de compte, amb Resource: "*") més els permisos d'escriptura sobre el repositori concret.

version: 0.2
env:
  variables: { REGION: eu-west-1, COMPTE_EINES: "555566667777", REPOSITORI: mercadofresco/tienda }
phases:
  pre_build:
    commands:
      - REGISTRE="${COMPTE_EINES}.dkr.ecr.${REGION}.amazonaws.com"; URI="${REGISTRE}/${REPOSITORI}"
      - SHA_CURT=$(echo "$CODEBUILD_RESOLVED_SOURCE_VERSION" | cut -c1-7)
      - ETIQUETA="${VERSIO_SEMANTICA}-${SHA_CURT}"       # p. ex. v1.6.0-a3f9c21
      - aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin "$REGISTRE"
  build:
    commands:
      - docker build --cache-from "${URI}:cache" --build-arg BUILDKIT_INLINE_CACHE=1 -t "${URI}:${ETIQUETA}" -t "${URI}:cache" .
      - docker run --rm "${URI}:${ETIQUETA}" python -c "import tienda; print(tienda.__version__)"
  post_build:
    commands:
      - docker push "${URI}:${ETIQUETA}" && docker push "${URI}:cache"
      # El digest es el que es desplega de debo; es passa a la fase seguent.
      - DIGEST=$(aws ecr describe-images --repository-name "$REPOSITORI" --image-ids imageTag="$ETIQUETA" --query 'imageDetails[0].imageDigest' --output text)
      - printf '{"imatge":"%s@%s","etiqueta":"%s"}' "$URI" "$DIGEST" "$ETIQUETA" > imatge.json
artifacts:
  files: [imatge.json]

Etiquetatge, immutabilitat i cicle de vida de les imatges

latest és una etiqueta com qualsevol altra: no significa «la més recent», només significa «la que algú va etiquetar així l'última vegada». I com a etiqueta de desplegament és directament perillosa:

Problema de latest Conseqüència concreta
No és reproduïble «Despleguem latest» no diu quin codi hi ha a producció
No es pot revertir Tornar enrere exigeix saber quina era l'anterior, i latest ja no ho és
Trenca el reinici Una tasca que es reinicia descarrega una altra imatge diferent de les seves germanes
Convivència mixta Amb imagePullPolicy agressiu, unes tasques executen una versió i altres, una altra
Auditoria impossible CloudTrail registra un desplegament, però no què es va desplegar

MercadoFresco fa servir l'esquema que ja va introduir el mòdul 8: versió semàntica més SHA curt del commit, per exemple v1.6.0-a3f9c21. La versió semàntica la llegeix un humà; el SHA la lliga a un commit exacte del repositori mercadofresco-tienda. I a la definició de tasca que es desplega, no es fa servir l'etiqueta sinó el digest:

555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/tienda@sha256:9c1e...f4a2

La raó és subtil però decisiva: una etiqueta és un punter mutable —encara que la immutabilitat la protegeixi dins del repositori, una fallada de configuració o una replicació mal feta poden desalinear-la—, mentre que el digest és el contingut. Desplegar per digest garanteix que allò que arrenca avui a producció és exactament allò que va passar les proves ahir a preproducció, bit a bit. L'etiqueta serveix perquè un humà trobi la imatge; el digest, perquè la màquina la desplegui.

Amb --image-tag-mutability IMMUTABLE, ECR rebutja qualsevol intent de reutilitzar una etiqueta ja existent. Això converteix un accident silenciós —sobreescriure v1.6.0-a3f9c21 amb un altre contingut— en un error de compilació visible.

L'altre control imprescindible és la política de cicle de vida. Sense ella, el repositori creix indefinidament: MercadoFresco desplega 4,8 vegades per setmana, amb imatges d'uns 220 MB, i sumant-hi les de les branques de característica el registre engreixa uns 6 GB a l'any per repositori. No són diners, però sí que és soroll i sí que complica les auditories.

{"rules": [
  {"rulePriority": 1, "description": "Conservar les 10 ultimes versions publicades", "action": {"type": "expire"},
   "selection": {"tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 10}},
  {"rulePriority": 2, "description": "Imatges de branca de caracteristica: 14 dies", "action": {"type": "expire"},
   "selection": {"tagStatus": "tagged", "tagPrefixList": ["rama-"], "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 14}},
  {"rulePriority": 3, "description": "Capes orfes sense etiqueta: 1 dia", "action": {"type": "expire"},
   "selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 1}}
]}

Tres advertiments sobre aquestes polítiques, que són la font número u de sotracs amb ECR:

  1. Les regles s'avaluen per prioritat i una imatge només l'afecta la primera que la selecciona. Una regla àmplia amb prioritat 1 anul·la tot el que vingui darrere.
  2. L'expiració no comprova si la imatge s'està fent servir. ECR esborrarà la imatge que està executant producció si la regla la selecciona. Per això la regla 1 conserva deu versions publicades i no dues: cal cobrir el pitjor cas de reversió.
  3. Es prova primer en sec. La consola ofereix una vista prèvia de la política sobre el contingut real del repositori; executar-la abans d'aplicar és obligatori.

Escaneig de vulnerabilitats: bàsic i millorat

ECR ofereix dos modes d'escaneig, i la diferència entre tots dos és més gran del que suggereix el nom.

Aspecte Escaneig bàsic Escaneig millorat (Amazon Inspector)
Motor Base de dades de CVE del sistema operatiu Amazon Inspector, continu
Abast Paquets del sistema operatiu Sistema operatiu i dependències de l'aplicació (Python, npm, Java, Go)
Quan En empènyer la imatge (o manual) En empènyer i de manera contínua en aparèixer CVE noves
Abast de compte Per repositori A nivell de registre, s'activa per a tot el compte
Integració Consola i API Security Hub, EventBridge, tauler d'Inspector
Cost Gratuït Es cobra per imatge escanejada i per reescaneig

Per a MercadoFresco importa la fila d'«abast»: la majoria de les vulnerabilitats reals de la botiga són a dependències de Python —una versió antiga d'una llibreria de serialització, per exemple—, i l'escaneig bàsic no les veu. La Marta activa l'escaneig millorat al compte d'eines i connecta el resultat a la porta de qualitat del pipeline:

aws ecr put-registry-scanning-configuration --scan-type ENHANCED --region eu-west-1 \
  --rules '[{"scanFrequency": "CONTINUOUS_SCAN",
             "repositoryFilters": [{"filter": "mercadofresco/*", "filterType": "WILDCARD"}]}]'

# La regla de bloqueig que executa la Lambda mercadofresco-puerta-calidad (modul 8):
aws ecr describe-image-scan-findings --repository-name mercadofresco/tienda \
  --image-id imageTag=v1.6.0-a3f9c21 \
  --query 'imageScanFindingsSummary.findingSeverityCounts'
# {"HIGH": 0, "MEDIUM": 3, "LOW": 12}   -> passa: la regla bloqueja CRITICAL i HIGH

La política acordada és pragmàtica i per això es compleix: CRITICAL bloqueja sempre; HIGH bloqueja excepte excepció documentada amb data de caducitat; MEDIUM i LOW generen un tiquet. Una política que bloqueja amb MEDIUM no sobreviu dues setmanes: algú la desactiva i llavors no en queda cap. L'escaneig continu té a més una propietat que el bàsic no pot donar: la imatge que avui és neta i demà no. Quan apareix una CVE nova que afecta una imatge ja desplegada, Inspector emet un esdeveniment a EventBridge i bus-mercadofresco (07-03) l'encamina cap a alertas-mercadofresco. Aquest avís és el substitut directe del cicle d'apedaçament de les instàncies.

Replicació, xifratge i política de repositori

La imatge es construeix al compte d'eines 555566667777 i s'executa a producció 111122223333, preproducció 222233334444 i desenvolupament 333344445555. Hi ha dues maneres de resoldre-ho:

Opció Com funciona Quan convé
Repositori central compartit Un sol repositori a eines amb política que autoritza els comptes de càrrega Menys còpies, una font de veritat, dependència d'un compte
Replicació entre comptes ECR copia la imatge a un repositori a cada compte destinació Aïlla fallades, permet polítiques diferents, duplica emmagatzematge

MercadoFresco fa servir repositori central compartit per a desenvolupament i preproducció, i replicació cap a producció, perquè vol que un incident al compte d'eines no impedeixi que producció escali el divendres a la tarda.

aws ecr put-replication-configuration --region eu-west-1 --replication-configuration '{
  "rules": [{"destinations": [{"region": "eu-west-1",    "registryId": "111122223333"},
                              {"region": "eu-central-1", "registryId": "111122223333"}],
             "repositoryFilters": [{"filter": "mercadofresco/", "filterType": "PREFIX_MATCH"}]}]}'

La segona línia de destinació replica a més a eu-central-1, que és on MercadoFresco tindria el seu pla de recuperació si la regió principal fallés. La replicació és asíncrona i triga de segons a alguns minuts segons la mida; el pipeline ha d'esperar que la imatge existeixi a destinació abans de desplegar, amb un wait explícit.

La política de repositori autoritza els comptes de l'organització a descarregar, sense autoritzar ningú a pujar:

{"Version": "2012-10-17", "Statement": [
  {"Sid": "DescargaDesdeLaOrganizacion", "Effect": "Allow", "Principal": "*",
   "Action": ["ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage", "ecr:BatchCheckLayerAvailability"],
   "Condition": {"StringEquals": {"aws:PrincipalOrgID": "o-a1b2c3d4e5"}}},
  {"Sid": "SoloElPipelinePublica", "Effect": "Allow",
   "Principal": {"AWS": "arn:aws:iam::555566667777:role/rol-build-mercadofresco-tienda"},
   "Action": ["ecr:PutImage", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload"]}
]}

És el mateix patró de 09-04: aws:PrincipalOrgID en lloc d'una llista de comptes que caldria mantenir a mà. I la separació entre qui llegeix i qui escriu és la que converteix el pipeline en l'únic camí cap a producció. El xifratge amb alias/mercadofresco-datos (04-02) té una conseqüència que sorprèn: si la clau és del compte d'eines i producció ha de descarregar la imatge, la política de la clau KMS ha d'autoritzar els principals de producció a fer servir kms:Decrypt. És l'error clàssic de les claus gestionades pel client entre comptes: la política del repositori està bé i la descàrrega falla igualment, amb un missatge que parla d'accés denegat i no esmenta KMS.

Amazon ECS: clúster, definició de tasca, tasca i servei

Amazon Elastic Container Service és l'orquestrador de contenidors propi d'AWS. El seu model té quatre objectes i convé no confondre'ls mai:

Objecte Què és Analogia amb el que ja s'ha vist
Clúster Agrupació lògica on s'executen les tasques Semblant a un ASG més el seu entorn
Definició de tasca La plantilla immutable i versionada: quines imatges, quanta CPU i memòria, quina xarxa, quins rols La plantilla de llançament lt-mercadofresco-tienda
Tasca Una instància en execució d'una definició de tasca; un o diversos contenidors que viuen i moren junts Una instància EC2 de l'ASG
Servei Manté N tasques en execució, les registra a l'ALB, les reposa i les desplega L'ASG asg-mercadofresco-tienda

La correspondència amb el que MercadoFresco ja té és gairebé un a un, i és la millor manera d'entendre ECS: el servei fa el que feia l'ASG, la definició de tasca fa el que feia la plantilla de llançament més l'AMI, i la tasca substitueix la instància. Les definicions de tasca són versionades i immutables: cada register-task-definition crea una revisió nova (mercadofresco-tienda:23) i les anteriors continuen existint. Revertir un desplegament és apuntar el servei a la revisió anterior, i això és un canvi d'un camp.

graph LR
  TD["Definicio de tasca<br/>mercadofresco-tienda:23"] --> SRV["Servei<br/>svc-mercadofresco-tienda<br/>desiredCount = 4"]
  SRV --> T1["Tasca 1<br/>AZ a"]
  SRV --> T2["Tasca 2<br/>AZ a"]
  SRV --> T3["Tasca 3<br/>AZ b"]
  SRV --> T4["Tasca 4<br/>AZ b"]
  ALB["alb-mercadofresco-tienda"] --> TG["tg-mercadofresco-tienda<br/>tipus de destinacio: ip"]
  TG --> T1
  TG --> T2
  TG --> T3
  TG --> T4
  ECR["ECR<br/>mercadofresco/tienda"] -.imatge.-> TD
  CL["Cluster<br/>ecs-mercadofresco"] --- SRV

Tipus de llançament, agent d'ECS i proveïdors de capacitat

ECS executa les tasques de dues maneres, i l'elecció canvia molt més el dia a dia que l'arquitectura.

Aspecte Tipus de llançament EC2 Tipus de llançament Fargate (10-02)
Qui gestiona les màquines Tu: AMI, pedaços, escalat del clúster AWS: no veus màquines
Unitat de facturació La instància EC2, tant si és plena com buida vCPU-hora i GB-hora de la tasca
Densitat Pots empaquetar moltes tasques per instància Una tasca és la seva pròpia unitat
Arrencada de tasca 5-15 s si hi ha lloc; 2 min si cal afegir instància 30-45 s sempre
GPU, instàncies especials No (o amb limitacions)
Modes de xarxa awsvpc, bridge, host, none Només awsvpc
Accés a l'amfitrió SSH, DaemonSet, volums de l'amfitrió No: ECS Exec per depurar
Cost amb ús alt i constant Més barat si l'empaquetatge és bo Més car per unitat, sense cost ociós

Al tipus de llançament EC2, cada instància executa l'agent d'ECS (amazon-ecs-agent), un contenidor que es registra al clúster, informa dels recursos disponibles i rep les ordres del pla de control; la manera correcta de crear aquestes instàncies és amb l'AMI optimitzada per a ECS, que ja porta l'agent i el runtime. Els proveïdors de capacitat són l'abstracció que evita gestionar l'escalat del clúster a mà. Un proveïdor de capacitat per a EC2 s'associa a un ASG i activa l'escalat gestionat: ECS calcula quantes instàncies calen per a les tasques pendents i ajusta la capacitat desitjada de l'ASG pel seu compte.

aws ecs create-capacity-provider --name cp-mercadofresco-ec2 --auto-scaling-group-provider '{
    "autoScalingGroupArn": "arn:aws:autoscaling:eu-west-1:111122223333:autoScalingGroup:...:autoScalingGroupName/asg-mercadofresco-tienda",
    "managedScaling": { "status": "ENABLED", "targetCapacity": 90, "minimumScalingStepSize": 1, "maximumScalingStepSize": 4 },
    "managedTerminationProtection": "ENABLED" }'

targetCapacity: 90 significa «mantén el clúster al 90 % d'ocupació»: deixa un 10 % de marge per absorbir una pujada sense esperar una instància nova. I managedTerminationProtection evita que l'ASG acabi una instància que encara executa tasques, la fallada més molesta del model EC2. Tot i així, aquest 2 min de la taula continua sent-hi en la pitjor situació: si no hi ha lloc, cal una instància, i una instància triga el que triga. És exactament el motiu pel qual MercadoFresco no es quedarà aquí i 10-02 existeix.

La definició de tasca de la botiga, comentada

Aquesta és la definició completa de mercadofresco-tienda, amb secrets injectats, registres cap a CloudWatch i mode de xarxa awsvpc.

{
  "family": "mercadofresco-tienda",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["EC2", "FARGATE"],
  "cpu": "1024", "memory": "2048",
  "executionRoleArn": "arn:aws:iam::111122223333:role/rol-ejecucion-mercadofresco-tienda",
  "taskRoleArn": "arn:aws:iam::111122223333:role/rol-tarea-mercadofresco-tienda",
  "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" },
  "containerDefinitions": [
    {
      "name": "tienda",
      "image": "555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/tienda@sha256:9c1e...f4a2",
      "essential": true, "cpu": 896, "memoryReservation": 1536, "memory": 1792,
      "portMappings": [{ "name": "http", "containerPort": 8080, "protocol": "tcp", "appProtocol": "http" }],
      "environment": [
        { "name": "ENTORNO", "value": "produccion" },
        { "name": "COLA_PEDIDOS", "value": "cola-mercadofresco-pedidos" },
        { "name": "TABLA_CARRITOS", "value": "mercadofresco-carritos" },
        { "name": "AWS_XRAY_DAEMON_ADDRESS", "value": "127.0.0.1:2000" }
      ],
      "secrets": [
        { "name": "BD_CONTRASENA",
          "valueFrom": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:mercadofresco/produccion/rds/mfadmin:password::" },
        { "name": "BD_USUARIO",
          "valueFrom": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:mercadofresco/produccion/rds/mfadmin:username::" },
        { "name": "ENDPOINT_CACHE",
          "valueFrom": "arn:aws:ssm:eu-west-1:111122223333:parameter/mercadofresco/produccion/cache/endpoint" }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": { "awslogs-group": "/ecs/mercadofresco-tienda", "awslogs-region": "eu-west-1",
                     "awslogs-stream-prefix": "tienda", "awslogs-create-group": "true",
                     "mode": "non-blocking", "max-buffer-size": "4m" }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f -s http://127.0.0.1:8080/salud || exit 1"],
        "interval": 15, "timeout": 3, "retries": 3, "startPeriod": 30
      },
      "readonlyRootFilesystem": true,
      "linuxParameters": { "initProcessEnabled": true },
      "mountPoints": [{ "sourceVolume": "temporal", "containerPath": "/tmp", "readOnly": false }],
      "stopTimeout": 30
    },
    {
      "name": "xray",
      "image": "public.ecr.aws/xray/aws-xray-daemon:3.x",
      "essential": false, "cpu": 32, "memoryReservation": 256,
      "portMappings": [{ "containerPort": 2000, "protocol": "udp" }],
      "logConfiguration": { "logDriver": "awslogs", "options": {
        "awslogs-group": "/ecs/mercadofresco-tienda", "awslogs-region": "eu-west-1",
        "awslogs-stream-prefix": "xray" } }
    }
  ],
  "volumes": [{ "name": "temporal" }],
  "tags": [ { "key": "Proyecto", "value": "mercadofresco" }, { "key": "Entorno", "value": "produccion" },
            { "key": "Componente", "value": "tienda" }, { "key": "Propietario", "value": "plataforma" },
            { "key": "CentroCoste", "value": "tecnologia" } ]
}

Els punts que cal entendre d'aquest JSON:

  • CPU i memòria a dos nivells. El cpu i memory de la tasca són el total reservat; els de cada contenidor reparteixen aquest total. Amb Fargate, els valors de tasca són obligatoris i només admeten combinacions concretes (10-02); amb EC2 es poden ometre i llavors la reserva es fa només per contenidor.
  • memoryReservation enfront de memory. memoryReservation és el mínim garantit que ECS fa servir per col·locar la tasca; memory és el límit dur: si el contenidor el supera, el kernel el mata amb OOM. Fixar només memory malgasta; fixar només memoryReservation deixa que un contenidor amb fuita es mengi la instància. Es posen tots dos.
  • essential: true a la botiga i false al sidecar d'X-Ray. Si un contenidor essencial mor, ECS mata la tasca sencera i la reposa. El dimoni d'X-Ray no ha de tombar la botiga si cau.
  • secrets en lloc d'environment per al que és sensible. L'agent d'ECS resol el valor a l'arrencada fent servir el rol d'execució i l'injecta com a variable d'entorn. Mai apareix a la definició de tasca, ni a la consola, ni a CloudTrail. La sintaxi :password:: selecciona una clau concreta del JSON del secret (04-03).
  • mode: non-blocking als registres. És l'opció que evita la fallada més desagradable del controlador awslogs: si CloudWatch Logs va lent, en mode bloquejant l'aplicació s'atura esperant a escriure el seu registre. Amb non-blocking es perden línies en el pitjor cas, cosa infinitament millor que perdre comandes.
  • readonlyRootFilesystem: true amb un volum per a /tmp. El sistema de fitxers de només lectura impedeix que un atacant escrigui binaris; el volum efímer dona el /tmp que Python necessita.
  • stopTimeout: 30. Segons que ECS espera entre el SIGTERM i el SIGKILL. És el marge que té Gunicorn per acabar les peticions en curs durant un desplegament.

Rol d'execució de la tasca enfront del rol de la tasca

És la confusió número u de qui comença amb ECS, i produeix errors que semblen no tenir sentit: la tasca no arrenca però els permisos de l'aplicació estan bé, o la tasca arrenca i l'aplicació no pot llegir una cua.

Rol d'execució de la tasca (executionRoleArn) Rol de la tasca (taskRoleArn)
Qui l'assumeix L'agent d'ECS, abans d'arrencar el contenidor El codi de la teva aplicació, dins del contenidor
Per a què serveix Descarregar la imatge d'ECR, escriure a CloudWatch Logs, resoldre els secrets Cridar les API d'AWS que necessita l'aplicació
Quan es fa servir A la fase d'aprovisionament Durant tota la vida de la tasca
Símptoma si falta o falla La tasca no arriba a arrencar: CannotPullContainerError, ResourceInitializationError L'aplicació arrenca i falla amb AccessDenied en cridar AWS
És obligatori Sí a Fargate i amb secrets o awslogs No, però sense ell l'aplicació no pot cridar res
{"Version": "2012-10-17", "Statement": [
  {"Sid": "DescargarImagenDeECR", "Effect": "Allow", "Resource": "*",
   "Action": ["ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability",
              "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"]},
  {"Sid": "DescifrarLaImagen", "Effect": "Allow", "Action": ["kms:Decrypt"],
   "Resource": "arn:aws:kms:eu-west-1:555566667777:key/*",
   "Condition": {"StringEquals": {"kms:ViaService": "ecr.eu-west-1.amazonaws.com"}}},
  {"Sid": "EscribirRegistros", "Effect": "Allow",
   "Action": ["logs:CreateLogStream", "logs:PutLogEvents", "logs:CreateLogGroup"],
   "Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:/ecs/mercadofresco-*:*"},
  {"Sid": "ResolverSecretos", "Effect": "Allow",
   "Action": ["secretsmanager:GetSecretValue", "ssm:GetParameters"],
   "Resource": ["arn:aws:secretsmanager:eu-west-1:111122223333:secret:mercadofresco/produccion/*",
                "arn:aws:ssm:eu-west-1:111122223333:parameter/mercadofresco/produccion/*"]}
]}

I el rol de la tasca, que és el que fa servir el codi i per tant el que ha de ser mínim:

{"Version": "2012-10-17", "Statement": [
  {"Effect": "Allow", "Action": ["sqs:SendMessage"],
   "Resource": "arn:aws:sqs:eu-west-1:111122223333:cola-mercadofresco-pedidos"},
  {"Effect": "Allow", "Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem"],
   "Resource": "arn:aws:dynamodb:eu-west-1:111122223333:table/mercadofresco-carritos"},
  {"Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::mercadofresco-catalogo-fotos/*"},
  {"Effect": "Allow", "Action": ["xray:PutTraceSegments", "xray:PutTelemetryRecords"], "Resource": "*"}
]}

La regla mnemotècnica que evita el 90 % dels errors: el rol d'execució treballa per a AWS, el rol de tasca treballa per al teu codi. Si la fallada passa abans de veure ni un sol registre de l'aplicació, és el d'execució. Si la fallada apareix als registres de l'aplicació, és el de tasca.

Mode de xarxa awsvpc i què implica

Amb networkMode: awsvpc, cada tasca rep la seva pròpia interfície de xarxa elàstica (ENI) amb la seva pròpia IP privada dins de la VPC. Les conseqüències són concretes i totes rellevants per a MercadoFresco:

  • Grups de seguretat per tasca. sg-mercadofresco-tienda s'aplica ara a la tasca, no a la instància. Dos serveis diferents a la mateixa instància poden tenir regles diferents, cosa impossible amb bridge.
  • Sense conflictes de port. A bridge calia fer servir hostPort: 0 per a l'assignació dinàmica i el grup de destinació registrava instancia:port. Amb awsvpc, cada tasca escolta al seu propi 8080 i el grup de destinació és de tipus ip.
  • La tasca consumeix una IP de la subxarxa. Amb snet-mercadofresco-app-a i -b de /20 hi ha adreces de sobres, però en subxarxes petites és un límit real que s'assoleix abans del que s'espera.
  • Límit d'ENI per instància. Al tipus de llançament EC2, cada instància admet un nombre limitat d'ENI segons la seva mida. Sense l'ajust awsvpcTrunking activat (aws ecs put-account-setting-default --name awsvpcTrunking --value enabled), una m5.large només pot executar dues o tres tasques amb awsvpc, per molta CPU lliure que tingui. És un dels paranys més cars del model EC2.
  • Sense accés al localhost de l'amfitrió. Els contenidors d'una mateixa tasca sí que es veuen entre ells per 127.0.0.1 —per això el sidecar d'X-Ray funciona amb AWS_XRAY_DAEMON_ADDRESS=127.0.0.1:2000—, però no veuen els contenidors d'altres tasques.
Mode de xarxa Aïllament SG per tasca Ports Disponible a Fargate
awsvpc ENI pròpia per tasca Sense conflictes Sí (únic)
bridge Xarxa virtual de l'amfitrió No (són de la instància) Necessita mapatge dinàmic No
host Comparteix la pila de xarxa de l'amfitrió No Conflictes directes No
none Sense xarxa No

El servei: ALB, comprovacions d'estat i col·locació

El servei és el que converteix la tasca en una cosa que es pot anomenar producció: manté el nombre desitjat, reposa el que mor, registra a l'ALB i gestiona els desplegaments.

aws ecs create-service --cluster ecs-mercadofresco --region eu-west-1 \
  --service-name svc-mercadofresco-tienda --task-definition mercadofresco-tienda:23 \
  --desired-count 4 \
  --capacity-provider-strategy capacityProvider=cp-mercadofresco-ec2,weight=1,base=2 \
  --network-configuration 'awsvpcConfiguration={subnets=[snet-mercadofresco-app-a,snet-mercadofresco-app-b],
      securityGroups=[sg-mercadofresco-tienda],assignPublicIp=DISABLED}' \
  --load-balancers 'targetGroupArn=arn:aws:elasticloadbalancing:eu-west-1:111122223333:targetgroup/tg-mercadofresco-tienda/abc123,containerName=tienda,containerPort=8080' \
  --health-check-grace-period-seconds 60 \
  --deployment-configuration '{"minimumHealthyPercent": 100, "maximumPercent": 200,
      "deploymentCircuitBreaker": { "enable": true, "rollback": true }}' \
  --placement-strategy 'type=spread,field=attribute:ecs.availability-zone' 'type=spread,field=instanceId' \
  --enable-execute-command --propagate-tags SERVICE

Tres detalls que importen:

  • --health-check-grace-period-seconds 60. És el període durant el qual el servei ignora l'estat del grup de destinació després d'arrencar una tasca. Sense ell, una aplicació que triga 40 segons a escalfar la memòria cau entra en un bucle infinit: l'ALB la marca com a no sana, ECS la mata, n'arrenca una altra, i així indefinidament. És l'error més frustrant de tots perquè no dona cap pista.
  • Grup de destinació de tipus ip. Obligatori amb awsvpc. Si tg-mercadofresco-tienda es va crear al mòdul 3 amb tipus instance, cal crear un grup de destinació nou: el tipus no es pot canviar.
  • Estratègies de col·locació (només amb tipus de llançament EC2, no s'apliquen a Fargate):
Estratègia Què fa Quan fer-la servir
spread per AZ Reparteix per zona de disponibilitat Sempre, la primera: és la disponibilitat
spread per instanceId Reparteix entre instàncies Segona: evita perdre diverses tasques amb una instància
binpack per memòria o CPU Omple una instància abans de fer servir la següent Optimitzar cost amb càrregues tolerants
random A l'atzar Gairebé mai

Per a la botiga, spread per AZ i després per instància. Per als treballadors de la cua, que són tolerants a fallades, binpack per memòria estalvia instàncies.

Desplegaments: renovats, percentatges i interruptor de desplegament

El desplegament per omissió d'ECS és l'actualització renovada (ECS rolling update). El servei arrenca tasques amb la revisió nova, espera que estiguin sanes al grup de destinació, drena i atura les velles, i repeteix. Els dos paràmetres que el governen són:

  • minimumHealthyPercent: percentatge del desiredCount que ha de continuar sa durant el desplegament.
  • maximumPercent: percentatge màxim que pot estar en execució alhora.
Configuració Comportament amb desiredCount = 4 Ús
100 / 200 Arrenca 4 de noves, després n'atura 4 de velles. Mai menys de 4 sanes Producció: sense pèrdua de capacitat, cost doble temporal
50 / 100 N'atura 2, n'arrenca 2, repeteix. Mai més de 4 Sense capacitat de sobres; degrada durant el desplegament
100 / 150 Finestra intermèdia, tanda a tanda Compromís raonable
0 / 100 Atura tot i arrenca tot Només desenvolupament: hi ha tall

MercadoFresco fa servir 100 / 200 a producció i 50 / 100 a desenvolupament, on el cost importa més que la disponibilitat. Amb 100 / 200 i una arrencada de tasca de 15 segons, un desplegament complet de quatre tasques acaba en poc més d'un minut, enfront dels vuit o nou de l'ASG amb instàncies.

L'interruptor de desplegament és la xarxa de seguretat. Amb enable: true, ECS compta les fallades consecutives d'arrencada de tasca; en superar el llindar —calculat a partir del desiredCount, amb un mínim de 10— declara el desplegament fallit. Amb rollback: true, a més reverteix automàticament a l'última revisió que sí que funcionava.

Això substitueix, en el cas més freqüent, l'alarma manual: si la imatge nova té un error de configuració que impedeix arrencar, el desplegament s'atura i es reverteix sol. El que l'interruptor de desplegament no detecta és una imatge que arrenca bé i respon malament: per a això calen les alarmes de CloudWatch associades al desplegament, amb alarmNames a la configuració, i el blue/green. El desplegament blue/green amb CodeDeploy (08-03) és l'altra opció: es canvia el controlador de desplegament a CODE_DEPLOY, i CodeDeploy aixeca el conjunt verd complet a tg-mercadofresco-verde, permet provar-lo per un port de proves i desplaça el trànsit de cop o en canari. És el que MercadoFresco integra al pipeline a 10-02, quan el servei ja sigui sobre Fargate.

Autoescalat del servei i descobriment amb Cloud Map

L'autoescalat d'ECS el proporciona Application Auto Scaling, el mateix servei que escala Aurora Serverless o DynamoDB. Es registra el servei com a destinació escalable i se li associa una política.

DESTINACIO="--service-namespace ecs --scalable-dimension ecs:service:DesiredCount \
  --resource-id service/ecs-mercadofresco/svc-mercadofresco-tienda"

aws application-autoscaling register-scalable-target $DESTINACIO --min-capacity 4 --max-capacity 20

aws application-autoscaling put-scaling-policy $DESTINACIO --policy-type TargetTrackingScaling \
  --policy-name seguiment-peticions-per-destinacio \
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 120.0, "ScaleInCooldown": 300, "ScaleOutCooldown": 30,
    "PredefinedMetricSpecification": { "PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/alb-mercadofresco-tienda/abc123/targetgroup/tg-mercadofresco-tienda/def456" } }'

L'elecció de mètrica no és indiferent:

Mètrica Què mesura Quan és la correcta
ECSServiceAverageCPUUtilization CPU mitjana del servei La càrrega és proporcional a la CPU
ECSServiceAverageMemoryUtilization Memòria mitjana Poques vegades: la memòria no baixa en baixar la càrrega
ALBRequestCountPerTarget Peticions per destinació La botiga: reacciona abans que la CPU
Mètrica pròpia (ApproximateNumberOfMessagesVisible) Profunditat de cua Els treballadors de cola-mercadofresco-pedidos

Per a la botiga, ALBRequestCountPerTarget amb destinació 120 és millor que la CPU per una raó de temps: el trànsit puja abans que la CPU, així que escalar per peticions avança la reacció entre 30 i 60 segons. I els refredaments són asimètrics a propòsit: 30 segons per créixer, 300 per decréixer. Créixer tard costa comandes; decréixer aviat costa una oscil·lació. Per als treballadors, la mètrica correcta és la profunditat de la cua dividida entre el nombre de tasques —el «retard per tasca» de 07-01—, calculada com a mètrica matemàtica de CloudWatch i feta servir en una política de seguiment de destinació personalitzada.

Descobriment de serveis amb Cloud Map

Quan un servei necessita cridar-ne un altre sense passar per l'ALB, la pregunta és com en troba la IP, que canvia amb cada tasca. AWS Cloud Map ho resol amb DNS: ECS registra i desregistra automàticament cada tasca en un espai de noms privat (aws servicediscovery create-private-dns-namespace --name interno.mercadofresco --vpc vpc-mercadofresco). Amb el servei configurat amb serviceRegistries, la botiga arriba a l'inventari a inventario.interno.mercadofresco i el DNS retorna les IP de les tasques sanes. Les alternatives són:

Mecanisme Avantatge Inconvenient
ALB intern Comprovacions d'estat, TLS, encaminament per ruta Cost fix i un salt més de latència
Cloud Map (DNS) Sense cost de balancejador, directe El client ha de gestionar reintents i memòria cau de DNS
ECS Service Connect Servidor intermediari gestionat amb reintents i mètriques per servei Afegeix un sidecar i certa complexitat

MercadoFresco encara no necessita això perquè la botiga és un monòlit darrere de l'ALB i tota la resta va per cues (mòdul 7). Quan la botiga es divideixi, ECS Service Connect serà l'opció preferible: dona mètriques per crida entre serveis sense instrumentar el codi.

Observabilitat: Container Insights, registres i X-Ray

Tot el del mòdul 5 continua valent, amb tres ajustos:

  • Container Insights s'activa a nivell de clúster (aws ecs update-cluster-settings --cluster ecs-mercadofresco --settings name=containerInsights,value=enhanced) i publica mètriques de CPU, memòria, xarxa i disc per servei i per tasca a l'espai de noms ECS/ContainerInsights. Sense ell, CloudWatch només dona CPUUtilization i MemoryUtilization a nivell de servei, cosa que no basta per diagnosticar quin contenidor consumeix.

  • Els registres van al grup /ecs/mercadofresco-tienda amb un flux per tasca. La conseqüència pràctica: com que les tasques són efímeres i es reposen constantment, l'única manera raonable de cercar és CloudWatch Logs Insights (05-01), no obrir fluxos a mà. I la retenció cal fixar-la explícitament: amb awslogs-create-group: true, el grup es crea sense caducitat i guarda per sempre.

  • X-Ray funciona amb el sidecar de l'exemple. L'SDK de l'aplicació envia els segments per UDP a 127.0.0.1:2000, el dimoni els agrupa i els puja. L'alternativa moderna és l'AWS Distro for OpenTelemetry com a sidecar, que a més de traces recull mètriques. En tots dos casos, el rol de tasca —no el d'execució— necessita xray:PutTraceSegments.

Les mètriques noves que la Marta afegeix al tauler mercadofresco-produccion:

Mètrica Llindar d'alarma Què indica
RunningTaskCount enfront de DesiredTaskCount Diferència > 0 durant 5 min Tasques que no aconsegueixen arrencar
CpuUtilized / CpuReserved per servei > 85 % sostingut Reserva mal dimensionada
MemoryUtilized / MemoryReserved > 90 % Risc d'OOM
TaskStopped amb exitCode: 137 Qualsevol El kernel va matar el contenidor per memòria
DeploymentCount > 1 durant 15 min Desplegament encallat

L'exitCode: 137 mereix una nota: és 128 + 9, és a dir, SIGKILL. A ECS gairebé sempre significa OOM: el contenidor va superar la seva memory i el kernel el va matar. El símptoma és una tasca que reapareix cada pocs minuts sense que els registres de l'aplicació diguin res, perquè no hi va haver temps d'escriure res.

La migració de MercadoFresco

El pla de la Marta separa el que es contenidoritzarà ara, el que es contenidoritzarà després i el que no es toca mai.

Component Decisió Motiu
Botiga (asg-mercadofresco-tienda) Contenidoritzar ja És la que pateix el pic del divendres i la que té l'arrencada de dos minuts
Treballadors de cola-mercadofresco-pedidos (asg-mercadofresco-trabajadores) Contenidoritzar ja Mateixa AMI, mateix problema d'apedaçament, i escalen per cua
Lambdes (-cobrar-pago, -reservar-stock, …) No tocar Ja no tenen servidor; contenidoritzar-les seria un retrocés
Aurora, DynamoDB, Redshift, ElastiCache No tocar Són serveis gestionats; el contenidor no hi aporta res
ALB, CloudFront, Route 53, WAF No tocar Només canvia el tipus de destinació del grup, a ip
Buckets i cues No tocar La interfície és la mateixa des d'un contenidor

Els passos, en l'ordre en què redueixen risc:

  1. Escriure el Dockerfile i executar-lo en local. Abans de tocar AWS, la imatge ha d'arrencar i respondre 200 a /salud al portàtil del Luis.
  2. Crear els repositoris d'ECR amb immutabilitat, escaneig millorat i cicle de vida, i afegir la fase de construcció d'imatge al projecte build-mercadofresco-tienda.
  3. Crear el clúster ecs-mercadofresco a desenvolupament, amb un proveïdor de capacitat sobre un ASG petit d'instàncies amb l'AMI optimitzada per a ECS.
  4. Registrar la definició de tasca i llançar una tasca solta amb run-task. Aquí apareixen els errors de rol d'execució, secrets i grups de seguretat, i aquí és on han d'aparèixer.
  5. Crear el grup de destinació de tipus ip i el servei a desenvolupament, i validar amb les proves de fum del projecte build-mercadofresco-humo (08-02).
  6. Repetir a preproducció amb la càrrega sintètica del divendres, mesurant temps d'arrencada de tasca i comparant-lo amb els 120 segons actuals.
  7. A producció, convivència: el mateix ALB amb dos grups de destinació i un repartiment ponderat —90 % a l'ASG, 10 % al servei d'ECS—, pujant el pes al llarg de dues setmanes.
  8. Retirar l'ASG i l'AMI quan el servei porti dues setmanes al 100 % sense incidents, i esborrar la plantilla de llançament del CDK en un PR que en deixi constància.

El pas 7 és el que fa que això sigui una migració i no un salt de fe. Amb el repartiment ponderat de l'ALB (03-03), un problema al servei d'ECS afecta el 10 % de les peticions durant el temps que es trigui a posar el pes a zero, que són segons.

Cost i neteja

ECS no costa res. El pla de control, les definicions de tasca, els serveis i els desplegaments són gratuïts. El que es paga amb el tipus de llançament EC2 és exactament el que ja es pagava: les instàncies, els seus volums EBS i la transferència de dades.

La comparació honesta per a MercadoFresco, amb la botiga contenidoritzada sobre les mateixes instàncies:

Concepte Abans (ASG amb AMI) Ara (ECS sobre EC2)
Instàncies en horari normal 2 × m5.large 2 × m5.large (més denses: hi caben també els treballadors)
Instàncies al pic Fins a 4 Fins a 4, amb millor empaquetatge
ECR ~2 GB × 0,10 = 0,20 USD/mes
Escaneig millorat ~4 USD/mes amb 4,8 imatges per setmana
Estalvi real Consolidar botiga i treballadors al mateix clúster elimina 2 instàncies dedicades

L'estalvi d'aquesta lliçó no ve d'ECS sinó de la densitat: els treballadors tenien el seu propi ASG amb les seves pròpies instàncies, moltes vegades ocioses, i en un clúster compartit s'empaqueten amb la botiga. L'estalvi gran —eliminar la capacitat ociosa per complet— arriba amb Fargate a 10-02, i l'anàlisi fina de costos, al mòdul 11. La neteja, en aquest ordre estricte:

# 1. El servei a zero abans d'esborrar-lo, o l'esborrat falla
aws ecs update-service --cluster ecs-mercadofresco --service svc-mercadofresco-tienda --desired-count 0
aws ecs delete-service --cluster ecs-mercadofresco --service svc-mercadofresco-tienda --force
# 2. Proveidor de capacitat i cluster
aws ecs delete-capacity-provider --capacity-provider cp-mercadofresco-ec2
aws ecs delete-cluster --cluster ecs-mercadofresco
# 3. L'ASG d'instancies del cluster: AIXO es el que costa diners
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name asg-cluster-mercadofresco --force-delete
# 4. Imatges i repositori; 5. el grup de registres, que si no guarda per sempre
aws ecr delete-repository --repository-name mercadofresco/tienda --force
aws logs delete-log-group --log-group-name /ecs/mercadofresco-tienda

El pas 4 és l'únic imprescindible des del punt de vista econòmic: esborrar el clúster d'ECS no esborra les instàncies EC2, que continuen facturant alegrement sense res a executar. És el residu més car d'aquesta lliçó.

Errors Habituals i Consells

Error: fer servir latest a la definició de tasca. Produeix desplegaments no reproduïbles i reversions impossibles. Consell: etiqueta amb v1.6.0-a3f9c21 i desplega per digest; que el pipeline sigui l'únic que decideix quin digest va a cada entorn.

Error: confondre el rol d'execució amb el de tasca. Mitja hora perduda per cada incident. Consell: si la tasca no arriba a arrencar, mira el rol d'execució; si l'aplicació falla cridant AWS, el de tasca. Els missatges CannotPullContainerError i ResourceInitializationError són sempre del d'execució.

Error: no fixar --health-check-grace-period-seconds. L'aplicació triga a escalfar-se, l'ALB la mata, ECS la reposa, bucle infinit. Consell: mesura el temps real fins al primer 200 a /salud i posa'n el doble.

Error: COPY . . sense .dockerignore. Fica .git complet i fitxers .env dins la imatge. Consell: el .dockerignore s'escriu abans que el Dockerfile, no després.

Error: capes en mal ordre. COPY . . abans de pip install invalida la memòria cau a cada commit. Consell: del que menys canvia al que més: base, sistema, dependències, codi.

Error: contenidor com a root amb sistema de fitxers escrivible. És innecessari i converteix qualsevol fallada de l'aplicació en escriptura de binaris. Consell: USER numèric, readonlyRootFilesystem: true i un volum efímer per a /tmp.

Error: oblidar el límit d'ENI amb awsvpc. Una m5.large executa dues tasques i el clúster escala «sense motiu». Consell: activa awsvpcTrunking a nivell de compte abans de dimensionar res.

Error: awslogs en mode bloquejant. Un retard de CloudWatch Logs frena l'aplicació. Consell: mode: non-blocking amb max-buffer-size explícit a tots els serveis amb trànsit d'usuari.

Error: política de cicle de vida agressiva. Esborra la imatge a la qual calia revertir. Consell: conserva almenys deu versions publicades i prova la política en sec abans d'aplicar-la.

Consell: activa l'interruptor de desplegament amb reversió a tots els serveis des del primer dia. No té cost i converteix un desplegament trencat en un incident de tres minuts que es resol sol.

Consell: fixa la retenció dels grups de registres d'ECS. Amb awslogs-create-group el grup neix sense caducitat, i amb tasques efímeres el volum de registres creix més de pressa del que ningú espera.

Consell: etiqueta les tasques amb propagateTags: SERVICE. És l'única manera que les cinc etiquetes obligatòries arribin a la tasca i que el mòdul 11 pugui repartir costos.

Consell: mantén la definició de tasca al CDK. L'ecs.FargateTaskDefinition o ecs.Ec2TaskDefinition de 09-02 genera rols amb permisos mínims automàticament amb els mètodes grant*, inclosos els de KMS que gairebé ningú no recorda.

Exercicis

Exercici 1: el Dockerfile dels treballadors

Escriu el Dockerfile complet del component de treballadors de MercadoFresco, que consumeix cola-mercadofresco-pedidos, escriu a Aurora i publica al tema mercadofresco-pedido-confirmado. És una aplicació Python sense servidor HTTP: no exposa ports i no pot fer servir un HEALTHCHECK basat en curl. Resol (a) com fas la comprovació d'estat sense HTTP i per què ECS la necessita igualment; (b) què canvia respecte al Dockerfile de la botiga quant a etapes, usuari i .dockerignore; (c) com gestiones l'apagada ordenada quan ECS envia SIGTERM enmig del processament d'un missatge; i (d) quin stopTimeout hi poses i amb quina relació respecte al temps de visibilitat de la cua.

Exercici 2: la tasca que no arrenca

El Luis registra la definició de tasca de la botiga i llança el servei a desenvolupament. Cap tasca no arriba a l'estat RUNNING. A la consola apareixen, en diferents intents, aquests tres motius d'aturada:

  1. CannotPullContainerError: pull access denied for 555566667777.dkr.ecr.eu-west-1.amazonaws.com/mercadofresco/tienda, repository does not exist or may require 'docker login'
  2. ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve secret from asm: AccessDeniedException
  3. La tasca arriba a RUNNING, però el servei la mata als 90 segons i n'arrenca una altra, indefinidament.

Per a cadascun: diagnostica la causa exacta, indica on ho comprovaries, i dona la correcció concreta. Per al tercer, explica a més per què el desired count no s'estabilitza mai i quines dues configuracions diferents podrien estar causant-ho.

Exercici 3: decidir l'etiquetatge i el cicle de vida

MercadoFresco desplega 4,8 vegades per setmana a producció, manté entre 6 i 10 branques de característica vives alhora i necessita poder revertir fins a quatre versions enrere a producció. Compliment exigeix poder demostrar, per a qualsevol dia dels últims 12 mesos, quina imatge exacta hi havia a producció. Dissenya (a) l'esquema complet d'etiquetatge d'imatges, incloent-hi quines etiquetes porta una imatge de branca i quines una de producció; (b) la política de cicle de vida completa en JSON, amb les seves prioritats; (c) com satisfàs el requisit de compliment de 12 mesos sense guardar 250 imatges; i (d) quin risc concret tindria posar la regla d'imatges sense etiqueta amb prioritat 1.

Solucions

Solució 1

# L'etapa "constructor" es identica a la de la botiga (venv a /opt/entorno amb
# --require-hashes); canvia nomes l'etapa d'execucio:
FROM python:3.12.4-slim-bookworm AS execucio
RUN apt-get update && apt-get install -y --no-install-recommends libpq5=15.* \
    && rm -rf /var/lib/apt/lists/* && groupadd --gid 10002 trabajador \
    && useradd --uid 10002 --gid trabajador --no-create-home --shell /usr/sbin/nologin trabajador
COPY --from=constructor /opt/entorno /opt/entorno
ENV PATH="/opt/entorno/bin:$PATH" PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY --chown=trabajador:trabajador ./trabajadores ./trabajadores
USER 10002
# (a) Sense HTTP ni curl: el bucle escriu una marca de temps despres de cada iteracio
# de sondeig i aquesta comanda falla si aquesta marca te mes de 90 segons.
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD python -m trabajadores.salud || exit 1
ENTRYPOINT ["python", "-m", "trabajadores.principal"]

(a) La comprovació sense HTTP. El bucle de sondeig escriu /tmp/latido amb la marca de temps després de cada cicle de ReceiveMessage, i trabajadores.salud retorna error si el fitxer té més de 90 segons. ECS la necessita igualment perquè un treballador penjat no mor: es queda esperant en un sòcol, deixa de consumir la cua i el servei no se n'assabenta. Sense comprovació d'estat, aquest treballador zombi compta com a capacitat sana i la cua creix amb quatre tasques «vives». Amb el HEALTHCHECK, ECS el mata i el reposa. Com que readonlyRootFilesystem estarà actiu, /tmp ha d'anar muntat com a volum efímer.

(b) Què canvia respecte a la botiga. L'estructura multietapa és idèntica —és el patró, no l'aplicació—. No hi ha EXPOSE ni portMappings perquè ningú no crida el treballador. No cal curl, cosa que permet una imatge encara més petita i una superfície d'atac menor: el HEALTHCHECK fa servir el mateix Python. L'UID és diferent (10002) per higiene, perquè els processos siguin distingibles a l'amfitrió. I el .dockerignore és el mateix, amb una addició important: el directori de dades de prova amb missatges d'exemple, que pot contenir dades que semblen reals i no han de viatjar dins una imatge.

(c) Apagada ordenada. El procés registra un gestor de SIGTERM que activa una bandera; el bucle principal la comprova abans de demanar el missatge següent, no enmig del processament. El missatge en curs s'acaba i s'esborra de la cua amb DeleteMessage; després el procés surt amb codi 0. Si el missatge no es pot acabar a temps, no s'esborra: en expirar el temps de visibilitat tornarà a la cua i un altre treballador el processarà, i d'aquí la importància de la idempotència amb mercadofresco-idempotencia (07-05). L'error que cal evitar és esborrar el missatge en rebre'l: llavors el SIGKILL el perd per sempre.

(d) stopTimeout i visibilitat. El processament d'una comanda triga com a màxim uns 20 segons, així que stopTimeout: 30 dona prou marge. La relació amb el temps de visibilitat de la cua és la clau: el temps de visibilitat ha de ser més gran que stopTimeout més el temps de processament, perquè un missatge interromput no reaparegui mentre el treballador antic encara l'està acabant i un segon treballador el processi en paral·lel. Amb processament de 20 s i stopTimeout de 30 s, un temps de visibilitat de 180 s és folgat i correcte. El màxim d'ECS per a stopTimeout és 120 segons, cosa que a més obliga que cap processament individual duri més que això.

Solució 2

Motiu 1: CannotPullContainerError — permisos del rol d'execució o xarxa. Hi ha tres causes possibles i es distingeixen ràpid. La primera, que el rol d'execució no tingui els permisos d'ECR (GetAuthorizationToken, BatchGetImage, GetDownloadUrlForLayer); es comprova a CloudTrail (05-03) buscant AccessDenied sobre ecr: amb el principal del rol. La segona, que la política del repositori al compte d'eines no autoritzi el compte de desenvolupament: és creuat i calen les dues polítiques, la del rol i la del repositori. La tercera, i la que produeix aquest missatge enganyós exacte, és de xarxa: les tasques són a snet-mercadofresco-app-a sense ruta cap a ECR, perquè el NAT està caigut o perquè no hi ha punts d'enllaç de VPC. Es distingeix mirant si el missatge arriba després d'un temps d'espera llarg (xarxa) o immediat (permisos). Correcció: els permisos de la política d'execució més els punts d'enllaç d'ecr.api, ecr.dkr i el d'S3 —les capes es descarreguen d'S3, i aquest és el que tothom oblida—.

Motiu 2: ResourceInitializationError ... unable to retrieve secret from asm. És inequívoc: el rol d'execució no pot llegir mercadofresco/produccion/rds/mfadmin. Dues causes, i cal revisar-les totes dues: falta secretsmanager:GetSecretValue sobre l'ARN del secret, o falta kms:Decrypt sobre alias/mercadofresco-datos, perquè el secret està xifrat amb una clau gestionada pel client (04-02) i llegir el secret exigeix desxifrar-lo. La segona és la que més s'oblida, i el missatge d'error no esmenta KMS. Es comprova a CloudTrail amb l'esdeveniment GetSecretValue fallit. Correcció: afegir tots dos permisos al rol d'execució —no al de tasca, que és l'error següent que cometrà el Luis— i verificar que la política de la clau KMS inclogui el rol com a usuari.

Motiu 3: el bucle infinit d'arrencada i mort. La tasca arriba a RUNNING, després mor. Dues configuracions poden causar-ho:

  • El període de gràcia de la comprovació d'estat. Si l'aplicació triga 60 segons a respondre 200 a /salud —connexió a Aurora, precàrrega de catàleg des d'ElastiCache— i el grup de destinació la declara no sana abans, el servei la desregistra i la mata. ECS la reposa, i el cicle es repeteix. Correcció: --health-check-grace-period-seconds al doble del temps mesurat, i revisar el llindar del grup de destinació (HealthyThresholdCount, Interval, Timeout).
  • El grup de seguretat. Si sg-mercadofresco-tienda no permet l'entrada des de sg-mercadofresco-alb al port 8080 —i no al 80, que és el que estava configurat quan la destinació era la instància—, la comprovació de l'ALB no passa mai. Amb awsvpc, el SG s'aplica a la tasca i el port és el del contenidor, no un port dinàmic de l'amfitrió.

Per què el desired count no s'estabilitza mai: perquè el servei no distingeix entre «la tasca va morir» i «la tasca no va arribar mai a estar sana». Reposa indefinidament, consumint quota d'execució i omplint el registre d'esdeveniments. I és exactament l'escenari per al qual existeix l'interruptor de desplegament: amb enable: true i rollback: true, després del llindar de fallades consecutives el desplegament es declara fallit i es reverteix, en lloc de girar en va durant hores. Si el Luis l'hagués activat, el diagnòstic hauria arribat com un esdeveniment de desplegament fallit en lloc de com un tauler que no s'estabilitza.

Solució 3

(a) Esquema d'etiquetatge. Cada imatge porta diverses etiquetes, perquè una etiqueta és un punter i se'n poden posar diverses al mateix digest:

Origen Etiquetes Exemple
Branca de característica rama-<nom>-<sha7> rama-cesta-rapida-b71c4e0
main sense publicar main-<sha7> main-a3f9c21
Versió publicada v<semver>-<sha7> i v<semver> v1.6.0-a3f9c21 i v1.6.0
Memòria cau de construcció cache (mutable, repositori a part) cache

L'etiqueta cache no pot conviure amb IMMUTABLE, així que va en un repositori diferent —mercadofresco/tienda-cache— amb immutabilitat desactivada i cicle de vida de tres dies. És el matís que es descobreix en aplicar la immutabilitat i trencar la construcció.

(b) Política de cicle de vida.

{"rules": [
  {"rulePriority": 10, "description": "Versions publicades: conservar-ne 12", "action": {"type": "expire"},
   "selection": {"tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 12}},
  {"rulePriority": 20, "description": "main sense publicar: 30 dies", "action": {"type": "expire"},
   "selection": {"tagStatus": "tagged", "tagPrefixList": ["main-"], "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 30}},
  {"rulePriority": 30, "description": "Branques de caracteristica: 14 dies", "action": {"type": "expire"},
   "selection": {"tagStatus": "tagged", "tagPrefixList": ["rama-"], "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 14}},
  {"rulePriority": 90, "description": "Sense etiqueta: 1 dia", "action": {"type": "expire"},
   "selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 1}}
]}

Les prioritats van de deu en deu per poder intercalar regles després sense renumerar-ho tot, que és la mateixa disciplina de les regles de WAF (04-05). Dotze versions publicades cobreixen amb folgança les quatre reversions exigides i unes dues setmanes i mitja de desplegaments.

(c) El requisit de compliment de 12 mesos. No es resol guardant imatges, es resol guardant evidència. Compliment no necessita poder executar la imatge de fa onze mesos; necessita poder demostrar quina imatge estava desplegada. Tres fonts ho donen sense cost d'emmagatzematge:

  • CloudTrail (05-03) registra cada RegisterTaskDefinition i UpdateService amb el digest exacte i qui ho va fer; amb el rastre d'organització al compte de seguretat i S3 amb Object Lock, l'evidència és immutable i consultable amb Athena.
  • El repositori Git mercadofresco-tienda conserva el commit i l'imatge.json de l'artefacte de cada execució del pipeline.
  • AWS Config (05-04) manté l'històric de configuració del servei d'ECS.

Si a més calgués poder reconstruir una imatge antiga, la resposta és reconstruir-la des del commit amb el Dockerfile reproduïble —versions fixades i hashes—, no guardar-la.

(d) El risc de posar la regla d'imatges sense etiqueta amb prioritat 1. Seria catastròfic per com avalua ECR: una imatge només l'afecta la primera regla que la selecciona, i una regla d'untagged amb prioritat 1 s'avalua abans que totes. Quan una imatge publicada perd la seva etiqueta —perquè una altra imatge la reutilitza, o durant una replicació, o en retirar una etiqueta v1.6.0 a mà—, passa a ser untagged i la regla 1 l'esborra en 24 hores, fins i tot si és la que està executant producció. Les regles d'untagged van sempre al final, amb la prioritat més alta, i només després que les regles específiques hagin tingut la seva oportunitat de retenir el que importa.

Conclusió

MercadoFresco ja no desplega màquines: desplega imatges. Aquesta lliçó ha substituït els tres artefactes que feien mal —l'AMI, el cicle d'apedaçament i l'arrencada de dos minuts— per un Dockerfile de quaranta línies, un registre i un orquestrador.

Tens clar què és un contenidor de debò: namespaces i cgroups sobre un kernel compartit, no una màquina virtual petita, amb el que això implica en arrencada (segons enfront de minuts), en mida (megues enfront de gigues), en densitat i en portabilitat —el mateix artefacte al portàtil del Luis i a producció, byte a byte—, i amb l'advertiment honest sobre l'aïllament que el màrqueting sol ometre. Tens el Dockerfile de la botiga amb construcció multietapa que deixa els compiladors fora, usuari 10001 sense privilegis, versions fixades amb hashes, HEALTHCHECK coherent amb /salud i l'ordre de capes que converteix una construcció de tres minuts en una de quaranta segons. I el .dockerignore que s'escriu abans que el Dockerfile, no després d'haver ficat .git dins una imatge.

Tens Amazon ECR amb mercadofresco/tienda i mercadofresco/trabajadores: autenticació per testimoni temporal, immutabilitat d'etiquetes, l'esquema v1.6.0-a3f9c21 que lliga cada imatge a un commit, i la regla que cal interioritzar —l'etiqueta serveix perquè un humà trobi la imatge; el digest, perquè la màquina la desplegui—. Amb les polítiques de cicle de vida i els seus tres paranys, l'escaneig millorat d'Inspector que sí que veu les dependències de Python i avisa quan una imatge ja desplegada deixa de ser neta, la replicació des del compte d'eines 555566667777 cap a producció, i el xifratge amb alias/mercadofresco-datos que exigeix recordar-se de la política de la clau.

I tens Amazon ECS: clúster ecs-mercadofresco, la definició de tasca mercadofresco-tienda amb secrets injectats des de Secrets Manager i Parameter Store, registres no bloquejants, sidecar d'X-Ray i awsvpc amb grup de seguretat per tasca; el servei svc-mercadofresco-tienda darrere de tg-mercadofresco-tienda amb període de gràcia, desplegament 100/200 i interruptor de desplegament amb reversió automàtica; l'autoescalat per ALBRequestCountPerTarget amb refredaments asimètrics; i la distinció que estalvia mitja hora a cada incident: el rol d'execució treballa per a AWS, el rol de tasca treballa per al teu codi.

Però queda un residu, i es veu a la taula de proveïdors de capacitat. Sota el clúster continuen existint instàncies EC2: cal triar-ne la mida, mantenir-ne l'AMI optimitzada per a ECS, vigilar que l'escalat gestionat no es quedi curt i acceptar que, quan no hi ha lloc per a una tasca nova, cal una instància nova i una instància triga dos minuts a arrencar. És el mateix número del qual fugíem. L'hem amagat darrere d'un proveïdor de capacitat, no l'hem eliminat. I continua havent-hi capacitat ociosa que es paga: dues instàncies enceses a les quatre de la matinada d'un dimarts.

A 10-02, «AWS Fargate», les instàncies desapareixen. Sense AMI que mantenir, sense clúster que escalar, sense empaquetatge de tasques en màquines, sense capacitat ociosa: es declara quanta CPU i quanta memòria necessita cada tasca i AWS hi posa la resta. Veuràs les combinacions vàlides de recursos, l'estalvi real de Graviton, com es depura sense SSH amb ECS Exec, els punts d'enllaç de VPC que eviten pagar el NAT, l'escalat programat per al divendres a les 17:00, i Fargate Spot per als treballadors de la cua. I la comparativa honesta entre Fargate, EC2 i Lambda, amb la decisió raonada de MercadoFresco per a cada component.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats