CI/CD
Integración y Entrega Continua
1. Concepto Base
¿Qué es CI?
Continuous Integration (Integración Continua)
La CI es la práctica de integrar cambios de código en un repositorio compartido múltiples veces al día, con validación automática mediante builds y tests.
Objetivos de CI
- Integrar código frecuentemente para detectar conflictos temprano
- Ejecutar pruebas automáticas en cada commit
- Compilar y validar el código de forma automática
- Generar feedback rápido a los desarrolladores (< 10 minutos)
Ejemplos de uso de CI
- Ejecutar linters para validar estilo de código
- Correr tests unitarios y de integración
- Compilar la aplicación para verificar que no hay errores
- Análisis de código estático (SonarQube, ESLint)
- Escaneo de dependencias vulnerables
¿Qué es CD?
Continuous Delivery vs Continuous Deployment
Continuous Delivery
Entrega Continua: El código está siempre en estado desplegable. Los despliegues a producción requieren aprobación manual.
- El pipeline prepara artefactos listos para producción
- Se despliega automáticamente a entornos de staging/QA
- El paso a producción es decisión del negocio
Continuous Deployment
Despliegue Continuo: Todo cambio que pasa las pruebas automáticas se despliega automáticamente a producción sin intervención humana.
- Mayor nivel de automatización
- Requiere alta confianza en las pruebas
- Feedback inmediato del usuario final
Ejemplos de uso de CD
- Despliegue automático a ambientes de staging
- Generación de artefactos (builds finales, imágenes Docker)
- Despliegue a producción con aprobación o automático
¿Qué problemas resuelven CI/CD?
- Integración manual propensa a errores → Automatización
- Detección tardía de bugs → Feedback rápido
- Despliegues lentos y riesgosos → Releases frecuentes y seguras
- Inconsistencias entre entornos → Pipelines reproducibles
- Falta de visibilidad → Trazabilidad completa
2. Pipeline CI Básico en GitLab
Estructura de Pipeline CI
Un pipeline CI típico incluye:
- Build automático - Compilación del código
- Lint - Validación de estilo y calidad de código
- Tests - Pruebas unitarias e integración
- Validación de MRs - Revisión de Merge Requests
Ejemplo: Pipeline CI en GitLab
stages:
- lint
- test
- build
lint-code:
stage: lint
image: node:18
script:
- npm ci
- npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
unit-tests:
stage: test
image: node:18
script:
- npm ci
- npm run test:unit
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
Build Automático
build-app:
stage: build
image: node:18
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
Nota: Los artefactos se pasan entre stages [1]
Validación de PRs/MRs
validate-merge-request:
stage: test
script:
- echo "Validando Merge Request..."
- npm run test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
only:
- merge_requests
Se ejecuta solo cuando se crea o actualiza un MR
3. Pipeline CD Básico
Estructura de Pipeline CD
- Generar artefactos (build final)
- Deploy automático a entornos de prueba (staging, S3)
- Deploy manual o automático a Producción
Generar Artefactos
build-production:
stage: build
image: node:18
script:
- npm ci
- NODE_ENV=production npm run build
artifacts:
paths:
- dist/
expire_in: 7 days
only:
- main
Los artefactos son el resultado compilado listo para desplegar
Deploy Automático a S3
deploy-s3-staging:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build-production
script:
- aws s3 sync ./dist s3://$S3_BUCKET_STAGING/ --delete
- echo "Deploy to staging successful"
environment:
name: staging
url: https://staging.example.com
only:
- main
Deploy Manual a Producción
deploy-production:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build-production
script:
- aws s3 sync ./dist s3://$S3_BUCKET_PROD/ --delete
- echo "Deploy to production successful"
environment:
name: production
url: https://www.example.com
when: manual
only:
- main
when: manual requiere aprobación humana
4. Fundamentos de Seguridad
Gestión de Secrets
Nunca guardes secretos en código
Mejores Prácticas
- Usar CI/CD Variables de GitLab (Settings → CI/CD → Variables)
- Marcar variables como Protected y Masked
- Utilizar Secret Managers externos (Vault, AWS Secrets Manager)
- Usar OIDC para autenticación sin credenciales de larga duración
Ejemplo: Variables en GitLab
deploy-s3:
script:
- aws s3 cp ./dist s3://$S3_BUCKET/ --recursive
variables:
AWS_DEFAULT_REGION: us-east-1
Secretos configurados en GitLab UI:
-
AWS_ACCESS_KEY_ID(masked, protected) -
AWS_SECRET_ACCESS_KEY(masked, protected) -
S3_BUCKET(puede ser pública)
5. Estrategias de Despliegue
Deploy Estándar (In-Place)
Reemplazar la versión actual directamente
- Se detiene la aplicación
- Se instala la nueva versión
- Se reinicia la aplicación
Ventajas: Simple, bajo costo
Desventajas: Downtime, rollback más lento
Blue-Green Deployment (Conceptual)
Dos entornos idénticos: Blue (actual) y Green (nuevo)
- El tráfico va a Blue (versión actual)
- Se despliega nueva versión en Green
- Se prueba Green sin afectar usuarios
- Se cambia el tráfico de Blue a Green
- Blue queda como respaldo para rollback instantáneo
Blue-Green: Ventajas
- Zero downtime
- Rollback instantáneo (volver a Blue)
- Testing completo antes de switch
- Aislamiento entre versiones
Blue-Green: Desventajas
- Doble infraestructura (costo)
- Complejidad en bases de datos sincronizadas
- 100% de usuarios afectados si hay problema
Canary Deployment (Conceptual)
Despliegue gradual a un subconjunto de usuarios
- Desplegar nueva versión a 5% de usuarios
- Monitorear métricas y errores
- Si todo bien, aumentar a 25%, luego 50%, etc.
- Finalmente 100% de tráfico a nueva versión
Canary: Ventajas
- Menor riesgo (exposición limitada)
- Feedback temprano de usuarios reales
- Control granular del rollout
- Rollback parcial si hay problemas
Canary: Desventajas
- Mayor complejidad técnica
- Requiere routing inteligente de tráfico
- Monitoreo sofisticado necesario
- Más tiempo para despliegue completo
6. Observabilidad Mínima
Los Tres Pilares de Observabilidad
- Logs - Registro de eventos y errores
- Metrics - Métricas de rendimiento (CPU, RAM, latencia)
- Traces - Seguimiento de requests a través del sistema
Logs
Registrar eventos clave del despliegue
deploy:
script:
- echo "Starting deployment at $(date)"
- aws s3 sync ./dist s3://$S3_BUCKET/
- echo "Deployment completed successfully"
after_script:
- echo "Cleaning up resources..."
Logs centralizados en GitLab CI/CD Jobs
Health Checks Post-Deploy
Verificar que la aplicación funciona después del despliegue
deploy-production:
stage: deploy
script:
- aws s3 sync ./dist s3://$S3_BUCKET_PROD/
- echo "Waiting for deployment to propagate..."
- sleep 30
- |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://www.example.com/health)
if [ $STATUS -ne 200 ]; then
echo "Health check failed with status $STATUS"
exit 1
fi
- echo "Health check passed!"
Health Check API
Tu aplicación debe exponer un endpoint:
GET /health
Respuesta:
{
"status": "healthy",
"timestamp": "2025-11-13T21:00:00Z",
"version": "1.2.3"
}
Observabilidad: Lo Mínimo
- Logs estructurados en cada stage
- Health checks después de deploy
- Alertas básicas por email/Slack si falla
- Backup automático antes de deploy
- Procedimiento documentado de rollback manual
🚀 PRÁCTICO
Pipeline para Microfrontend Single-SPA
Objetivo
Crear un pipeline en GitLab que:
- Ejecute lint y test
- Compile el microfrontend single-spa
- Suba los archivos a S3
- Solo se ejecute en branch
main
Estructura del Proyecto
my-single-spa-app/
├── src/
├── dist/ # Build output
├── package.json
├── webpack.config.js
└── .gitlab-ci.yml
package.json Scripts
{
"scripts": {
"lint": "eslint src --ext .js,.jsx",
"test": "jest",
"build": "webpack --mode production",
"build:local": "webpack serve --mode development"
},
"devDependencies": {
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0",
"single-spa": "^5.9.5",
"eslint": "^8.45.0",
"jest": "^29.6.0"
}
}
.gitlab-ci.yml (Parte 1)
stages:
- lint
- test
- build
- deploy
variables:
AWS_DEFAULT_REGION: us-east-1
NODE_VERSION: "18"
# Template para jobs de Node.js
.node-template: &node-template
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
.gitlab-ci.yml (Parte 2)
lint:
<<: *node-template
stage: lint
script:
- npm run lint
rules:
- if: $CI_COMMIT_BRANCH == "main"
test:
<<: *node-template
stage: test
script:
- npm run test -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
rules:
- if: $CI_COMMIT_BRANCH == "main"
.gitlab-ci.yml (Parte 3)
build:
<<: *node-template
stage: build
script:
- NODE_ENV=production npm run build
- ls -lah dist/
artifacts:
paths:
- dist/
expire_in: 1 day
rules:
- if: $CI_COMMIT_BRANCH == "main"
.gitlab-ci.yml (Parte 4)
deploy-s3:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build
script:
- echo "Deploying to S3 bucket $S3_BUCKET..."
- aws s3 sync ./dist s3://$S3_BUCKET/ --delete --acl public-read
- echo "Deployment completed successfully!"
- echo "URL: https://$S3_BUCKET.s3.amazonaws.com/index.html"
environment:
name: production
url: https://$S3_BUCKET.s3.amazonaws.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
Variables de Entorno en GitLab
Settings → CI/CD → Variables
| Key | Value | Protected | Masked |
|---|---|---|---|
AWS_ACCESS_KEY_ID |
AKIA... | ✅ | ✅ |
AWS_SECRET_ACCESS_KEY |
wJalr... | ✅ | ✅ |
S3_BUCKET |
my-single-spa-app | ✅ | ❌ |
Configuración S3 Bucket
- Crear bucket en AWS S3
- Bucket Policy:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-single-spa-app/*"
}]
}
Webpack Config para Single-SPA
const { merge } = require('webpack-merge');
const singleSpaDefaults = require('webpack-config-single-spa-react');
module.exports = (webpackConfigEnv, argv) => {
const defaultConfig = singleSpaDefaults({
orgName: 'myorg',
projectName: 'my-app',
webpackConfigEnv,
argv,
});
return merge(defaultConfig, {
output: {
publicPath: process.env.NODE_ENV === 'production'
? 'https://my-single-spa-app.s3.amazonaws.com/'
: 'http://localhost:8080/',
},
});
};
Flujo Completo
-
Desarrollador hace commit a branch
feature/new-component -
Crea Merge Request a
main - Pipeline NO corre (solo en main)
-
MR aprobado y merged a
main -
Pipeline se ejecuta:
- ✅ Lint
- ✅ Test
- ✅ Build
- ✅ Deploy a S3
- Aplicación actualizada en producción
Mejoras Opcionales
- Agregar health check después de deploy
- Notificaciones a Slack cuando deploy exitoso
- Dependency scanning para vulnerabilidades
- Deploy a staging antes de producción
- Rollback automático si health check falla
Health Check Post-Deploy (Opcional)
deploy-s3:
# ... código anterior ...
after_script:
- |
echo "Running health check..."
sleep 15
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$S3_BUCKET.s3.amazonaws.com/index.html)
if [ $STATUS -ne 200 ]; then
echo "❌ Health check failed with status $STATUS"
exit 1
fi
echo "✅ Health check passed!"
Notificaciones a Slack (Opcional)
notify-slack:
stage: .post
image: curlimages/curl:latest
script:
- |
curl -X POST $SLACK_WEBHOOK_URL \
-H 'Content-Type: application/json' \
-d "{\"text\":\"✅ Deploy exitoso: $CI_COMMIT_MESSAGE\"}"
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
Variable: SLACK_WEBHOOK_URL (masked)
Resumen
Lo que Aprendimos
✅ CI/CD: Conceptos de integración y entrega continua
✅ Pipeline CI: Lint, test, build
✅ Pipeline CD: Artefactos, deploy a S3, producción
✅ Seguridad: Secrets, dependency scanning
✅ Estrategias: In-place, Blue-Green, Canary
✅ Observabilidad: Logs, health checks, rollback
✅ Práctica: Pipeline completo para single-spa
Recursos Adicionales
¡Gracias!
¿Preguntas?
CI/CD
By anlijudavid
CI/CD
- 36