MercadoFresco ja té les tres peces: un repositori governat, una construcció que verifica i produeix
artefactes, i un desplegament que sap tornar enrere sol. I tanmateix, quan el Luis fusiona una PR continua
havent-se de recordar de llançar la construcció, copiar la ruta del ZIP, escriure create-deployment amb
el bucket i la clau correctes, esperar, mirar si ha anat bé, i repetir-ho per a l'entorn següent. Vuit
ordres, quatre esperes i una seqüència que només viu al seu cap.
AWS CodePipeline és l'orquestració que falta. Defineix el camí que recorre un canvi des del commit fins a producció com una sèrie d'etapes que s'executen en ordre, cada una amb les seves accions, amb artefactes que flueixen d'una a l'altra i amb la garantia que una etapa no comença si l'anterior no ha acabat bé. El que avui és memòria d'una persona passa a ser una definició versionada que s'executa igual totes les vegades.
Avís de cost. CodePipeline V1 costa 1 USD per pipeline actiu i mes —un pipeline és actiu si ha tingut alguna execució— amb el primer gratis. V2 cobra 0,002 USD per minut d'acció, sense quota fixa, amb 100 minuts gratis al mes. Per a MercadoFresco, amb unes 20 execucions diàries, V2 surt a uns 4 USD al mes. A això s'hi sumen els minuts de CodeBuild de 08-02 i l'emmagatzematge del bucket d'artefactes, que creix ràpid si ningú no li posa cicle de vida. Dades fictícies.
Contingut
- Lliurament continu enfront de desplegament continu
- Estructura: pipeline, etapes, accions, execucions i transicions
- Els artefactes i el bucket que els mou
- El pipeline de MercadoFresco
- Tipus V1 i V2, disparadors i variables
- L'acció d'origen amb CodeConnections
- Construcció, desplegament i accions en paral·lel amb
runOrder - Aprovació manual: què ha de llegir la Marta en trenta segons
- Entorns: desenvolupament, preproducció i producció
- Proves de fum i portes de qualitat amb una acció Lambda
- Reintents, execucions en cua i mode superposat
- Permisos: rol del pipeline, rols per acció i entre comptes
- Notificacions cap a correu i Slack
- El pipeline com a codi
- Cost i neteja
- Errors habituals i consells
- Exercicis
- Conclusió
Lliurament continu enfront de desplegament continu
Els dos termes es confonen constantment i la diferència és una sola cosa: si hi ha o no una persona entre la validació i producció.
| Aspecte | Lliurament continu | Desplegament continu |
|---|---|---|
| Què automatitza | Tot fins a la porta de producció | Tot, producció inclosa |
| Qui decideix que surti | Una persona aprova | Ningú: si passa les proves, surt |
| Requisit previ | Pipeline fiable | Pipeline fiable i proves en què confies |
| Temps fins a producció | Minuts + espera humana | Minuts |
| Risc per desplegament | Igual | Igual, però més desplegaments i més petits |
| Quan triar-lo | En començar; canvis sensibles | Quan la reversió automàtica està provada |
Hi ha un parany habitual: pensar que l'aprovació manual redueix el risc. No el redueix per si sola. El redueix només si qui aprova té informació suficient per decidir; si la Marta prem «aprovar» sense mirar res, l'aprovació és una fricció que endarrereix el desplegament sense filtrar res. I hi ha un efecte secundari pervers: les aprovacions acumulen canvis —«ja que aprovo, que vagin els tres junts»— i un desplegament de tres canvis és més difícil de diagnosticar que tres desplegaments d'un.
La decisió de MercadoFresco: lliurament continu, amb aprovació manual abans de producció, i amb data de revisió. La Marta ho planteja explícitament com una etapa transitòria: l'aprovació es manté fins que es compleixin tres condicions —que les proves de fum cobreixin el flux de compra complet, que la reversió automàtica s'hagi provat a propòsit almenys tres vegades, i que passin dos mesos sense un desplegament que s'hagi de revertir a mà—. Complertes les tres, l'aprovació desapareix. Posar condicions de sortida a un control manual és el que impedeix que es quedi per sempre «per si de cas».
Estructura: pipeline, etapes, accions, execucions i transicions
| Concepte | Què és | Regla que cal recordar |
|---|---|---|
| Pipeline | El flux complet | Un pipeline per aplicació desplegable |
| Etapa | Un grup d'accions amb nom | Només comença si l'anterior ha tingut èxit |
| Acció | Una unitat de treball | Sis tipus: origen, construcció, prova, desplegament, aprovació, invocació |
| Execució | Un recorregut concret | Identificada, amb el seu commit i els seus artefactes |
| Transició | El pas entre dues etapes | Es pot deshabilitar per retenir canvis |
| Artefacte | El que flueix entre accions | Va per S3, no en memòria |
Dos matisos que estalvien sorpreses. Dins d'una etapa, les accions amb el mateix runOrder s'executen
en paral·lel, i les de runOrder més gran esperen les anteriors; és el mecanisme per paral·lelitzar sense
crear etapes noves. I una transició deshabilitada és una eina operativa poc coneguda i molt
útil: durant el pic del divendres, la Marta deshabilita la transició cap a producció i els canvis
s'acumulen validats en preproducció, a punt per sortir el dilluns amb un clic.
Els artefactes i el bucket que els mou
Cada acció pot declarar artefactes d'entrada i de sortida. CodePipeline els desa comprimits en un bucket d'S3 i cada acció rep els seus descomprimits. Tres conseqüències pràctiques. El bucket ha de tenir el versionat activat: sense això el pipeline no funciona, i és un requisit dur. Els artefactes s'acumulen: amb 20 execucions diàries i 45 MB per artefacte són 27 GB a l'any, així que sense regla de cicle de vida la factura d'S3 creix sola. I és el punt on es garanteix la immutabilitat: el ZIP que aprova la Marta és literalment el mateix objecte que es desplega en producció, sense reconstruir-se entre entorns, de manera que «el que es va validar en preproducció» i «el que va sortir» són el mateix fitxer byte a byte. Aquesta garantia és la raó de ser d'aquesta lliçó.
aws s3api put-bucket-lifecycle-configuration --bucket mercadofresco-artefactos \
--lifecycle-configuration '{"Rules":[{
"ID":"caducar-artefactos-pipeline","Status":"Enabled",
"Filter":{"Prefix":"pipeline/"},
"Expiration":{"Days":60},
"NoncurrentVersionExpiration":{"NoncurrentDays":15},
"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}' \
--profile mercadofresco-dev --region eu-west-1El pipeline de MercadoFresco
flowchart TB
A[Origen<br/>GitHub main] --> B[Construccio<br/>build-mercadofresco-tienda]
B --> C{Etapa Qualitat}
C --> C1[Analisi estatica]
C --> C2[Proves de contracte]
C1 --> D[Migracio d'esquema<br/>fase EXPANDIR]
C2 --> D
D --> E[Desplegar en desenvolupament<br/>dg-...-desarrollo]
E --> F[Proves de fum<br/>build-mercadofresco-humo]
F --> G[Desplegar en preproduccio]
G --> H[Proves de fum preprod]
H --> I[APROVACIO MANUAL<br/>alertas-mercadofresco]
I --> J[Desplegar en produccio<br/>blue/green + canari]
J --> K[Porta de qualitat Lambda<br/>metriques MercadoFresco/Tienda]
K -->|Metriques OK| L[Execucio correcta]
K -->|Degradat| M[Aturar i revertir]
Vuit etapes, i cada una respon una pregunta concreta: què ha canviat? compila i passa les proves? compleix la qualitat? està l'esquema preparat? funciona en un entorn real? passa el flux de compra? ho autoritza algú? continua sa després?
Tipus V1 i V2, disparadors i variables
| Aspecte | V1 | V2 |
|---|---|---|
| Preu | 1 USD/mes per pipeline actiu | 0,002 USD per minut d'acció |
| Disparadors per filtre | No | Sí: branca, etiqueta, ruta de fitxer |
| Variables de pipeline | No | Sí, amb valors a cada execució |
| Etapes en paral·lel | Limitat | Sí |
| Quan compensa | Pipelines amb moltíssimes execucions | Gairebé sempre |
Tria V2 llevat que tinguis un motiu clar. Els disparadors amb filtre per si sols justifiquen el
canvi: sense ells, qualsevol commit a main llança el pipeline sencer, inclosa una correcció al
README. Amb ells:
{
"triggers": [{
"providerType": "CodeStarSourceConnection",
"gitConfiguration": {
"sourceActionName": "Origen",
"push": [{
"branches": { "includes": ["main"] },
"filePaths": {
"includes": ["app/**", "scripts/**", "requirements.txt", "appspec.yml"],
"excludes": ["**/*.md", "docs/**", ".github/**"]
}
}],
"pullRequest": [{
"events": ["OPEN", "UPDATED"],
"branches": { "includes": ["desarrollo", "main"] }
}]
}
}]
}Aquest disparador fa dues coses diferents. En push a main, executa el pipeline complet, però només si
ha canviat codi de debò: un canvi a docs/ no gasta minuts ni molesta ningú. I en pull request,
executa —amb les etapes de desplegament omeses— la construcció i les proves, que és el que tanca la
protecció de main que vam deixar oberta a 08-01: la PR no es pot fusionar si la comprovació no és
en verd.
Les variables de pipeline permeten parametritzar una execució sense tocar la definició. Es declaren en un
bloc variables amb nom, valor per defecte i descripció, es referencien com #{variables.nivelLog}
i es poden fixar en llançar l'execució a mà. Un advertiment: si mai et temptés declarar alguna cosa com
omitirHumo, recorda que una variable que permet saltar-se una porta és una porta que se saltarà. Si
la poses, que el seu ús quedi a CloudTrail i que el runbook exigeixi justificar-la per escrit.
L'acció d'origen amb CodeConnections
{
"name": "Origen",
"actionTypeId": {
"category": "Source", "owner": "AWS",
"provider": "CodeStarSourceConnection", "version": "1"
},
"configuration": {
"ConnectionArn": "arn:aws:codeconnections:eu-west-1:111122223333:connection/a1b2c3d4-EXAMPLE",
"FullRepositoryId": "mercadofresco/mercadofresco-tienda",
"BranchName": "main",
"DetectChanges": "false",
"OutputArtifactFormat": "CODEBUILD_CLONE_REF"
},
"outputArtifacts": [{ "name": "CodigoFuente" }],
"runOrder": 1
}Tres camps mereixen explicació. DetectChanges: false apaga el webhook implícit perquè a V2 els
dispars els governa el bloc triggers; deixar-lo a true amb triggers definits produeix execucions
duplicades, que és un desconcert clàssic. OutputArtifactFormat: CODEBUILD_CLONE_REF lliura a
CodeBuild una referència al repositori en lloc d'un ZIP, de manera que la construcció té la història
de Git disponible —necessària per a git describe o git-secrets --scan—; el preu és que el rol de
CodeBuild necessita codeconnections:UseConnection. I el ConnectionArn ha d'apuntar a una connexió en
estat AVAILABLE: si continua en PENDING, com vam avisar a 08-01, l'acció falla amb un error de permisos
que no esmenta la connexió.
Construcció, desplegament i accions en paral·lel amb runOrder
L'etapa de construcció consumeix CodigoFuente i produeix l'artefacte desplegable:
{
"name": "Construccion",
"actions": [{
"name": "ConstruirYProbar",
"actionTypeId": { "category": "Build", "owner": "AWS",
"provider": "CodeBuild", "version": "1" },
"configuration": { "ProjectName": "build-mercadofresco-tienda" },
"inputArtifacts": [{ "name": "CodigoFuente" }],
"outputArtifacts": [{ "name": "PaqueteTienda" }],
"namespace": "construccion",
"runOrder": 1
}]
}El namespace és la peça que connecta aquesta lliçó amb 08-02: exposa les variables que el buildspec
va declarar a exported-variables, de manera que les etapes posteriors poden fer servir #{construccion.VERSION_APP}
sense recalcular res. És així com el missatge d'aprovació sabrà quina versió està aprovant la Marta.
L'etapa de qualitat mostra el paral·lelisme:
{
"name": "Calidad",
"actions": [
{ "name": "AnalisisEstatico", "runOrder": 1,
"actionTypeId": { "category": "Test", "owner": "AWS",
"provider": "CodeBuild", "version": "1" },
"configuration": { "ProjectName": "build-mercadofresco-analisis" },
"inputArtifacts": [{ "name": "CodigoFuente" }] },
{ "name": "PruebasContrato", "runOrder": 1,
"actionTypeId": { "category": "Test", "owner": "AWS",
"provider": "CodeBuild", "version": "1" },
"configuration": { "ProjectName": "build-mercadofresco-contrato" },
"inputArtifacts": [{ "name": "CodigoFuente" }] }
]
}Totes dues tenen runOrder: 1 i corren alhora; si qualsevol falla, l'etapa falla i el pipeline
s'atura. Una tercera amb runOrder: 2 esperaria totes dues. I el desplegament consumeix l'artefacte:
{
"name": "DesplegarProduccion",
"actions": [{
"name": "CodeDeployProduccion",
"actionTypeId": { "category": "Deploy", "owner": "AWS",
"provider": "CodeDeploy", "version": "1" },
"configuration": {
"ApplicationName": "app-mercadofresco-tienda",
"DeploymentGroupName": "dg-mercadofresco-tienda-produccion"
},
"inputArtifacts": [{ "name": "PaqueteTienda" }],
"runOrder": 1
}]
}Fixa't que l'inputArtifacts és el mateix PaqueteTienda que es va desplegar en desenvolupament i en
preproducció. No es reconstrueix. És la garantia d'immutabilitat que fa que les proves anteriors
signifiquin alguna cosa.
Aprovació manual: què ha de llegir la Marta en trenta segons
Una aprovació mal dissenyada és un botó que es prem sense mirar. Una de ben dissenyada dona la informació justa per decidir.
{
"name": "AprobacionProduccion",
"actions": [{
"name": "AprobarMarta",
"actionTypeId": { "category": "Approval", "owner": "AWS",
"provider": "Manual", "version": "1" },
"configuration": {
"NotificationArn": "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
"CustomData": "Versio #{construccion.VERSION_APP} | Commit #{construccion.COMMIT_CORTO} | Autor #{construccion.AUTOR} | Fum preprod: OK | Migracio: EXPANDIR aplicada | Finestra: NO desplegar divendres 16-22h",
"ExternalEntityLink": "https://github.com/mercadofresco/mercadofresco-tienda/compare/#{construccion.VERSION_ANTERIOR}...#{construccion.VERSION_APP}"
},
"timeoutInMinutes": 1440,
"runOrder": 1
}]
}El que fa útil aquesta aprovació són els dos camps de text. CustomData porta les cinc coses que
la Marta necessita: quina versió, quin commit, qui ho ha fet, si les proves de fum van passar en preproducció i
si hi ha una migració d'esquema en joc, més el recordatori de la finestra prohibida. I
ExternalEntityLink és un enllaç al diff entre la versió desplegada i la candidata: amb un clic veu
exactament què canvia, que és la pregunta que de debò importa.
El timeoutInMinutes: 1440 és una decisió, no un detall: l'aprovació caduca a les 24 hores i
l'execució falla. És correcte —un canvi que fa tres dies que espera ja no és el canvi que es va validar,
perquè main ha avançat— i evita que s'acumuli una cua d'aprovacions zombis.
Tres regles perquè això funcioni: qui aprova no és qui ha programat —o l'aprovació no filtra
res—; el permís codepipeline:PutApprovalResult es concedeix només a qui ha d'aprovar; i si
l'aprovació es prem sempre sense mirar, cal treure-la, perquè un control que no filtra només afegeix
retard i una falsa sensació de seguretat.
Entorns: desenvolupament, preproducció i producció
Les tres etapes de desplegament del pipeline fan servir grups de desplegament diferents:
| Entorn | Grup de desplegament | Estratègia | Qui dispara | Dades |
|---|---|---|---|---|
| Desenvolupament | dg-mercadofresco-tienda-desarrollo |
Al lloc, AllAtOnce |
Automàtic | Sintètiques |
| Preproducció | dg-mercadofresco-tienda-preproduccion |
Blue/green | Automàtic | Anonimitzades |
| Producció | dg-mercadofresco-tienda-produccion |
Blue/green + canari | Després d'aprovació | Reals |
Aquí cal ser honest sobre el que MercadoFresco té avui: els tres entorns viuen al mateix compte
111122223333, separats per etiquetes, subxarxes i polítiques d'IAM. Funciona, però té tres límits
que convé anomenar sense adorns. El radi d'explosió no està acotat: un error en una política o un
script amb el perfil equivocat pot tocar producció des d'una tasca de desenvolupament. Els límits de
servei es comparteixen: les proves de càrrega en preproducció consumeixen la mateixa quota d'invocacions
concurrents de Lambda que la botiga real. I la factura no se separa de debò: les etiquetes ajuden
—ho veurem a 11-02— però no són una frontera.
La separació seriosa és un compte d'AWS per entorn, amb el pipeline en un compte d'eines que assumeix rols als altres. Això és AWS Organizations i és 09-04; aquí n'hi ha prou de saber que la separació per etiquetes és un punt de partida i no la destinació, i que el pipeline ja està preparat per al canvi perquè cada etapa apunta a un grup de desplegament independent.
Proves de fum i portes de qualitat amb una acció Lambda
Les proves de fum verifiquen que el que s'ha desplegat funciona de debò, contra l'entorn tot just actualitzat i per la porta principal:
# tests/humo/test_flujo_compra.py -> l'executa build-mercadofresco-humo
import os, requests, pytest
BASE = os.environ["URL_ENTORNO"] # p. ex. https://preprod.mercadofresco.example
def test_salut_respon_i_diu_la_versio():
r = requests.get(f"{BASE}/salud", timeout=5)
assert r.status_code == 200
assert r.json()["version"] == os.environ["VERSION_ESPERADA"]
def test_flux_de_compra_complet():
s = requests.Session()
assert s.get(f"{BASE}/productos?categoria=fruta", timeout=5).status_code == 200
s.post(f"{BASE}/carrito", json={"sku": "FRUT-0012", "uds": 2}, timeout=5)
r = s.post(f"{BASE}/pedidos", timeout=10, json={
"franja_entrega": "tarde", "metodo_pago": "tarjeta_prueba",
"clave_idempotencia": f"humo-{os.environ['ID_EJECUCION']}"})
assert r.status_code == 201
assert r.json()["estado"] == "confirmado"
assert r.elapsed.total_seconds() < 2.0 # l'objectiu de 07-05La tercera asserció és la que sol faltar i la que més incidents evita: no només que la comanda es confirmi, sinó que es confirmi dins del pressupost de latència. Una comanda que triga 6 segons està trencada encara que retorni 201.
La porta de qualitat va un pas més enllà: després de desplegar en producció, una acció d'invocació consulta les mètriques reals i decideix si el pipeline continua.
# mercadofresco-puerta-calidad
import boto3
from datetime import datetime, timedelta, timezone
cp = boto3.client("codepipeline")
cw = boto3.client("cloudwatch")
LLINDARS = {
"TiempoConfirmacionPedido": {"stat": "p95", "max": 1500},
"PedidosConfirmados": {"stat": "Sum", "min": 5},
}
def _metrica(nom, stat, minuts=10):
"""Retorna el valor agregat, o None si no hi ha dades."""
fi = datetime.now(timezone.utc)
es_percentil = stat.startswith("p")
r = cw.get_metric_statistics(
Namespace="MercadoFresco/Tienda", MetricName=nom,
StartTime=fi - timedelta(minutes=minuts), EndTime=fi, Period=60,
ExtendedStatistics=[stat] if es_percentil else None,
Statistics=None if es_percentil else [stat])
punts = r["Datapoints"]
if not punts:
return None
if es_percentil:
return max(p["ExtendedStatistics"][stat] for p in punts)
return sum(p[stat] for p in punts)
def handler(event, context):
job_id = event["CodePipeline.job"]["id"]
fallades = []
try:
for nom, regla in LLINDARS.items():
valor = _metrica(nom, regla["stat"])
if valor is None:
# Sense dades NO es aprovar: es no saber. I no saber, en produccio, es fallar.
fallades.append(f"{nom}: sense dades en 10 min")
elif "max" in regla and valor > regla["max"]:
fallades.append(f"{nom} {regla['stat']}={valor:.0f} > {regla['max']}")
elif "min" in regla and valor < regla["min"]:
fallades.append(f"{nom}={valor:.0f} < {regla['min']}")
if fallades:
cp.put_job_failure_result(jobId=job_id,
failureDetails={"type": "JobFailed", "message": "; ".join(fallades)[:265]})
else:
cp.put_job_success_result(jobId=job_id)
except Exception as e:
# Davant d'un error inesperat, fallar. Mai aprovar per defecte.
cp.put_job_failure_result(jobId=job_id,
failureDetails={"type": "JobFailed", "message": str(e)[:265]})Tres decisions de disseny en aquest codi, i les tres són deliberades. Sense dades es falla, perquè
PedidosConfirmados a zero durant deu minuts en horari comercial no vol dir «tot bé», vol dir
que ningú no està comprant —que és exactament el que vols detectar—. Davant d'una excepció es falla,
perquè una porta que s'obre quan es trenca no és una porta. I és obligatori cridar
put_job_success_result o put_job_failure_result: si la Lambda acaba sense fer-ho, l'acció es queda
en InProgress fins al timeout d'una hora, igual que el ganxo de CodeDeploy a 08-03.
Reintents, execucions en cua i mode superposat
# Reintentar nomes el que ha fallat, sense tornar a construir
aws codepipeline retry-stage-execution \
--pipeline-name pipeline-mercadofresco-tienda \
--stage-name DesplegarPreproduccion \
--pipeline-execution-id 7a1f2c33-EXAMPLE \
--retry-mode FAILED_ACTIONS \
--profile mercadofresco-dev --region eu-west-1FAILED_ACTIONS reintenta només les accions fallides i ALL_ACTIONS l'etapa sencera: el primer quan
la fallada ha estat transitòria, el segon si l'etapa té accions interdependents. Els tres modes
d'execució són una decisió que es pren una vegada i afecta tots els dies:
| Mode | Comportament | Quan |
|---|---|---|
SUPERSEDED (per defecte) |
El canvi nou substitueix el que espera | Lliurament continu ràpid |
QUEUED |
S'encuen i s'executen en ordre | Quan cada canvi s'ha de desplegar |
PARALLEL |
Execucions simultànies independents | Pipelines per branca de funcionalitat |
SUPERSEDED és el correcte per a MercadoFresco i convé entendre per què, perquè sembla que es
perdin canvis i no és així. Si el Luis empeny tres commits en deu minuts, no té sentit desplegar tres
vegades: el tercer conté els dos anteriors, així que desplegar només l'últim és alhora més
ràpid i equivalent. El que substitueix és l'execució en espera, no el codi. L'excepció és quan cada
execució té un efecte propi que no és acumulatiu —per exemple un pipeline que publica versions
etiquetades—; allà QUEUED és el correcte.
Permisos: rol del pipeline, rols per acció i entre comptes
El rol del pipeline és el que assumeix CodePipeline per orquestrar, i mereix la mateixa disciplina de 04-01: no executa la feina, només la llança.
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "Artefactos", "Effect": "Allow",
"Action": ["s3:GetObject", "s3:GetObjectVersion", "s3:PutObject", "s3:GetBucketVersioning"],
"Resource": ["arn:aws:s3:::mercadofresco-artefactos",
"arn:aws:s3:::mercadofresco-artefactos/*"] },
{ "Sid": "LanzarConstrucciones", "Effect": "Allow",
"Action": ["codebuild:StartBuild", "codebuild:BatchGetBuilds"],
"Resource": "arn:aws:codebuild:eu-west-1:111122223333:project/build-mercadofresco-*" },
{ "Sid": "LanzarDespliegues", "Effect": "Allow",
"Action": ["codedeploy:CreateDeployment", "codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig", "codedeploy:RegisterApplicationRevision"],
"Resource": "arn:aws:codedeploy:eu-west-1:111122223333:*:app-mercadofresco-*" },
{ "Sid": "PuertaDeCalidad", "Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-puerta-calidad" },
{ "Sid": "Origen", "Effect": "Allow",
"Action": "codeconnections:UseConnection",
"Resource": "arn:aws:codeconnections:eu-west-1:111122223333:connection/a1b2c3d4-EXAMPLE" },
{ "Sid": "CifradoDeArtefactos", "Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:eu-west-1:111122223333:key/*",
"Condition": {"StringEquals": {"kms:ViaService": "s3.eu-west-1.amazonaws.com"}} }
]
}Fixa't que el rol del pipeline no té permisos sobre EC2, Aurora ni SQS: només pot llançar
CodeBuild i CodeDeploy, que al seu torn actuen amb els seus propis rols. Aquesta separació en tres rols
—el del pipeline, el de la construcció i el de la instància— és el que impedeix que comprometre'n un doni
accés a tot. I kms:GenerateDataKey és imprescindible: sense ell, el pipeline pot llegir artefactes però no
escriure'ls en un bucket xifrat, amb un error d'accés denegat que no esmenta KMS.
L'accés entre comptes, que serà el model de 09-04, funciona amb un rol assumible: el compte de
producció publica un rol que confia en el compte d'eines, i l'acció de desplegament porta un
roleArn. La condició imprescindible: la clau de KMS que xifra el bucket d'artefactes ha de ser una
clau de client compartida amb els comptes de destinació, perquè la clau gestionada per AWS no es pot
compartir i el desplegament fallaria en no poder desxifrar l'artefacte.
Notificacions cap a correu i Slack
Dos mecanismes, com a 08-01, i convé triar bé:
aws codestar-notifications create-notification-rule \
--name notif-pipeline-mercadofresco \
--resource arn:aws:codepipeline:eu-west-1:111122223333:pipeline-mercadofresco-tienda \
--detail-type FULL \
--event-type-ids codepipeline-pipeline-pipeline-execution-failed \
codepipeline-pipeline-manual-approval-needed \
codepipeline-pipeline-stage-execution-failed \
--targets TargetType=SNS,TargetAddress=arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
--profile mercadofresco-dev --region eu-west-1Fixa't en el que NO és a la llista: codepipeline-pipeline-pipeline-execution-succeeded. Amb 20
execucions diàries, notificar els èxits són 400 missatges al mes que ensenyen l'equip a ignorar el canal,
i llavors l'avís que importa —la fallada d'un divendres— passa desapercebut. Notifica només el que és
accionable: fallades i aprovacions pendents. És la mateixa disciplina de 05-01 amb les alarmes.
Per a Slack, AWS Chatbot connecta el tema d'SNS amb un canal i afegeix l'important: botons per aprovar
des del xat mateix, cosa que baixa el temps d'aprovació de la Marta d'hores a minuts. I per a lògica
pròpia, EventBridge rep tots els esdeveniments del pipeline amb source: ["aws.codepipeline"], com vam
veure a 07-03.
El pipeline com a codi
Tot el d'aquesta lliçó s'ha creat amb aws codepipeline create-pipeline i un JSON al portàtil del
Luis. És a dir: hem automatitzat el desplegament de l'aplicació amb una eina que al seu torn està
configurada a mà. Si algú esborra el pipeline, no hi ha manera fiable de recrear-lo; si la Marta canvia un
llindar de la porta de qualitat, no queda registre de qui ni de per què.
La sortida provisional és exportar la definició i versionar-la a mercadofresco-infra:
aws codepipeline get-pipeline --name pipeline-mercadofresco-tienda \
--query 'pipeline' --output json > pipeline-mercadofresco-tienda.json
aws codepipeline update-pipeline --cli-input-json file://pipeline-mercadofresco-tienda.json \
--profile mercadofresco-dev --region eu-west-1Funciona, però és un pedaç: el JSON exportat porta metadades que cal netejar, no parametritza per entorn i no gestiona dependències entre recursos. La solució de debò és definir el pipeline —i tota la infraestructura— com a codi, i això és el mòdul 9: CloudFormation a 09-01 i CDK a 09-02.
Cost i neteja
| Concepte | Càlcul | Cost |
|---|---|---|
| CodePipeline V2, ~20 execucions/dia | ~1.700 min d'acció/mes × 0,002 | 3,40 USD |
| Lambda de la porta de qualitat | 600 invocacions de 3 s | ~0,01 USD |
| Artefactes a S3, amb caducitat de 60 dies | ~3 GB | 0,07 USD |
| CodePipeline | ≈ 3,50 USD/mes | |
| Mòdul 8 complet (amb CodeBuild i CodeDeploy) | ≈ 28 USD/mes |
Vint-i-vuit dòlars al mes per eliminar els desplegaments manuals d'un equip de tres persones. La comparació honesta no és contra zero: és contra el cost d'una caiguda de trenta minuts un divendres a les 19:00, amb 900 comandes/hora, més les hores que el Luis dedicava a desplegar a mà.
# Retenir canvis sense esborrar res: la transicio deshabilitada
aws codepipeline disable-stage-transition --pipeline-name pipeline-mercadofresco-tienda \
--stage-name DesplegarProduccion --transition-type Inbound \
--reason "Pic del divendres: sense desplegaments fins al dilluns" \
--profile mercadofresco-dev --region eu-west-1
# Esborrar un pipeline de proves (no esborra el bucket ni els seus artefactes)
aws codepipeline delete-pipeline --name pipeline-pruebas-borrar \
--profile mercadofresco-dev --region eu-west-1Esborrar el pipeline no esborra els artefactes, que continuen ocupant i facturant a S3. És el residu més comú d'aquest mòdul: revisa el bucket i la seva regla de cicle de vida.
Errors Habituals i Consells
Deixar DetectChanges: true amb triggers de V2 definits. Cada push llança dues execucions, es trepitgen i
ningú no entén per què.
Bucket d'artefactes sense versionat. El pipeline no funciona i el missatge no és evident. És un requisit dur.
Sense regla de cicle de vida al bucket. Vint execucions diàries de 45 MB són 27 GB a l'any de ZIP que ningú no desplegarà.
Reconstruir l'artefacte a cada etapa. Trenca la garantia d'immutabilitat: el que aprova la Marta deixa de ser el que surt a producció. Construeix una vegada, desplega el mateix objecte.
Una aprovació manual sense CustomData útil. Es converteix en un botó que es prem sense mirar, i
llavors és només retard.
Que aprovi la mateixa persona que ha programat el canvi. L'aprovació deixa de filtrar res.
Una porta de qualitat que aprova quan no hi ha dades o quan llança una excepció. Una porta que s'obre en trencar-se no és una porta.
Oblidar put_job_success_result / put_job_failure_result a la Lambda d'invocació. L'acció es
queda en InProgress fins al timeout d'una hora.
Notificar els èxits. Quatre-cents missatges al mes ensenyen l'equip a ignorar el canal, i l'avís que importa es perd.
Un rol de pipeline amb PowerUserAccess. El pipeline només ha de llançar accions; la feina la fan
els rols de CodeBuild i CodeDeploy. Tres rols separats, tres radis d'explosió acotats.
Oblidar kms:GenerateDataKey al rol del pipeline, o fer servir la clau gestionada per AWS en un pipeline
entre comptes: el desplegament falla en no poder desxifrar l'artefacte.
Consell: fes servir V2 i filtra per ruta de fitxer. Un canvi al README no ha de gastar minuts ni
molestar ningú.
Consell: connecta el pipeline a les PR, no només a main. És el que tanca la protecció de branca que
vam deixar oberta a 08-01.
Consell: posa condicions de sortida a l'aprovació manual. Un control temporal sense criteri per retirar-lo es queda per sempre.
Consell: aprèn a fer servir la transició deshabilitada. És la manera neta de dir «avui no es desplega a producció» sense desmuntar res.
Exercicis
Exercici 1: el pipeline que va desplegar el que no tocava
Divendres, 18:50. La Marta aprova la versió 1.7.0 després de veure que les proves de fum van passar en preproducció.
El pipeline desplega en producció i al cap de quatre minuts salta mercadofresco-alb-latencia-alta.
CodeDeploy reverteix. Investigant, descobreixen que l'etapa de producció reconstruïa l'artefacte amb una
acció de CodeBuild pròpia en lloc de reutilitzar PaqueteTienda, i que entre la construcció de
preproducció (18:20) i la de producció (18:52) algú havia fusionat una altra PR a main.
Respon: (a) què es va desplegar exactament en producció; (b) per què les proves de fum de preproducció no valien res en aquest disseny; (c) com es corregeix el pipeline; (d) quines altres dues salvaguardes del mòdul haurien limitat el dany; (e) quina mesura de procés proposaries per a l'horari.
Exercici 2: dissenyar la porta de qualitat
MercadoFresco vol passar de lliurament continu a desplegament continu: treure l'aprovació de la Marta i
que la porta de qualitat decideixi sola. Dades: en horari comercial es confirmen entre 120 i 900 comandes per
hora; de 02:00 a 07:00 n'hi ha entre 0 i 5; el p95 normal de TiempoConfirmacionPedido és 400 ms i l'objectiu
és no superar 1.500 ms; el desplegament canari de la Lambda de cobraments dura 5 minuts i el blue/green de la
botiga triga uns 12 a completar-se.
Dissenya la porta: quines mètriques, quins llindars, quina finestra d'observació, què fa davant l'absència de dades i en quin punt del pipeline es col·loca. Justifica cada decisió i digues quines tres condicions s'haurien de complir abans de treure l'aprovació manual.
Exercici 3: el pipeline de les tres emergències
El dilluns passen tres coses. (a) A les 09:15, el Luis empeny cinc commits seguits a main corregint
un error d'estil; el pipeline arrenca cinc vegades i la quarta es queda esperant. (b) A les 11:40,
l'etapa DesplegarPreproduccion falla perquè l'agent de CodeDeploy d'una instància no ha respost; la resta
del pipeline havia anat bé i la construcció va trigar 9 minuts. (c) A les 17:20 cal desplegar una
correcció urgent de seguretat, però la Marta és en un avió i no pot aprovar fins a les 21:00.
Per a cada situació indica què passa amb la configuració descrita a la lliçó, quina ordre o acció concreta faries servir, i què canviaries perquè no torni a ser un problema.
Solucions
Solució 1
(a) Es va desplegar la 1.7.0 més el canvi de la PR fusionada a les 18:45, és a dir, codi que mai no va passar per preproducció ni per l'aprovació. La Marta va aprovar una cosa i en va sortir una altra. I el greu és que res de la interfície no ho indicava: l'execució continuava dient-se 1.7.0.
(b) Perquè validaven un artefacte diferent del que es va desplegar. Una prova només diu alguna cosa sobre
el binari concret que va provar; si reconstrueixes després, has llençat aquesta informació. Aquí la
reconstrucció va agafar main en el seu estat del moment, no el commit de l'execució, així que va arrossegar
un canvi nou. És la diferència entre «hem provat això» i «hem provat una cosa semblant a això».
(c) Eliminant l'acció de construcció de l'etapa de producció i fent que l'acció de
desplegament consumeixi el PaqueteTienda produït a l'etapa de construcció, que és el mateix objecte d'S3
que ja es va desplegar en desenvolupament i preproducció:
Construir una vegada, desplegar moltes és la regla, i no admet excepcions «perquè en producció cal
una altra configuració»: la configuració per entorn es resol en temps d'execució amb Parameter
Store, no reconstruint el paquet. Si calgués comprovar-ho, n'hi ha prou de verificar que el commit de
les metadades de l'artefacte coincideix amb el de l'execució del pipeline.
(d) La porta de qualitat hauria detectat la degradació per mètriques fins i tot si la reversió de CodeDeploy no hagués saltat, aturant el pipeline i deixant-ne constància. I la transició deshabilitada cap a producció durant el pic del divendres hauria impedit el desplegament a les 18:50, que és quan més car surt equivocar-se. Val la pena notar que la reversió automàtica va funcionar: quatre minuts de degradació en lloc de trenta, i sense que ningú hagués de fer res. La fallada era aigües amunt, en el disseny del pipeline, no en el mecanisme de seguretat.
(e) Una finestra de desplegament explícita: res a producció els divendres de 16:00 a 22:00, ni en festius ni després de les 18:00 qualsevol dia. No és desconfiança en el pipeline, és aritmètica: el cost d'un incident al pic amb 900 comandes/hora és diverses vegades el d'un dilluns al matí, i a les 19:00 d'un divendres hi ha menys gent disponible per respondre. Implementar-ho amb la transició deshabilitada de manera programada, no confiant que algú se'n recordi.
Solució 2
Mètriques i llindars. Tres senyals complementaris, perquè un de sol és fàcil d'enganyar:
| Mètrica | Llindar | Per què |
|---|---|---|
TiempoConfirmacionPedido p95 |
> 1.500 ms → fallar | Objectiu de negoci de 07-05; el normal és 400 ms |
PedidosConfirmados (Sum) |
Caiguda > 40 % respecte a la mateixa franja dels 7 dies previs | Detecta que ningú no compra, que és la fallada silenciosa |
| Errors 5xx de l'ALB (ràtio) | > 1 % de les peticions | Detecta la fallada evident i ràpida |
El llindar relatiu de PedidosConfirmados és la decisió clau de l'exercici. Un llindar absolut no
pot funcionar quan el volum normal oscil·la entre 0 i 900 segons l'hora: si poses «almenys 5 comandes
en 10 minuts», la porta farà fallar tots els desplegaments nocturns legítims; si poses 0, no detectarà mai
res. Comparar amb la mateixa franja horària dels set dies anteriors s'adapta sol al patró real,
inclòs el pic del divendres.
Finestra d'observació: 15 minuts, i la raó és aritmètica. El blue/green triga uns 12 minuts a completar-se, així que una finestra més curta mesuraria en part l'entorn blau i en part el verd, barrejant les dues versions i diluint qualsevol degradació. Quinze minuts garanteixen almenys tres de trànsit íntegrament sobre la versió nova.
Davant l'absència de dades, depèn de l'hora, i això és el que fa la porta utilitzable de nit. En
horari comercial (07:00-02:00), sense dades de PedidosConfirmados és fallar: vol dir que ningú no
compra. De 02:00 a 07:00 és el normal, així que en aquesta franja PedidosConfirmados s'ignora i la porta es
recolza en TiempoConfirmacionPedido mesurat amb trànsit sintètic —una prova de fum cada minut des de
CloudWatch Synthetics—, sense el qual de matinada no hi hauria res a mesurar. La regla general es manté:
sense dades no és aprovar; és no saber, i no saber només és acceptable si has decidit per endavant que en
aquesta franja no hi ha res a saber.
On es col·loca: dues portes, no una. Una immediatament després del desplegament canari de la Lambda
de cobraments, amb finestra de 5 minuts ajustada a la durada del canari, per tallar abans que el
canari promocioni al 100 %. I una altra després del blue/green de la botiga, amb la finestra de 15 minuts,
dins del terminationWaitTimeInMinutes de 30 perquè la reversió continuï sent de 90 segons. Una
porta que avalua quan ja s'ha terminat l'entorn blau arriba tard.
Les tres condicions per treure l'aprovació manual, que són les que la Marta ja va escriure: que les proves de fum cobreixin el flux de compra complet —inclòs el cobrament amb targeta de prova—, que la reversió automàtica s'hagi provat a propòsit almenys tres vegades amb temps mesurats, i que passin dos mesos sense cap desplegament que s'hagi hagut de revertir a mà. A això convé afegir-hi una quarta que l'exercici suggereix: que la porta de qualitat hagi funcionat en mode observació durant un mes — registrant el que hauria fet sense arribar a bloquejar— i no hagi produït falsos positius. Treure l'aprovació humana i estrenar la porta automàtica el mateix dia és canviar un control per una hipòtesi.
Solució 3
(a) Els cinc commits. Amb el mode SUPERSEDED per defecte, no s'executen cinc pipelines complets:
la primera execució segueix el seu curs i les següents es van substituint entre si, de manera que en queden
dues —la que corria i l'última—. No es perd res, perquè el cinquè commit conté els quatre
anteriors. Així que tècnicament el comportament és correcte. El que sí que és un malbaratament és haver
arrencat: eren correccions d'estil. L'arranjament és el filtre per ruta de fitxer de V2, amb
excludes per a **/*.md i docs/**, i si el canvi afecta app/ però és trivial, el flux correcte és
agrupar-ho en un sol commit abans de fusionar. Res a fer en aquell moment tret de deixar que acabi.
(b) La fallada de l'agent. És una fallada transitòria d'infraestructura, no del codi, així que no cal reconstruir: reconstruir llençaria nou minuts i, pitjor, produiria un artefacte nou amb el risc de l'exercici 1.
aws codepipeline retry-stage-execution \
--pipeline-name pipeline-mercadofresco-tienda \
--stage-name DesplegarPreproduccion \
--pipeline-execution-id <id> --retry-mode FAILED_ACTIONS \
--profile mercadofresco-dev --region eu-west-1FAILED_ACTIONS reintenta només l'acció de desplegament reutilitzant el mateix PaqueteTienda. Perquè no
torni a ser un problema: l'agent instal·lat com a associació de Systems Manager amb actualització
automàtica, com vam veure a 08-03, i una alarma sobre l'estat de l'agent a les instàncies. I si és
recurrent, revisar si a la instància li falta sortida de xarxa o si l'ASG la va crear amb una AMI sense agent.
(c) La correcció urgent sense la Marta. El disseny ja té la resposta i l'important és no
improvisar. L'aprovació admet diversos aprovadors: qui tingui codepipeline:PutApprovalResult pot
autoritzar, així que ha d'existir un suplent designat per endavant —el Luis no, perquè és qui va escriure
el canvi i la regla que no aprovi l'autor és justament la que protegeix aquí; el suplent seria un
segon responsable tècnic, o la Sara si l'organització ho accepta per a casos documentats—. Si de debò no
hi ha ningú, la sortida és AWS Chatbot a Slack, que permet a la Marta aprovar des del mòbil tan bon punt
tingui xarxa, inclosa la de l'avió.
El que no s'ha de fer és saltar-se el pipeline i desplegar a mà amb create-deployment: es perden
les proves de fum, la porta de qualitat i la traçabilitat, justament en un canvi de seguretat que és dels
que més convé tenir registrats. I tampoc fer servir una variable com omitirHumo, per la raó de la
lliçó: una porta que es pot saltar se saltarà.
Com a mesura permanent, dues coses. Una política d'aprovadors escrita amb titular i suplent, revisada cada trimestre. I valorar un camí ràpid documentat per a correccions de seguretat: un pipeline amb les mateixes portes automàtiques però amb l'aprovació humana substituïda per la notificació immediata a tot l'equip i un post-mortem obligatori en 24 hores. Que un canvi sigui urgent no justifica saltar-se els controls automàtics; com a molt justifica substituir el control humà per una rendició de comptes posterior.
Conclusió
La seqüència que vivia al cap del Luis ara és pipeline-mercadofresco-tienda, una definició
versionada que s'executa igual totes les vegades. Un commit a main que toqui app/ arrenca el flux,
construeix, prova, comprova la qualitat, aplica la fase d'expansió de l'esquema, desplega en desenvolupament,
passa les proves de fum, desplega en preproducció, torna a passar-les, avisa la Marta amb la informació
per decidir en trenta segons, desplega en producció amb blue/green i canari, i comprova amb les
mètriques reals de MercadoFresco/Tienda que la botiga continua sana. Ningú no escriu cap ordre.
Saps distingir lliurament continu de desplegament continu —la diferència és una persona entre la
validació i producció— i per què MercadoFresco tria el primer amb condicions de sortida escrites,
perquè un control temporal sense criteri per retirar-lo es queda per sempre. Coneixes l'estructura del
pipeline i els dos matisos que més joc donen: que les accions amb el mateix runOrder corren en
paral·lel i que una transició deshabilitada és la manera neta de dir «avui no es desplega». I sobretot
tens la garantia que dona sentit a tota la resta: l'artefacte es construeix una vegada i el mateix
objecte d'S3 recorre els tres entorns, de manera que el que la Marta aprova i el que surt a producció són el
mateix fitxer byte a byte —trencar això, com a l'exercici 1, invalida totes les proves anteriors—.
Saps per què V2 és l'elecció per defecte: els disparadors amb filtre per branca, etiqueta i ruta de
fitxer eviten que un canvi al README gasti minuts, i les execucions sobre pull request són el que
tanca la protecció de main que va quedar oberta a 08-01. Coneixes l'acció d'origen amb
CodeConnections i els seus dos paranys —DetectChanges: true amb triggers produeix execucions duplicades,
i una connexió en PENDING falla amb un error que no l'esmenta—, el namespace que exposa les variables
exportades pel buildspec de 08-02, i l'aprovació manual que serveix de debò: CustomData amb
versió, commit, autor, estat del fum i migració pendent, més un ExternalEntityLink al diff.
Tens les portes de qualitat amb les seves tres decisions deliberades —sense dades es falla, davant
d'una excepció es falla, i cal cridar sempre put_job_success_result o put_job_failure_result—, els
tres modes d'execució amb SUPERSEDED com el correcte perquè el commit nou conté els anteriors, i el
reintent amb FAILED_ACTIONS que no reconstrueix. Saps separar els permisos en tres rols —el del
pipeline, que només llança; el de la construcció; i el de la instància— i que kms:GenerateDataKey és el que
falta quan un pipeline no pot escriure en un bucket xifrat. I saps notificar només el que és accionable:
fallades i aprovacions, mai els èxits, o el canal es torna soroll.
Tot per uns 28 dòlars al mes per al mòdul complet, amb dos residus que cal vigilar: els artefactes que sobreviuen al pipeline esborrat i el bucket sense cicle de vida.
Queden dos caps solts que la lliçó ha deixat a la vista a propòsit. El primer és que els tres
entorns comparteixen el compte 111122223333: el radi d'explosió no està acotat, els límits de
servei es comparteixen i la factura no se separa de debò. El segon és més incòmode: aquest pipeline, que
existeix perquè ningú no desplegui a mà, s'ha creat a mà, amb un JSON al portàtil del Luis i un
create-pipeline.
A 08-05, «Un pipeline d'extrem a extrem», tanquem el mòdul seguint un canvi real des del
portàtil del Luis fins a producció: el camp «franja horària de lliurament», que toca la botiga, un consumidor
de cola-mercadofresco-pedidos, l'esquema d'Aurora i el contracte de l'esdeveniment PedidoConfirmado. Veurem
pas a pas què es comprova i què passa si falla; com s'aplica expansió-contracció a Aurora i es
versiona un esdeveniment sense trencar els consumidors existents, amb l'ordre correcte entre productor i
consumidor que tanca l'incident del mòdul 7; la piràmide de proves i quines bloquegen; les mètriques
DORA de MercadoFresco abans i després; el runbook de reversió quan la fallada es detecta mitja hora
tard; i les banderes de funcionalitat amb AppConfig que converteixen «desplegar un divendres» en una decisió
de negoci i no de fe.
Curs d'AWS
Mòdul 1: Introducció a AWS
- Què és AWS?
- Configuració del teu compte d'AWS
- Infraestructura global d'AWS
- Consola d'administració d'AWS
- AWS CLI i SDK
Mòdul 2: Serveis principals d'AWS
Mòdul 3: Xarxes i lliurament de contingut
- Amazon VPC
- Grups de seguretat i llistes de control d'accés
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Mòdul 4: Seguretat i identitat
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager i Parameter Store
- AWS Shield
- AWS WAF
Mòdul 5: Monitoratge i gestió
- Amazon CloudWatch
- AWS X-Ray i traçabilitat distribuïda
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Mòdul 6: Bases de dades
- Com triar la base de dades adequada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Mòdul 7: Integració d'aplicacions
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrons d'integració: idempotència, reintents i cues de missatges fallits
