CI/CD y DevOps
"Si duele hacerlo frecuentemente, hacelo más frecuentemente — para que el dolor te fuerce a arreglarlo." — Jez Humble (sobre deploys frecuentes).
Qué vas a aprender en este capítulo
CI/CD (Continuous Integration / Continuous Delivery) y DevOps son las prácticas que permiten a los equipos modernos desplegar código decenas de veces al día con alta confianza. Este capítulo cubre el flujo de trabajo profesional con Git, la configuración de pipelines automáticos, y los fundamentos de Docker para entornos reproducibles.
5.1 Flujo de trabajo con Git
💡 Intuición
Git es la herramienta de control de versiones más usada en el mundo. Pero usarlo bien en equipo requiere un flujo de trabajo (workflow) acordado. Sin un flujo definido, el repositorio se convierte en caos.
Git Flow (el más documentado) y Trunk-Based Development (el que usan Google y Netflix) son los dos enfoques principales.
En equipos pequeños como el de La Esquina (2-4 desarrolladores), un flujo simplificado funciona perfecto:
main: siempre deployable.feature/xxx: ramas de trabajo.- Pull Requests para mergear con revisión.
📐 Fundamento
Ramas (branches):
main ─────────────────────────────────────────── ▶ producción
│ │
└── feature/login ─ PR ──────── merge
│
└── commit1 ─ commit2 ─ commit3
Comandos de flujo diario:
# 1. Actualizar main antes de empezar
git checkout main
git pull origin main
# 2. Crear rama de feature
git checkout -b feature/agregar-pago-tarjeta
# 3. Trabajar (commits frecuentes, mensajes claros)
git add src/pagos.py tests/test_pagos.py
git commit -m "feat: agregar soporte para pago con tarjeta de crédito"
# 4. Subir la rama
git push origin feature/agregar-pago-tarjeta
# 5. Abrir Pull Request en GitHub
# 6. Peer review + CI automático
# 7. Merge a main
# 8. Borrar la rama
git branch -d feature/agregar-pago-tarjeta
Convención de mensajes de commit (Conventional Commits):
tipo(scope): descripción breve
| Tipo | Cuándo usarlo |
|---|---|
feat |
Nueva funcionalidad |
fix |
Corrección de bug |
refactor |
Cambio de código sin cambiar comportamiento |
test |
Agregar o mejorar pruebas |
docs |
Documentación |
chore |
Mantenimiento (deps, config) |
Ejemplos:
feat(pedidos): implementar envío en tiempo real a cocinafix(auth): corregir sesión que no expiraba en móvilestest(fiscal): agregar casos límite para cálculo de IVA
Pull Requests — buenas prácticas:
- Una PR = una funcionalidad o fix. No mezclar.
- PRs pequeñas (< 400 líneas de cambios). Fáciles de revisar.
- Descripción clara: qué cambiaste y por qué.
- Ligar a la historia de usuario correspondiente.
- Al menos un revisor aprueba antes de mergear.
🛠️ En la práctica
Protección de ramas en GitHub:
En Settings → Branches → Add rule:
- Branch name pattern:
main - Require pull request reviews before merging: ✓ (1 review)
- Require status checks to pass before merging: ✓ (CI debe pasar)
- Restrict who can push to matching branches: ✓ (solo admins)
Esto garantiza que nadie puede romper main por accidente.
5.2 Continuous Integration (CI)
💡 Intuición
Integración continua significa que cada vez que un desarrollador hace push, el sistema automáticamente:
- Descarga el código.
- Instala dependencias.
- Corre todas las pruebas.
- Si algo falla, notifica al equipo.
- Si todo pasa, el código está "integrado" con éxito.
¿Por qué "continua"? Porque en el pasado se integraba todo al final del proyecto. Para entonces, los conflictos eran enormes. Con CI, cada push es una integración pequeña y los problemas se detectan en minutos, no meses.
El contrato del equipo con CI: Si el CI falla, es la prioridad #1 arreglarlo. El CI roto bloquea a todo el equipo.
📐 Fundamento
GitHub Actions — CI integrado en GitHub:
Se configura en archivos YAML en .github/workflows/.
Pipeline CI básico para La Esquina:
# .github/workflows/ci.yml
name: CI — Tests y Linting
on:
push:
branches: [ main, "feature/*" ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Configurar Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Instalar dependencias
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Linting con flake8
run: flake8 src/ tests/
- name: Ejecutar pruebas con cobertura
run: |
pytest --cov=src --cov-fail-under=80
- name: Publicar reporte de cobertura
uses: codecov/codecov-action@v3
--cov-fail-under=80: Si la cobertura baja del 80%, el CI falla. Esto previene que se agregue código sin pruebas.
Pasos típicos de un pipeline CI:
- Checkout del código
- Setup del entorno (lenguaje, versión)
- Instalar dependencias (
pip install,npm install) - Lint (verificar estilo de código)
- Pruebas unitarias
- Pruebas de integración (si aplica)
- Análisis de cobertura
- (Opcional) Análisis de seguridad (bandit, snyk)
- (Opcional) Análisis de deuda técnica (SonarQube)
5.3 Continuous Delivery / Deployment (CD)
💡 Intuición
Continuous Delivery: El software siempre está en un estado listo para ser desplegado a producción. El deployment es una decisión humana.
Continuous Deployment: El deployment es automático después de que el CI pasa. Cada push a main va directamente a producción (con ciertas salvaguardas).
La diferencia:
- Delivery: CI pasa → artefacto listo → humano decide cuándo deployar.
- Deployment: CI pasa → deploy automático.
Para La Esquina, empezamos con Delivery (deploy manual después del CI) y podemos evolucionar a Deployment cuando haya más confianza en las pruebas.
📐 Fundamento
Pipeline CD básico:
# .github/workflows/deploy.yml (se ejecuta solo en main)
name: CD — Deploy a Producción
on:
push:
branches: [ main ]
jobs:
deploy:
needs: test # solo si el job 'test' del CI pasó
runs-on: ubuntu-latest
environment: production # requiere aprobación manual en GitHub
steps:
- uses: actions/checkout@v4
- name: Build imagen Docker
run: docker build -t laesquina-backend:${{ github.sha }} .
- name: Push a Registry
run: |
echo <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>s</mi><mi>e</mi><mi>c</mi><mi>r</mi><mi>e</mi><mi>t</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>R</mi><mi>E</mi><mi>G</mi><mi>I</mi><mi>S</mi><mi>T</mi><mi>R</mi><msub><mi>Y</mi><mi>P</mi></msub><mi>A</mi><mi>S</mi><mi>S</mi><mi>W</mi><mi>O</mi><mi>R</mi><mi>D</mi></mrow><mi mathvariant="normal">∣</mi><mi>d</mi><mi>o</mi><mi>c</mi><mi>k</mi><mi>e</mi><mi>r</mi><mi>l</mi><mi>o</mi><mi>g</mi><mi>i</mi><mi>n</mi><mo>−</mo><mi>u</mi></mrow><annotation encoding="application/x-tex">{{ secrets.REGISTRY_PASSWORD }} | docker login -u </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">secr</span><span class="mord mathnormal">e</span><span class="mord mathnormal">t</span><span class="mord mathnormal">s</span><span class="mord">.</span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord mathnormal" style="margin-right:0.0576em;">E</span><span class="mord mathnormal">G</span><span class="mord mathnormal" style="margin-right:0.0785em;">I</span><span class="mord mathnormal" style="margin-right:0.0576em;">S</span><span class="mord mathnormal" style="margin-right:0.1389em;">T</span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.2222em;">Y</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.2222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.1389em;">P</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.0576em;">S</span><span class="mord mathnormal" style="margin-right:0.0576em;">S</span><span class="mord mathnormal" style="margin-right:0.1389em;">W</span><span class="mord mathnormal" style="margin-right:0.0278em;">O</span><span class="mord mathnormal" style="margin-right:0.0077em;">R</span><span class="mord mathnormal" style="margin-right:0.0278em;">D</span></span></span><span class="mord">∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">oc</span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mord mathnormal" style="margin-right:0.0278em;">er</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">in</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span></span></span></span>{{ secrets.REGISTRY_USER }} --password-stdin
docker push laesquina-backend:${{ github.sha }}
- name: Deploy al servidor
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
script: |
docker pull laesquina-backend:${{ github.sha }}
docker stop laesquina-backend || true
docker run -d --name laesquina-backend \
-p 8000:8000 \
--env-file /etc/laesquina/.env \
laesquina-backend:${{ github.sha }}
Secrets en GitHub: Las credenciales (contraseñas, SSH keys) se guardan en Settings → Secrets, nunca en el código.
Estrategias de deploy:
| Estrategia | Descripción | Downtime |
|---|---|---|
| Recreate | Detener v1, iniciar v2 | Sí |
| Rolling | Reemplazar instancias gradualmente | No |
| Blue-Green | Dos entornos, swapping de tráfico | No |
| Canary | % del tráfico va a la nueva versión primero | No |
5.4 Docker para entornos reproducibles
💡 Intuición
"En mi máquina funciona" es la frase más famosa del desarrollo de software. El código funciona en el laptop del desarrollador, pero no en el servidor. O en el laptop de otro desarrollador. O en el entorno de pruebas.
Docker resuelve esto empaquetando el código junto con su entorno completo (sistema operativo base, dependencias, configuración) en una imagen. Esa imagen corre idéntica en cualquier máquina que tenga Docker instalado.
Una imagen Docker es como un USB con un sistema operativo portable.
📐 Fundamento
Conceptos clave:
- Imagen: Plantilla inmutable que define el contenido del contenedor. Se construye desde un
Dockerfile. - Contenedor: Instancia en ejecución de una imagen. Es efímero (se puede crear y destruir).
- Registry: Repositorio de imágenes (Docker Hub, GitHub Container Registry).
- Docker Compose: Herramienta para definir y correr múltiples contenedores juntos (ej: backend + BD + Redis).
Dockerfile para La Esquina (Python/FastAPI):
# Dockerfile
FROM python:3.11-slim
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Instalar dependencias primero (aprovecha el caché de Docker)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copiar el código
COPY src/ ./src/
# Variable de entorno para el modo de producción
ENV ENVIRONMENT=production
# Puerto que expone el contenedor
EXPOSE 8000
# Comando para iniciar la aplicación
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
docker-compose.yml para desarrollo:
# docker-compose.yml
version: "3.8"
services:
backend:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=mysql://laesquina:password@db:3306/laesquina
depends_on:
- db
volumes:
- ./src:/app/src # hot reload en desarrollo
db:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=laesquina
- MYSQL_USER=laesquina
- MYSQL_PASSWORD=password
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
Comandos básicos:
# Construir imagen
docker build -t laesquina .
# Correr contenedor
docker run -p 8000:8000 laesquina
# Con Docker Compose (levanta backend + BD juntos)
docker compose up # foreground
docker compose up -d # background (daemon)
docker compose down # parar y eliminar contenedores
docker compose logs -f # ver logs en tiempo real
5.5 Cultura DevOps
💡 Intuición
DevOps es la fusión de Development y Operations. Históricamente, el equipo de desarrollo y el de operaciones (los que mantienen los servidores) estaban separados y a menudo en conflicto:
- Dev quería desplegar cambios rápido.
- Ops quería estabilidad — cada cambio era un riesgo.
DevOps rompe esa separación: el mismo equipo que desarrolla es responsable de operar. "You build it, you run it" (Werner Vogels, CTO de Amazon).
📐 Fundamento
Los tres caminos de DevOps (Gene Kim):
- Flow: Maximizar el flujo de trabajo de izquierda a derecha (Dev → Ops → Cliente). Eliminar cuellos de botella.
- Feedback: Crear loops de retroalimentación rápidos de derecha a izquierda (monitoreo → alerta → corrección).
- Continual Learning: Experimentación, aprendizaje de fallos, mejora continua.
Prácticas DevOps:
| Práctica | Herramientas comunes |
|---|---|
| Control de versiones | Git, GitHub, GitLab |
| CI/CD | GitHub Actions, Jenkins, GitLab CI |
| Containerización | Docker, Kubernetes |
| Infrastructure as Code | Terraform, Ansible, AWS CDK |
| Monitoreo | Prometheus, Grafana, Datadog |
| Log management | ELK Stack, Loki, Splunk |
| Alerting | PagerDuty, OpsGenie, Slack |
Métricas DORA (DevOps Research and Assessment):
Las cuatro métricas que predicen la performance de equipos de alto rendimiento:
| Métrica | Elite performers | Low performers |
|---|---|---|
| Deployment frequency | Múltiples deploys/día | Mensual o menos |
| Lead time for changes | < 1 hora | 1-6 meses |
| Change failure rate | 0–15% | 46–60% |
| Time to restore service | < 1 hora | 1–6 meses |
(Informe State of DevOps 2023 — Google Cloud / DORA)
🛠️ En la práctica
Pipeline completo de La Esquina:
Developer push → GitHub
│
▼
GitHub Actions CI:
1. Lint + type check
2. Pruebas unitarias (100 tests, < 30s)
3. Pruebas de integración (BD de prueba)
4. Cobertura ≥ 80%
│
[¿Pasó todo?]
│ Sí │ No
▼ ▼
PR abierta Notificación al dev
→ Code Review └── Fix y push nuevamente
→ Aprobación
│
▼
Merge a main
│
▼
GitHub Actions CD:
1. Build imagen Docker
2. Push a registry
3. Deploy a staging (automático)
4. Smoke tests en staging
5. [Aprobación manual del PO]
6. Deploy a producción
│
▼
Monitoreo:
- Health checks cada 30s
- Alertas en Slack si error rate > 1%
- Logs accesibles para debugging
¿Cuánto tiempo tarda? En un equipo bien configurado:
- CI: 3-5 minutos
- Deploy a staging: 2 minutos
- Deploy a producción: 2 minutos
Total: un cambio puede estar en producción en < 15 minutos de haber sido mergeado.
5.6 Cierre de Ingeniería de Software
El sistema de La Esquina ahora tiene:
- Cultura y proceso: Scrum con sprints de 2 semanas, daily standups, retrospectivas.
- Calidad medible: Code coverage ≥80%, defect density monitoreada.
- Pruebas automáticas: Unitarias (TDD), integración, y smoke tests.
- Pipeline CI/CD: Cada push dispara pruebas; cada merge a main puede ir a producción.
- Entorno reproducible: Docker para desarrollo, staging y producción.
De "código que funciona en mi laptop" a "software profesional que se entrega continuamente con confianza". Eso es ingeniería de software.
5.7 Ejercicios
✏️ Ejercicio 5.1 — Git workflow
Describí paso a paso (comandos Git incluidos) el proceso completo para:
"El desarrollador Ernesto va a implementar la historia HU-014: notificaciones push cuando el pedido esté listo."
Solución
# 1. Actualizar main
git checkout main
git pull origin main
# 2. Crear rama
git checkout -b feature/HU-014-notificaciones-push
# 3. Implementar + pruebas (ciclo TDD)
# ... (escribe código y pruebas) ...
git add src/notificaciones.py tests/test_notificaciones.py
git commit -m "feat(notificaciones): implementar push cuando pedido esté listo"
# 4. Mientras trabaja, mantener actualizado con main
git fetch origin main
git rebase origin/main # o merge, según política del equipo
# 5. Push de la rama
git push origin feature/HU-014-notificaciones-push
# 6. Abrir Pull Request en GitHub
# Título: "feat: notificaciones push cuando pedido listo (HU-014)"
# Descripción: qué cambió, cómo probar, enlace a HU-014
# 7. CI corre automáticamente. Corregir si falla.
# 8. Peer review: otro dev revisa y aprueba.
# 9. Merge a main (squash merge o merge commit según política).
# 10. Borrar la rama
git branch -d feature/HU-014-notificaciones-push
✏️ Ejercicio 5.2 — GitHub Actions
Escribí un workflow de GitHub Actions que:
- Se active en push a cualquier rama y en PRs a main.
- Configure Node.js 18.
- Corra
npm install. - Corra
npm test. - Solo haga el deploy (a un servidor ficticio) cuando el push sea a main.
Solución
name: CI/CD Node.js
on:
push:
branches: ["**"]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js 18
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"
- name: Instalar dependencias
run: npm install
- name: Ejecutar pruebas
run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Deploy al servidor
run: |
echo "Deploying to production..."
# En la práctica: SSH al servidor, rsync, docker deploy, etc.
echo "Deploy completado."
5.8 Para profundizar
- Kim, Humble, Debois & Willis, The DevOps Handbook — el libro fundacional de DevOps.
- Humble & Farley, Continuous Delivery — el libro de CD.
- Documentación de GitHub Actions: docs.github.com/actions
- Docker Getting Started: docs.docker.com/get-started
- Sistemas Operativos — para entender cómo los contenedores usan el kernel Linux.
- Redes I — para entender la comunicación entre servicios.
5.X Mini-proyecto integrador
🏗️ Proyecto final — Pipeline CI/CD completo
Alcance: automatizar de cero el deploy de una app web a producción usando todo el libro: Scrum + tests + CI + CD + observabilidad.
Entregables (un repo público en GitHub):
- Backlog (cap. 2) — 5 historias en GitHub Projects con criterios INVEST.
- Tests (cap. 3-4) — pirámide: unit tests (>80 % coverage), 2-3 integration tests, 1 end-to-end con Playwright.
- GitHub Actions (cap. 5) — pipeline que en cada PR: lint → test → build → docker push → deploy a staging.
- Docker (cap. 5) — Dockerfile multi-stage, imagen final < 100 MB.
- Deploy (cap. 5) — staging en Fly.io / Railway / Render free tier. Producción solo se actualiza al mergear a
main. - DORA básico — instrumentar deployment frequency y lead time con un script simple sobre los logs de Actions. Mostrá los 4 KPIs en el README.
- Postmortem ficticio — simulá un incidente (
/healthfalla 5xx) y escribí un postmortem blameless de 1 página.
Criterio de éxito: alguien forkea el repo, corre make setup, hace un PR trivial, y ve el pipeline correr verde en menos de 10 minutos. Si rompe un test, el deploy se bloquea.
Tiempo estimado: 2-3 semanas. Es proyecto portafolio: lo enseñás en entrevistas.
Definiciones nuevas: CI (Continuous Integration), CD (Continuous Delivery/Deployment), GitHub Actions, pipeline, Docker, imagen, contenedor, Docker Compose, Docker Registry, DevOps, Infrastructure as Code, métricas DORA, deployment frequency, lead time, change failure rate, time to restore.