Módulo 8: Capstone The Andes Cargo Pipeline
2. Repaso de arquitectura: el pipeline completo
Descripción
Antes de correr nada en este módulo, vale la pena tener el mapa completo del pipeline en un solo lugar — no disperso entre siete módulos, sino como un único diagrama que puedas señalar, pieza por pieza, y explicar qué hace y por qué está ahí. Esta lección no ejecuta ningún comando nuevo: arma, con precisión, el diagrama completo de andes-cargo-infra/ tal como queda al cerrar el Módulo 7 —los tres workflows, el guardrail, y la pieza de red que hace posible que cualquiera de los tres hable con LocalStack— para que las lecciones 3 y 4 corran sobre un mapa ya entendido, no sobre piezas sueltas que hay que recordar sobre la marcha.
Conexión con el módulo
Esta lección es puramente de repaso — cada pieza del diagrama ya la construiste y la corriste en un módulo anterior; aquí solo se ensamblan en una sola imagen. La lección 3 recorre exactamente este diagrama, de izquierda a derecha, con un cambio real cruzándolo. La lección 4 recorre la misma imagen, pero deteniéndose exactamente donde el guardrail actúa.
Analogía: los planos completos de una fábrica, antes del recorrido guiado
Imagina que vas a dar un recorrido guiado por una fábrica que ayudaste a construir, pieza por pieza, a lo largo de varios meses. Antes de abrir las puertas a los visitantes, tiene sentido pararte frente a los planos completos del edificio —no la memoria de cada semana de construcción por separado, sino el plano final, con cada estación de la línea de montaje marcada, cada tubería que conecta un área con otra— y confirmar que puedes señalar cualquier punto y explicar, sin dudar, qué pasa ahí y por qué. Esta lección es ese momento frente a los planos: antes de las lecciones 3 y 4, que son el recorrido guiado real, con material pasando de verdad por cada estación.
El diagrama completo: los tres workflows y el guardrail
┌─────────────────────────────────────────────────────┐
│ andes-cargo-infra/ (repositorio Git) │
│ │
│ .github/workflows/ │
│ ├── ci.yml (Módulo 3, guardrail M6) │
│ ├── apply.yml (Módulo 5) │
│ ├── drift.yml (Módulo 5) │
│ └── guardrail-demo.yml (Módulo 6, solo para probar) │
└─────────────────────────────────────────────────────┘
① pull_request (PR contra main)
│
▼
┌─────────────────────── ci.yml — job: terraform-checks ───────────────────────┐
│ checkout → setup-terraform → fmt -check → init → validate → install │
│ awslocal → [confirm LocalStack, continue-on-error] → install tflocal → │
│ plan -out=tfplan → ┌─────────────────────────────────┐ → publish │
│ │ GUARDRAIL (Módulo 6, lección 7) │ summary → │
│ │ grep sobre terraform show -json │ upload-artifact │
│ │ "actions":["delete"] en │ (terraform-plan)│
│ │ aws_dynamodb_table.shipments? │ │
│ │ SÍ → exit 1, job falla aquí │ │
│ │ NO → sigue al step siguiente │ │
│ └─────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
│ (solo si el job completo tuvo éxito, incluido el guardrail)
▼
② (una persona revisa el resumen del plan, aprueba, fusiona el PR)
│
▼
③ push a main (la fusión, simulada con `act push`)
│
▼
┌────────────────────── apply.yml — dos jobs, en Stages ───────────────────────┐
│ Stage 0 — fetch-reviewed-plan: │
│ download-artifact (terraform-plan) → confirmar que tfplan llegó intacto │
│ │
│ Stage 1 — terraform-apply (needs: fetch-reviewed-plan): │
│ checkout → setup-terraform → download-artifact (de nuevo, otro contenedor)│
│ → init → install tflocal → apply -auto-approve tfplan │
│ │
│ concurrency: { group: apply-andes-cargo-infra, cancel-in-progress: false } │
└────────────────────────────────────────────────────────────────────────────┘
│
▼
④ drift.yml — schedule (06:00 UTC) o workflow_dispatch, en cualquier momento
checkout → setup-terraform (terraform_wrapper: false) → init → install
tflocal → plan -detailed-exitcode → reporta 0 (sin drift) / 2 (drift) /
otro (falló) en $GITHUB_STEP_SUMMARY
Cuatro piezas, un solo repositorio: ci.yml es la puerta de entrada —nada llega a main sin pasar por ahí, incluido el guardrail—; apply.yml es la única puerta de salida hacia infraestructura real, y solo se abre con un plan que ya pasó por la primera puerta; drift.yml no reacciona a ningún evento del repositorio, vigila por su cuenta, con su propio horario; guardrail-demo.yml no es parte del flujo normal — es el banco de pruebas que usaste en el Módulo 6, y que vuelves a usar en la lección 4 de este módulo, para demostrar que el guardrail funciona sin necesitar un Pull Request real que lo dispare.
La red: el contenedor de act hablando con LocalStack en el host
Esta es la pieza que hace posible que cualquiera de los pasos de arriba llegue a AWS —o, en este laboratorio, a su simulación—:
TU MÁQUINA (host) CONTENEDOR DE act (efímero, por job)
┌─────────────────────┐ ┌──────────────────────────────┐
│ localstack_main │ │ catthehacker/ubuntu:act- │
│ puerto 4566 │ │ latest │
│ (docker run, │◄───────────────────── │ │
│ Módulo 1, lección 8)│ host.docker.internal │ AWS_ENDPOINT_URL= │
│ │ :4566 │ http://host.docker.internal │
│ cuenta 000000000000 │ │ :4566 │
└─────────────────────┘ │ │
│ terraform / tflocal / awslocal│
.actrc: │ corren AQUÍ, no en el host │
-P ubuntu-latest=catthehacker/ubuntu:act-latest └──────────────────────────────┘
--container-options
"--add-host=host.docker.internal:host-gateway"
host.docker.internal es el nombre especial que Docker resuelve, desde dentro de un contenedor, hacia el host que lo ejecuta — sin el flag --add-host=host.docker.internal:host-gateway de .actrc (Módulo 3, lección 5), ese nombre no resuelve en Linux, aunque sí lo haga por defecto en Docker Desktop de macOS/Windows. Es, literalmente, el único puente entre el mundo efímero y aislado del contenedor de act y el LocalStack que persiste, arrancado una sola vez, en tu máquina.
El flujo secuencial completo, de PR a apply
sequenceDiagram
participant Dev as Developer
participant CI as ci.yml (PR)
participant Rev as Revisor humano
participant Main as main
participant Apply as apply.yml (push)
participant LS as LocalStack
Dev->>CI: abre PR (rama feature/*)
CI->>CI: fmt, init, validate
CI->>LS: awslocal s3 ls (verificación, continue-on-error)
CI->>CI: terraform plan -out=tfplan
CI->>CI: guardrail: grep sobre plan JSON
alt guardrail detecta destroy en Shipments
CI-->>Dev: job falla, NUNCA sube el artefacto
else guardrail pasa
CI->>CI: publica resumen, upload-artifact
CI-->>Rev: plan visible para revisión
Rev->>Main: aprueba y fusiona el PR
Main->>Apply: push dispara apply.yml
Apply->>Apply: download-artifact (mismo tfplan, SHA256 verificado)
Apply->>LS: terraform apply -auto-approve tfplan
LS-->>Apply: recursos creados (o fallo honesto sin token)
end
Este diagrama es la versión ejecutable de la analogía de la caja fuerte de dos llaves que ya conoces del Módulo 4: ci.yml es la primera llave (calcula qué pasaría), una persona real es la segunda (decide si eso debería pasar), y apply.yml nunca se activa sin que las dos llaves hayan girado en el orden correcto.
Confirmando el inventario con act -l
Antes de correr cualquier escenario en las lecciones 3 y 4, confirma que los cuatro workflows siguen exactamente donde deberían estar:
cd andes-cargo-infra
act -l
Qué esperar (literal, ejecutado para escribir esta lección):
Stage Job ID Job name Workflow name Workflow file Events
0 fetch-reviewed-plan fetch-reviewed-plan apply apply.yml push
0 terraform-checks terraform-checks ci ci.yml pull_request
0 check-drift check-drift drift-detection drift.yml schedule,workflow_dispatch
0 destroy-shipments-check destroy-shipments-check guardrail-demo guardrail-demo.yml workflow_dispatch
1 terraform-apply terraform-apply apply apply.yml push
Cinco filas, cuatro workflows: apply.yml aparece dos veces porque tiene dos jobs en dos Stage distintos —fetch-reviewed-plan en el Stage 0, terraform-apply en el Stage 1, encadenados por needs: (Módulo 5, lección 3)—. drift.yml es el único que escucha dos eventos a la vez (schedule y workflow_dispatch), y guardrail-demo.yml es el único que nunca se disparó por ningún evento del repositorio real —solo workflow_dispatch, a mano, exactamente como corresponde a un banco de pruebas—. Si tu salida coincide, fila por fila, con esta tabla, tu proyecto está en el estado exacto que las lecciones 3 y 4 asumen.
Errores comunes
Buscar un quinto workflow o un job nuevo en este repaso (de expectativa). Qué pasa: alguien, al ver el título "repaso de arquitectura", espera que esta lección revele una pieza que los módulos anteriores no mostraron. Cómo corregirlo: esta lección, a propósito, no tiene ninguna pieza nueva — su único valor es ensamblar, en un solo diagrama, exactamente lo que ya construiste, para que las lecciones 3 y 4 no tengan que reconstruir el mapa sobre la marcha.
Confundir guardrail-demo.yml con un workflow que corre en producción (conceptual, revisita el Módulo 6). Qué pasa: alguien, viendo las cinco filas de act -l, asume que guardrail-demo.yml forma parte del flujo normal de Pull Requests, igual que ci.yml. Cómo detectarlo: revisa la columna Events — guardrail-demo.yml solo escucha workflow_dispatch, nunca pull_request ni push. Cómo corregirlo: el guardrail real, el que protege cada Pull Request de verdad, vive dentro de ci.yml (el step agregado en el Módulo 6, lección 7) — guardrail-demo.yml es, exclusivamente, el banco de pruebas que siembra un state falso para poder probar ese mismo guardrail sin necesitar infraestructura real aplicada.
Pensar que host.docker.internal es una configuración de Terraform (de configuración, revisita el Módulo 3). Qué pasa: alguien busca ese nombre dentro de providers.tf, sin encontrarlo. Cómo corregirlo: host.docker.internal:4566 vive en el env: AWS_ENDPOINT_URL de cada workflow (Módulo 3, lección 5) y en el flag --container-options de .actrc — es una pieza de red de Docker/act, no de configuración de Terraform. providers.tf se mantiene deliberadamente mínimo, listo para que el endpoint llegue desde afuera.
Ejercicios
Ejercicio 1 — Traza, de memoria, el camino completo de un plan bloqueado. Sin mirar el diagrama de esta lección, dibuja (en papel o en un editor de texto) qué steps de ci.yml SÍ corren y cuáles NUNCA corren cuando el guardrail detecta una destrucción de Shipments.
Ver solución
Corren, en orden: checkout, setup-terraform, fmt -check, init, validate, install awslocal, confirm LocalStack (con o sin éxito, por el continue-on-error), install tflocal, terraform plan, y el step del guardrail, que es donde el job se detiene con exit 1. Nunca corren: publish the plan to the job summary ni upload the plan for apply.yml to use later — ambos steps viven después del guardrail en el archivo, y GitHub Actions (igual que act) no ejecuta ningún step posterior a uno que falló, salvo que ese step tenga su propio if: always() o similar, que este ci.yml no usa.
Ejercicio 2 — Explica por qué apply.yml tiene dos Stage y no uno solo. Un colega pregunta por qué no simplificar apply.yml a un único job con todos los steps juntos, en vez de dos jobs separados por needs:. Respóndele con la razón exacta del Módulo 5.
Ver solución
Separar en dos jobs (fetch-reviewed-plan y terraform-apply) hace que la verificación de que el artefacto existe y llegó intacto sea una condición explícita, con su propio resultado visible, antes de que empiece cualquier intento de apply — si fetch-reviewed-plan falla, terraform-apply (que depende de él con needs:) nunca corre en absoluto. Con un único job, un fallo temprano en la descarga del artefacto interrumpiría el job igual, pero sin la claridad de "esta etapa específica es la que falló" que da un Stage separado, y sin la garantía estructural de que ningún step de apply real pueda ejecutarse antes de confirmar que el plan llegó bien.
Ejercicio 3 — Ubica dónde vive cada pieza de seguridad de esta guía en el diagrama. Señala, sobre el diagrama del pipeline completo de esta lección, en qué punto exacto actúan cada una de estas tres piezas: secrets.AWS_ACCESS_KEY_ID (Módulo 4), el guardrail (Módulo 6), y concurrency: (Módulo 5).
Ver solución
secrets.AWS_ACCESS_KEY_ID actúa en el bloque env: de ci.yml, antes de que corra cualquier step — no es un punto del flujo, es una configuración que todos los steps de ese job heredan. El guardrail actúa dentro de ci.yml, específicamente entre el step Terraform plan y el step Publish the plan to the job summary — es el único punto de todo el diagrama que puede detener un cambio antes de que se publique o se suba como artefacto. concurrency: actúa a nivel del workflow apply.yml completo, no de ningún step específico — es la regla que decide si una corrida nueva de apply.yml puede empezar de inmediato o tiene que esperar a que la corrida actual termine, sin importar en qué step exacto esté esa corrida en curso.
Resumen y siguiente paso
En esta lección armaste el diagrama completo del pipeline de Andes Cargo: los tres workflows de producción (ci.yml, apply.yml, drift.yml) más el banco de pruebas del guardrail (guardrail-demo.yml), la red que conecta el contenedor de act con el LocalStack del host, y el flujo secuencial completo de un cambio desde que se abre un Pull Request hasta que se aplica. Confirmaste, con act -l, que las cinco filas de jobs coinciden exactamente con lo que este repaso predijo.
Antes de avanzar deberías poder: dibujar el diagrama completo de memoria, señalando dónde actúa cada pieza de seguridad; explicar por qué guardrail-demo.yml no forma parte del flujo normal de Pull Requests; y trazar el camino exacto que sigue —o no sigue— un plan bloqueado por el guardrail.
Con el mapa completo ya en la cabeza, la lección 3 recorre este mismo diagrama de punta a punta, con un cambio real de HCL cruzándolo — el primero de los dos recorridos que cierran la tesis técnica de esta guía.
Recursos
- nektosact.com — User Guide — referencia de
act -l, usada en esta lección para confirmar el inventario completo. - Docker Docs — Networking: use cases and network drivers — documentación oficial de la resolución de nombres entre contenedores y el host, base de
host.docker.internal. - Módulo 3 de esta guía (
05-connecting-the-runner-to-localstack.md) — el origen completo de la pieza de red que este repaso diagrama. - Módulo 6 de esta guía (
07-hands-on-a-failing-guardrail-example.md) — el origen del guardrail, diagramado aquí dentro deci.yml.