MercadoFresco tiene ya las tres piezas: un repositorio gobernado, una construcción que verifica y produce
artefactos, y un despliegue que sabe volver atrás solo. Y sin embargo, cuando Luis fusiona una PR sigue
teniendo que acordarse de lanzar la construcción, copiar la ruta del ZIP, escribir create-deployment con
el bucket y la clave correctos, esperar, mirar si fue bien, y repetirlo para el entorno siguiente. Ocho
comandos, cuatro esperas y una secuencia que solo vive en su cabeza.
AWS CodePipeline es la orquestación que falta. Define el camino que recorre un cambio desde el commit hasta producción como una serie de etapas que se ejecutan en orden, cada una con sus acciones, con artefactos que fluyen de una a otra y con la garantía de que una etapa no empieza si la anterior no terminó bien. Lo que hoy es memoria de una persona pasa a ser una definición versionada que se ejecuta igual todas las veces.
Aviso de coste. CodePipeline V1 cuesta 1 USD por pipeline activo y mes —un pipeline es activo si tuvo alguna ejecución— con el primero gratis. V2 cobra 0,002 USD por minuto de acción, sin cuota fija, con 100 minutos gratis al mes. Para MercadoFresco, con unas 20 ejecuciones diarias, V2 sale a unos 4 USD al mes. A eso se suman los minutos de CodeBuild de 08-02 y el almacenamiento del bucket de artefactos, que crece rápido si nadie le pone ciclo de vida. Datos ficticios.
Contenido
- Entrega continua frente a despliegue continuo
- Estructura: pipeline, etapas, acciones, ejecuciones y transiciones
- Los artefactos y el bucket que los mueve
- El pipeline de MercadoFresco
- Tipos V1 y V2, disparadores y variables
- La acción de origen con CodeConnections
- Construcción, despliegue y acciones en paralelo con
runOrder - Aprobación manual: qué debe leer Marta en treinta segundos
- Entornos: desarrollo, preproducción y producción
- Pruebas de humo y puertas de calidad con una acción Lambda
- Reintentos, ejecuciones en cola y modo superpuesto
- Permisos: rol del pipeline, roles por acción y entre cuentas
- Notificaciones hacia correo y Slack
- El pipeline como código
- Coste y limpieza
- Errores comunes y consejos
- Ejercicios
- Conclusión
Entrega continua frente a despliegue continuo
Los dos términos se confunden constantemente y la diferencia es una sola cosa: si hay o no una persona entre la validación y producción.
| Aspecto | Entrega continua | Despliegue continuo |
|---|---|---|
| Qué automatiza | Todo hasta la puerta de producción | Todo, producción incluida |
| Quién decide que salga | Una persona aprueba | Nadie: si pasa las pruebas, sale |
| Requisito previo | Pipeline fiable | Pipeline fiable y pruebas en las que confías |
| Tiempo hasta producción | Minutos + espera humana | Minutos |
| Riesgo por despliegue | Igual | Igual, pero más despliegues y más pequeños |
| Cuándo elegirlo | Al empezar; cambios sensibles | Cuando la reversión automática está probada |
Hay una trampa habitual: pensar que la aprobación manual reduce el riesgo. No lo reduce por sí sola. Lo reduce solo si quien aprueba tiene información suficiente para decidir; si Marta pulsa «aprobar» sin mirar nada, la aprobación es una fricción que retrasa el despliegue sin filtrar nada. Y hay un efecto secundario perverso: las aprobaciones acumulan cambios —«ya que apruebo, que vayan los tres juntos»— y un despliegue de tres cambios es más difícil de diagnosticar que tres despliegues de uno.
La decisión de MercadoFresco: entrega continua, con aprobación manual antes de producción, y con fecha de revisión. Marta lo plantea explícitamente como una etapa transitoria: la aprobación se mantiene hasta que se cumplan tres condiciones —que las pruebas de humo cubran el flujo de compra completo, que la reversión automática se haya probado a propósito al menos tres veces, y que pasen dos meses sin un despliegue que haya que revertir a mano—. Cumplidas las tres, la aprobación desaparece. Poner condiciones de salida a un control manual es lo que impide que se quede para siempre «por si acaso».
Estructura: pipeline, etapas, acciones, ejecuciones y transiciones
| Concepto | Qué es | Regla que hay que recordar |
|---|---|---|
| Pipeline | El flujo completo | Un pipeline por aplicación desplegable |
| Etapa | Un grupo de acciones con nombre | Solo empieza si la anterior tuvo éxito |
| Acción | Una unidad de trabajo | Seis tipos: origen, construcción, prueba, despliegue, aprobación, invocación |
| Ejecución | Un recorrido concreto | Identificada, con su commit y sus artefactos |
| Transición | El paso entre dos etapas | Se puede deshabilitar para retener cambios |
| Artefacto | Lo que fluye entre acciones | Va por S3, no en memoria |
Dos matices que ahorran sorpresas. Dentro de una etapa, las acciones con el mismo runOrder se ejecutan
en paralelo, y las de runOrder mayor esperan a las anteriores; es el mecanismo para paralelizar sin
crear etapas nuevas. Y una transición deshabilitada es una herramienta operativa poco conocida y muy
útil: durante el pico del viernes, Marta deshabilita la transición hacia producción y los cambios se
acumulan validados en preproducción, listos para salir el lunes con un clic.
Los artefactos y el bucket que los mueve
Cada acción puede declarar artefactos de entrada y de salida. CodePipeline los guarda comprimidos en un bucket de S3 y cada acción recibe los suyos descomprimidos. Tres consecuencias prácticas. El bucket debe tener versionado activado: sin él el pipeline no funciona, y es un requisito duro. Los artefactos se acumulan: con 20 ejecuciones diarias y 45 MB por artefacto son 27 GB al año, así que sin regla de ciclo de vida la factura de S3 crece sola. Y es el punto donde se garantiza la inmutabilidad: el ZIP que aprueba Marta es literalmente el mismo objeto que se despliega en producción, sin reconstruirse entre entornos, de modo que «lo que se validó en preproducción» y «lo que salió» son el mismo fichero byte a byte. Esa garantía es la razón de ser de esta lección.
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[Construccion<br/>build-mercadofresco-tienda]
B --> C{Etapa Calidad}
C --> C1[Analisis estatico]
C --> C2[Pruebas de contrato]
C1 --> D[Migracion esquema<br/>fase EXPANDIR]
C2 --> D
D --> E[Desplegar en desarrollo<br/>dg-...-desarrollo]
E --> F[Pruebas de humo<br/>build-mercadofresco-humo]
F --> G[Desplegar en preproduccion]
G --> H[Pruebas de humo preprod]
H --> I[APROBACION MANUAL<br/>alertas-mercadofresco]
I --> J[Desplegar en produccion<br/>blue/green + canario]
J --> K[Puerta de calidad Lambda<br/>metricas MercadoFresco/Tienda]
K -->|Metricas OK| L[Ejecucion correcta]
K -->|Degradado| M[Detener y revertir]
Ocho etapas, y cada una responde a una pregunta concreta: ¿qué ha cambiado? ¿compila y pasa las pruebas? ¿cumple la calidad? ¿está el esquema preparado? ¿funciona en un entorno real? ¿pasa el flujo de compra? ¿lo autoriza alguien? ¿sigue sano después?
Tipos V1 y V2, disparadores y variables
| Aspecto | V1 | V2 |
|---|---|---|
| Precio | 1 USD/mes por pipeline activo | 0,002 USD por minuto de acción |
| Disparadores por filtro | No | Sí: rama, etiqueta, ruta de fichero |
| Variables de pipeline | No | Sí, con valores en cada ejecución |
| Etapas en paralelo | Limitado | Sí |
| Cuándo compensa | Pipelines con muchísimas ejecuciones | Casi siempre |
Elige V2 salvo que tengas un motivo claro. Los disparadores con filtro por sí solos justifican el
cambio: sin ellos, cualquier commit a main lanza el pipeline entero, incluida una corrección en el
README. Con ellos:
{
"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"] }
}]
}
}]
}Este disparador hace dos cosas distintas. En push a main, ejecuta el pipeline completo, pero solo si
cambió código de verdad: un cambio en docs/ no gasta minutos ni molesta a nadie. Y en pull request,
ejecuta —con las etapas de despliegue omitidas— la construcción y las pruebas, que es lo que cierra la
protección de main que dejamos abierta en 08-01: la PR no se puede fusionar si la comprobación no está
en verde.
Las variables de pipeline permiten parametrizar una ejecución sin tocar la definición. Se declaran en un
bloque variables con nombre, valor por defecto y descripción, se referencian como #{variables.nivelLog}
y se pueden fijar al lanzar la ejecución a mano. Una advertencia: si alguna vez te tienta declarar algo tipo
omitirHumo, recuerda que una variable que permite saltarse una puerta es una puerta que se saltará. Si
la pones, que su uso quede en CloudTrail y que el runbook exija justificarla por escrito.
La acción de origen con 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 campos merecen explicación. DetectChanges: false apaga el webhook implícito porque en V2 los
disparos los gobierna el bloque triggers; dejarlo en true con triggers definidos produce ejecuciones
duplicadas, que es un desconcierto clásico. OutputArtifactFormat: CODEBUILD_CLONE_REF entrega a
CodeBuild una referencia al repositorio en lugar de un ZIP, de modo que la construcción tiene la historia
de Git disponible —necesaria para git describe o git-secrets --scan—; el precio es que el rol de
CodeBuild necesita codeconnections:UseConnection. Y el ConnectionArn debe apuntar a una conexión en
estado AVAILABLE: si sigue en PENDING, como avisamos en 08-01, la acción falla con un error de permisos
que no menciona la conexión.
Construcción, despliegue y acciones en paralelo con runOrder
La etapa de construcción consume CodigoFuente y produce el artefacto 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 es la pieza que conecta esta lección con 08-02: expone las variables que el buildspec
declaró en exported-variables, de modo que etapas posteriores pueden usar #{construccion.VERSION_APP}
sin recalcular nada. Es así como el mensaje de aprobación sabrá qué versión está aprobando Marta.
La etapa de calidad muestra el paralelismo:
{
"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" }] }
]
}Las dos tienen runOrder: 1 y corren a la vez; si cualquiera falla, la etapa falla y el pipeline se
detiene. Una tercera con runOrder: 2 esperaría a ambas. Y el despliegue consume el artefacto:
{
"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
}]
}Fíjate en que el inputArtifacts es el mismo PaqueteTienda que se desplegó en desarrollo y en
preproducción. No se reconstruye. Es la garantía de inmutabilidad que hace que las pruebas anteriores
signifiquen algo.
Aprobación manual: qué debe leer Marta en treinta segundos
Una aprobación mal diseñada es un botón que se pulsa sin mirar. Una bien diseñada da la información justa para 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": "Version #{construccion.VERSION_APP} | Commit #{construccion.COMMIT_CORTO} | Autor #{construccion.AUTOR} | Humo preprod: OK | Migracion: EXPANDIR aplicada | Ventana: NO desplegar viernes 16-22h",
"ExternalEntityLink": "https://github.com/mercadofresco/mercadofresco-tienda/compare/#{construccion.VERSION_ANTERIOR}...#{construccion.VERSION_APP}"
},
"timeoutInMinutes": 1440,
"runOrder": 1
}]
}Lo que hace útil esta aprobación son los dos campos de texto. CustomData lleva las cinco cosas que
Marta necesita: qué versión, qué commit, quién lo hizo, si las pruebas de humo pasaron en preproducción y
si hay una migración de esquema en juego, más el recordatorio de la ventana prohibida. Y
ExternalEntityLink es un enlace al diff entre la versión desplegada y la candidata: en un clic ve
exactamente qué cambia, que es la pregunta que de verdad importa.
El timeoutInMinutes: 1440 es una decisión, no un detalle: la aprobación caduca a las 24 horas y la
ejecución falla. Es correcto —un cambio que lleva tres días esperando ya no es el cambio que se validó,
porque main ha avanzado— y evita que se acumule una cola de aprobaciones zombis.
Tres reglas para que esto funcione: quien aprueba no es quien programó —o la aprobación no filtra
nada—; el permiso codepipeline:PutApprovalResult se concede solo a quien debe aprobar; y si la
aprobación se pulsa siempre sin mirar, hay que quitarla, porque un control que no filtra solo añade
retraso y una falsa sensación de seguridad.
Entornos: desarrollo, preproducción y producción
Las tres etapas de despliegue del pipeline usan grupos de despliegue distintos:
| Entorno | Grupo de despliegue | Estrategia | Quién dispara | Datos |
|---|---|---|---|---|
| Desarrollo | dg-mercadofresco-tienda-desarrollo |
En el sitio, AllAtOnce |
Automático | Sintéticos |
| Preproducción | dg-mercadofresco-tienda-preproduccion |
Blue/green | Automático | Anonimizados |
| Producción | dg-mercadofresco-tienda-produccion |
Blue/green + canario | Tras aprobación | Reales |
Aquí hay que ser honesto sobre lo que MercadoFresco tiene hoy: los tres entornos viven en la misma cuenta
111122223333, separados por etiquetas, subredes y políticas de IAM. Funciona, pero tiene tres límites
que conviene nombrar sin adornos. El radio de explosión no está acotado: un error en una política o un
script con el perfil equivocado puede tocar producción desde una tarea de desarrollo. Los límites de
servicio se comparten: las pruebas de carga en preproducción consumen la misma cuota de invocaciones
concurrentes de Lambda que la tienda real. Y la factura no se separa de verdad: las etiquetas ayudan
—lo veremos en 11-02— pero no son una frontera.
La separación seria es una cuenta de AWS por entorno, con el pipeline en una cuenta de herramientas que asume roles en las demás. Eso es AWS Organizations y es 09-04; aquí basta con saber que la separación por etiquetas es un punto de partida y no el destino, y que el pipeline ya está preparado para el cambio porque cada etapa apunta a un grupo de despliegue independiente.
Pruebas de humo y puertas de calidad con una acción Lambda
Las pruebas de humo verifican que lo desplegado funciona de verdad, contra el entorno recién actualizado y por la puerta principal:
# tests/humo/test_flujo_compra.py -> lo ejecuta build-mercadofresco-humo
import os, requests, pytest
BASE = os.environ["URL_ENTORNO"] # p. ej. https://preprod.mercadofresco.example
def test_salud_responde_y_dice_la_version():
r = requests.get(f"{BASE}/salud", timeout=5)
assert r.status_code == 200
assert r.json()["version"] == os.environ["VERSION_ESPERADA"]
def test_flujo_de_compra_completo():
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 # el objetivo de 07-05La tercera aserción es la que suele faltar y la que más incidentes previene: no solo que el pedido se confirme, sino que se confirme dentro del presupuesto de latencia. Un pedido que tarda 6 segundos está roto aunque devuelva 201.
La puerta de calidad va un paso más allá: después de desplegar en producción, una acción de invocación consulta las métricas reales y decide si el pipeline continúa.
# mercadofresco-puerta-calidad
import boto3
from datetime import datetime, timedelta, timezone
cp = boto3.client("codepipeline")
cw = boto3.client("cloudwatch")
UMBRALES = {
"TiempoConfirmacionPedido": {"stat": "p95", "max": 1500},
"PedidosConfirmados": {"stat": "Sum", "min": 5},
}
def _metrica(nombre, stat, minutos=10):
"""Devuelve el valor agregado, o None si no hay datos."""
fin = datetime.now(timezone.utc)
es_percentil = stat.startswith("p")
r = cw.get_metric_statistics(
Namespace="MercadoFresco/Tienda", MetricName=nombre,
StartTime=fin - timedelta(minutes=minutos), EndTime=fin, Period=60,
ExtendedStatistics=[stat] if es_percentil else None,
Statistics=None if es_percentil else [stat])
puntos = r["Datapoints"]
if not puntos:
return None
if es_percentil:
return max(p["ExtendedStatistics"][stat] for p in puntos)
return sum(p[stat] for p in puntos)
def handler(event, context):
job_id = event["CodePipeline.job"]["id"]
fallos = []
try:
for nombre, regla in UMBRALES.items():
valor = _metrica(nombre, regla["stat"])
if valor is None:
# Sin datos NO es aprobar: es no saber. Y no saber, en produccion, es fallar.
fallos.append(f"{nombre}: sin datos en 10 min")
elif "max" in regla and valor > regla["max"]:
fallos.append(f"{nombre} {regla['stat']}={valor:.0f} > {regla['max']}")
elif "min" in regla and valor < regla["min"]:
fallos.append(f"{nombre}={valor:.0f} < {regla['min']}")
if fallos:
cp.put_job_failure_result(jobId=job_id,
failureDetails={"type": "JobFailed", "message": "; ".join(fallos)[:265]})
else:
cp.put_job_success_result(jobId=job_id)
except Exception as e:
# Ante un error inesperado, fallar. Nunca aprobar por defecto.
cp.put_job_failure_result(jobId=job_id,
failureDetails={"type": "JobFailed", "message": str(e)[:265]})Tres decisiones de diseño en este código, y las tres son deliberadas. Sin datos se falla, porque
PedidosConfirmados a cero durante diez minutos en horario comercial no significa «todo bien», significa
que nadie está comprando —que es exactamente lo que quieres detectar—. Ante una excepción se falla,
porque una puerta que se abre cuando se rompe no es una puerta. Y es obligatorio llamar a
put_job_success_result o put_job_failure_result: si la Lambda termina sin hacerlo, la acción se queda
en InProgress hasta el timeout de una hora, igual que el gancho de CodeDeploy en 08-03.
Reintentos, ejecuciones en cola y modo superpuesto
# Reintentar solo lo que fallo, sin volver 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 solo las acciones fallidas y ALL_ACTIONS la etapa entera: el primero cuando el
fallo fue transitorio, el segundo si la etapa tiene acciones interdependientes. Los tres modos de
ejecución son una decisión que se toma una vez y afecta a todos los días:
| Modo | Comportamiento | Cuándo |
|---|---|---|
SUPERSEDED (por defecto) |
El cambio nuevo sustituye al que espera | Entrega continua rápida |
QUEUED |
Se encolan y se ejecutan en orden | Cuando cada cambio debe desplegarse |
PARALLEL |
Ejecuciones simultáneas independientes | Pipelines por rama de funcionalidad |
SUPERSEDED es el correcto para MercadoFresco y conviene entender por qué, porque suena a que se
pierden cambios y no es así. Si Luis empuja tres commits en diez minutos, no tiene sentido desplegar tres
veces: el tercero contiene a los dos anteriores, así que desplegar solo el último es a la vez más
rápido y equivalente. Lo que sustituye es la ejecución en espera, no el código. La excepción es cuando cada
ejecución tiene un efecto propio que no es acumulativo —por ejemplo un pipeline que publica versiones
etiquetadas—; ahí QUEUED es lo correcto.
Permisos: rol del pipeline, roles por acción y entre cuentas
El rol del pipeline es el que asume CodePipeline para orquestar, y merece la misma disciplina de 04-01: no ejecuta el trabajo, solo lo lanza.
{
"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"}} }
]
}Fíjate en que el rol del pipeline no tiene permisos sobre EC2, Aurora ni SQS: solo puede lanzar
CodeBuild y CodeDeploy, que a su vez actúan con sus propios roles. Esa separación en tres roles —el del
pipeline, el de la construcción y el de la instancia— es lo que impide que comprometer uno dé acceso a
todo. Y kms:GenerateDataKey es imprescindible: sin él, el pipeline puede leer artefactos pero no
escribirlos en un bucket cifrado, con un error de acceso denegado que no menciona KMS.
El acceso entre cuentas, que será el modelo de 09-04, funciona con un rol asumible: la cuenta de
producción publica un rol que confía en la cuenta de herramientas, y la acción de despliegue lleva un
roleArn. La condición imprescindible: la clave de KMS que cifra el bucket de artefactos debe ser una
clave de cliente compartida con las cuentas destino, porque la clave gestionada por AWS no se puede
compartir y el despliegue fallaría al no poder descifrar el artefacto.
Notificaciones hacia correo y Slack
Dos mecanismos, como en 08-01, y conviene elegir bien:
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-1Fíjate en lo que NO está en la lista: codepipeline-pipeline-pipeline-execution-succeeded. Con 20
ejecuciones diarias, notificar los éxitos son 400 mensajes al mes que enseñan al equipo a ignorar el canal,
y entonces el aviso que importa —el fallo de un viernes— pasa desapercibido. Notifica solo lo accionable:
fallos y aprobaciones pendientes. Es la misma disciplina de 05-01 con las alarmas.
Para Slack, AWS Chatbot conecta el tema de SNS con un canal y añade lo importante: botones para aprobar
desde el propio chat, lo que baja el tiempo de aprobación de Marta de horas a minutos. Y para lógica
propia, EventBridge recibe todos los eventos del pipeline con source: ["aws.codepipeline"], como vimos en
07-03.
El pipeline como código
Todo lo de esta lección se ha creado con aws codepipeline create-pipeline y un JSON en el portátil de
Luis. Es decir: hemos automatizado el despliegue de la aplicación con una herramienta que a su vez está
configurada a mano. Si alguien borra el pipeline, no hay forma fiable de recrearlo; si Marta cambia un
umbral de la puerta de calidad, no queda registro de quién ni por qué.
La salida provisional es exportar la definición y versionarla en 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, pero es un apaño: el JSON exportado trae metadatos que hay que limpiar, no parametriza por entorno y no gestiona dependencias entre recursos. La solución de verdad es definir el pipeline —y toda la infraestructura— como código, y eso es el módulo 9: CloudFormation en 09-01 y CDK en 09-02.
Coste y limpieza
| Concepto | Cálculo | Coste |
|---|---|---|
| CodePipeline V2, ~20 ejecuciones/día | ~1.700 min de acción/mes × 0,002 | 3,40 USD |
| Lambda de la puerta de calidad | 600 invocaciones de 3 s | ~0,01 USD |
| Artefactos en S3, con caducidad de 60 días | ~3 GB | 0,07 USD |
| CodePipeline | ≈ 3,50 USD/mes | |
| Módulo 8 completo (con CodeBuild y CodeDeploy) | ≈ 28 USD/mes |
Veintiocho dólares al mes por eliminar los despliegues manuales de un equipo de tres personas. La comparación honesta no es contra cero: es contra el coste de una caída de treinta minutos un viernes a las 19:00, con 900 pedidos/hora, más las horas que Luis dedicaba a desplegar a mano.
# Retener cambios sin borrar nada: la transicion deshabilitada
aws codepipeline disable-stage-transition --pipeline-name pipeline-mercadofresco-tienda \
--stage-name DesplegarProduccion --transition-type Inbound \
--reason "Pico del viernes: sin despliegues hasta el lunes" \
--profile mercadofresco-dev --region eu-west-1
# Borrar un pipeline de pruebas (no borra el bucket ni sus artefactos)
aws codepipeline delete-pipeline --name pipeline-pruebas-borrar \
--profile mercadofresco-dev --region eu-west-1Borrar el pipeline no borra los artefactos, que siguen ocupando y facturando en S3. Es el residuo más común de este módulo: revisa el bucket y su regla de ciclo de vida.
Errores Comunes y Consejos
Dejar DetectChanges: true con triggers de V2 definidos. Cada push lanza dos ejecuciones, se pisan y
nadie entiende por qué.
Bucket de artefactos sin versionado. El pipeline no funciona y el mensaje no es evidente. Es un requisito duro.
Sin regla de ciclo de vida en el bucket. Veinte ejecuciones diarias de 45 MB son 27 GB al año de ZIP que nadie va a desplegar.
Reconstruir el artefacto en cada etapa. Rompe la garantía de inmutabilidad: lo que aprueba Marta deja de ser lo que sale a producción. Construye una vez, despliega el mismo objeto.
Una aprobación manual sin CustomData útil. Se convierte en un botón que se pulsa sin mirar, y
entonces es solo retraso.
Que apruebe la misma persona que programó el cambio. La aprobación deja de filtrar nada.
Una puerta de calidad que aprueba cuando no hay datos o cuando lanza una excepción. Una puerta que se abre al romperse no es una puerta.
Olvidar put_job_success_result / put_job_failure_result en la Lambda de invocación. La acción se
queda en InProgress hasta el timeout de una hora.
Notificar los éxitos. Cuatrocientos mensajes al mes enseñan al equipo a ignorar el canal, y el aviso que importa se pierde.
Un rol de pipeline con PowerUserAccess. El pipeline solo debe lanzar acciones; el trabajo lo hacen
los roles de CodeBuild y CodeDeploy. Tres roles separados, tres radios de explosión acotados.
Olvidar kms:GenerateDataKey en el rol del pipeline, o usar la clave gestionada por AWS en un pipeline
entre cuentas: el despliegue falla al no poder descifrar el artefacto.
Consejo: usa V2 y filtra por ruta de fichero. Un cambio en el README no debe gastar minutos ni
molestar a nadie.
Consejo: conecta el pipeline a las PR, no solo a main. Es lo que cierra la protección de rama que
dejamos abierta en 08-01.
Consejo: pon condiciones de salida a la aprobación manual. Un control temporal sin criterio para retirarlo se queda para siempre.
Consejo: aprende a usar la transición deshabilitada. Es la forma limpia de decir «hoy no se despliega a producción» sin desmontar nada.
Ejercicios
Ejercicio 1: el pipeline que desplegó lo que no era
Viernes, 18:50. Marta aprueba la versión 1.7.0 tras ver que las pruebas de humo pasaron en preproducción.
El pipeline despliega en producción y a los cuatro minutos salta mercadofresco-alb-latencia-alta.
CodeDeploy revierte. Investigando, descubren que la etapa de producción reconstruía el artefacto con una
acción de CodeBuild propia en lugar de reutilizar PaqueteTienda, y que entre la construcción de
preproducción (18:20) y la de producción (18:52) alguien había fusionado otra PR en main.
Responde: (a) qué se desplegó exactamente en producción; (b) por qué las pruebas de humo de preproducción no valían nada en este diseño; (c) cómo se corrige el pipeline; (d) qué otras dos salvaguardas del módulo habrían limitado el daño; (e) qué medida de proceso propondrías para el horario.
Ejercicio 2: diseñar la puerta de calidad
MercadoFresco quiere pasar de entrega continua a despliegue continuo: quitar la aprobación de Marta y
que la puerta de calidad decida sola. Datos: en horario comercial se confirman entre 120 y 900 pedidos por
hora; de 02:00 a 07:00 hay entre 0 y 5; TiempoConfirmacionPedido p95 normal es 400 ms y el objetivo es no
superar 1.500 ms; el despliegue canario de la Lambda de cobros dura 5 minutos y el blue/green de la tienda
tarda unos 12 en completarse.
Diseña la puerta: qué métricas, qué umbrales, qué ventana de observación, qué hace ante ausencia de datos y en qué punto del pipeline se coloca. Justifica cada decisión y di qué tres condiciones deberían cumplirse antes de quitar la aprobación manual.
Ejercicio 3: el pipeline de las tres emergencias
El lunes ocurren tres cosas. (a) A las 09:15, Luis empuja cinco commits seguidos a main corrigiendo
un error de estilo; el pipeline arranca cinco veces y la cuarta se queda esperando. (b) A las 11:40, la
etapa DesplegarPreproduccion falla porque el agente de CodeDeploy de una instancia no respondió; el resto
del pipeline había ido bien y la construcción tardó 9 minutos. (c) A las 17:20 hay que desplegar una
corrección urgente de seguridad, pero Marta está en un avión y no puede aprobar hasta las 21:00.
Para cada situación indica qué ocurre con la configuración descrita en la lección, qué comando o acción concreta usarías, y qué cambiarías para que no vuelva a ser un problema.
Soluciones
Solución 1
(a) Se desplegó la 1.7.0 más el cambio de la PR fusionada a las 18:45, es decir, código que nunca pasó por preproducción ni por la aprobación. Marta aprobó una cosa y salió otra. Y lo grave es que nada en la interfaz lo indicaba: la ejecución seguía llamándose 1.7.0.
(b) Porque validaban un artefacto distinto del que se desplegó. Una prueba solo dice algo sobre el
binario concreto que probó; si reconstruyes después, has tirado esa información. Aquí la reconstrucción
tomó main en su estado del momento, no el commit de la ejecución, así que arrastró un cambio nuevo. Es la
diferencia entre «probamos esto» y «probamos algo parecido a esto».
(c) Eliminando la acción de construcción de la etapa de producción y haciendo que la acción de
despliegue consuma el PaqueteTienda producido en la etapa de construcción, que es el mismo objeto de S3
que ya se desplegó en desarrollo y preproducción:
Construir una vez, desplegar muchas es la regla, y no admite excepciones «porque en producción hace
falta otra configuración»: la configuración por entorno se resuelve en tiempo de ejecución con Parameter
Store, no reconstruyendo el paquete. Si hiciera falta comprobarlo, basta con verificar que el commit de
los metadatos del artefacto coincide con el de la ejecución del pipeline.
(d) La puerta de calidad habría detectado la degradación por métricas incluso si la reversión de CodeDeploy no hubiera saltado, deteniendo el pipeline y dejando constancia. Y la transición deshabilitada hacia producción durante el pico del viernes habría impedido el despliegue a las 18:50, que es cuando más caro sale equivocarse. Vale la pena notar que la reversión automática funcionó: cuatro minutos de degradación en lugar de treinta, y sin que nadie tuviera que hacer nada. El fallo estuvo aguas arriba, en el diseño del pipeline, no en el mecanismo de seguridad.
(e) Una ventana de despliegue explícita: nada a producción los viernes de 16:00 a 22:00, ni en festivos ni después de las 18:00 cualquier día. No es desconfianza en el pipeline, es aritmética: el coste de un incidente en el pico con 900 pedidos/hora es varias veces el de un lunes por la mañana, y a las 19:00 de un viernes hay menos gente disponible para responder. Implementarlo con la transición deshabilitada de forma programada, no confiando en que alguien se acuerde.
Solución 2
Métricas y umbrales. Tres señales complementarias, porque una sola es fácil de engañar:
| Métrica | Umbral | Por qué |
|---|---|---|
TiempoConfirmacionPedido p95 |
> 1.500 ms → fallar | Objetivo de negocio de 07-05; el normal es 400 ms |
PedidosConfirmados (Sum) |
Caída > 40 % frente a la misma franja de los 7 días previos | Detecta que nadie compra, que es el fallo silencioso |
| Errores 5xx del ALB (ratio) | > 1 % de las peticiones | Detecta el fallo evidente y rápido |
El umbral relativo de PedidosConfirmados es la decisión clave del ejercicio. Un umbral absoluto no
puede funcionar cuando el volumen normal oscila entre 0 y 900 según la hora: si pones «al menos 5 pedidos
en 10 minutos», la puerta fallará todos los despliegues nocturnos legítimos; si pones 0, no detectará nunca
nada. Comparar con la misma franja horaria de los siete días anteriores se adapta solo al patrón real,
incluido el pico del viernes.
Ventana de observación: 15 minutos, y la razón es aritmética. El blue/green tarda unos 12 minutos en completarse, así que una ventana más corta mediría en parte el entorno azul y en parte el verde, mezclando las dos versiones y diluyendo cualquier degradación. Quince minutos garantizan al menos tres de tráfico íntegramente sobre la versión nueva.
Ante ausencia de datos, depende de la hora, y esto es lo que hace la puerta utilizable de noche. En
horario comercial (07:00-02:00), sin datos de PedidosConfirmados es fallar: significa que nadie
compra. De 02:00 a 07:00 es lo normal, así que en esa franja PedidosConfirmados se ignora y la puerta se
apoya en TiempoConfirmacionPedido medido con tráfico sintético —una prueba de humo cada minuto desde
CloudWatch Synthetics—, sin el cual de madrugada no habría nada que medir. La regla general se mantiene:
sin datos no es aprobar; es no saber, y no saber solo es aceptable si has decidido de antemano que en esa
franja no hay nada que saber.
Dónde se coloca: dos puertas, no una. Una inmediatamente después del despliegue canario de la Lambda
de cobros, con ventana de 5 minutos ajustada a la duración del canario, para cortar antes de que el
canario promocione al 100 %. Y otra después del blue/green de la tienda, con la ventana de 15 minutos,
dentro del terminationWaitTimeInMinutes de 30 para que la reversión siga siendo de 90 segundos. Una
puerta que evalúa cuando ya se ha terminado el entorno azul llega tarde.
Las tres condiciones para quitar la aprobación manual, que son las que Marta ya escribió: que las pruebas de humo cubran el flujo de compra completo —incluido el cobro con tarjeta de prueba—, que la reversión automática se haya probado a propósito al menos tres veces con tiempos medidos, y que pasen dos meses sin ningún despliegue que haya habido que revertir a mano. A eso conviene añadir una cuarta que el ejercicio sugiere: que la puerta de calidad haya funcionado en modo observación durante un mes — registrando lo que habría hecho sin llegar a bloquear— y no haya producido falsos positivos. Quitar la aprobación humana y estrenar la puerta automática el mismo día es cambiar un control por una hipótesis.
Solución 3
(a) Los cinco commits. Con el modo SUPERSEDED por defecto, no se ejecutan cinco pipelines completos:
la primera ejecución sigue su curso y las siguientes se van sustituyendo entre sí, de modo que quedan
dos —la que corría y la última—. No se pierde nada, porque el quinto commit contiene a los cuatro
anteriores. Así que técnicamente el comportamiento es correcto. Lo que sí es un desperdicio es haber
arrancado siquiera: eran correcciones de estilo. El arreglo es el filtro por ruta de fichero de V2, con
excludes para **/*.md y docs/**, y si el cambio afecta a app/ pero es trivial, el flujo correcto es
agruparlo en un solo commit antes de fusionar. Nada que hacer en el momento salvo dejar que termine.
(b) El fallo del agente. Es un fallo transitorio de infraestructura, no del código, así que no hay que reconstruir: reconstruir tiraría nueve minutos y, peor, produciría un artefacto nuevo con el riesgo del ejercicio 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 solo la acción de despliegue reutilizando el mismo PaqueteTienda. Para que no
vuelva a ser un problema: el agente instalado como asociación de Systems Manager con actualización
automática, como vimos en 08-03, y una alarma sobre el estado del agente en las instancias. Y si es
recurrente, revisar si a la instancia le falta salida de red o si el ASG la creó con una AMI sin agente.
(c) La corrección urgente sin Marta. El diseño ya tiene la respuesta y lo importante es no
improvisar. La aprobación admite varios aprobadores: quien tenga codepipeline:PutApprovalResult puede
autorizar, así que debe existir un suplente designado de antemano —Luis no, porque es quien escribió el
cambio y la regla de que no apruebe el autor es justamente la que protege aquí; el suplente sería un
segundo responsable técnico, o Sara si la organización lo acepta para casos documentados—. Si de verdad no
hay nadie, la salida es AWS Chatbot en Slack, que permite a Marta aprobar desde el móvil en cuanto tenga
red, incluida la del avión.
Lo que no hay que hacer es saltarse el pipeline y desplegar a mano con create-deployment: se pierden
las pruebas de humo, la puerta de calidad y la trazabilidad, justo en un cambio de seguridad que es de los
que más conviene tener registrados. Y tampoco usar una variable tipo omitirHumo, por la razón de la
lección: una puerta que se puede saltar se saltará.
Como medida permanente, dos cosas. Una política de aprobadores escrita con titular y suplente, revisada cada trimestre. Y valorar un camino rápido documentado para correcciones de seguridad: un pipeline con las mismas puertas automáticas pero con la aprobación humana sustituida por la notificación inmediata a todo el equipo y un post-mortem obligatorio en 24 horas. Que un cambio sea urgente no justifica saltarse los controles automáticos; como mucho justifica sustituir el control humano por una rendición de cuentas posterior.
Conclusión
La secuencia que vivía en la cabeza de Luis es ahora pipeline-mercadofresco-tienda, una definición
versionada que se ejecuta igual todas las veces. Un commit en main que toque app/ arranca el flujo,
construye, prueba, comprueba la calidad, aplica la fase de expansión del esquema, despliega en desarrollo,
pasa las pruebas de humo, despliega en preproducción, vuelve a pasarlas, avisa a Marta con la información
para decidir en treinta segundos, despliega en producción con blue/green y canario, y comprueba con las
métricas reales de MercadoFresco/Tienda que la tienda sigue sana. Nadie escribe un comando.
Sabes distinguir entrega continua de despliegue continuo —la diferencia es una persona entre la
validación y producción— y por qué MercadoFresco elige la primera con condiciones de salida escritas,
porque un control temporal sin criterio para retirarlo se queda para siempre. Conoces la estructura del
pipeline y los dos matices que más juego dan: que las acciones con el mismo runOrder corren en
paralelo y que una transición deshabilitada es la forma limpia de decir «hoy no se despliega». Y sobre
todo tienes la garantía que da sentido a todo lo demás: el artefacto se construye una vez y el mismo
objeto de S3 recorre los tres entornos, de modo que lo que Marta aprueba y lo que sale a producción son el
mismo fichero byte a byte —romper eso, como en el ejercicio 1, invalida todas las pruebas anteriores—.
Sabes por qué V2 es la elección por defecto: los disparadores con filtro por rama, etiqueta y ruta de
fichero evitan que un cambio en el README gaste minutos, y las ejecuciones sobre pull request son lo que
cierra la protección de main que quedó abierta en 08-01. Conoces la acción de origen con
CodeConnections y sus dos trampas —DetectChanges: true junto a triggers produce ejecuciones duplicadas,
y una conexión en PENDING falla con un error que no la menciona—, el namespace que expone las variables
exportadas por el buildspec de 08-02, y la aprobación manual que sirve de verdad: CustomData con
versión, commit, autor, estado del humo y migración pendiente, más un ExternalEntityLink al diff.
Tienes las puertas de calidad con sus tres decisiones deliberadas —sin datos se falla, ante excepción se
falla, y hay que llamar siempre a put_job_success_result o put_job_failure_result—, los tres modos de
ejecución con SUPERSEDED como el correcto porque el commit nuevo contiene a los anteriores, y el
reintento con FAILED_ACTIONS que no reconstruye. Sabes separar los permisos en tres roles —el del
pipeline, que solo lanza; el de la construcción; y el de la instancia— y que kms:GenerateDataKey es lo que
falta cuando un pipeline no puede escribir en un bucket cifrado. Y sabes notificar solo lo accionable:
fallos y aprobaciones, nunca los éxitos, o el canal se vuelve ruido.
Todo por unos 28 dólares al mes para el módulo completo, con dos residuos que vigilar: los artefactos que sobreviven al pipeline borrado y el bucket sin ciclo de vida.
Quedan dos cabos sueltos que la lección ha dejado a la vista a propósito. El primero es que los tres
entornos comparten la cuenta 111122223333: el radio de explosión no está acotado, los límites de
servicio se comparten y la factura no se separa de verdad. El segundo es más incómodo: este pipeline, que
existe para que nadie despliegue a mano, se ha creado a mano, con un JSON en el portátil de Luis y un
create-pipeline.
En 08-05, «Un pipeline de extremo a extremo», cerramos el módulo siguiendo un cambio real desde el
portátil de Luis hasta producción: el campo «franja horaria de entrega», que toca la tienda, un consumidor
de cola-mercadofresco-pedidos, el esquema de Aurora y el contrato del evento PedidoConfirmado. Veremos
paso a paso qué se comprueba y qué pasa si falla; cómo se aplica expansión-contracción en Aurora y se
versiona un evento sin romper a los consumidores existentes, con el orden correcto entre productor y
consumidor que cierra el incidente del módulo 7; la pirámide de pruebas y cuáles bloquean; las métricas
DORA de MercadoFresco antes y después; el runbook de reversión cuando el fallo se detecta media hora
tarde; y las banderas de funcionalidad con AppConfig que convierten «desplegar un viernes» en una decisión
de negocio y no de fe.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
