CI/CD: Conceptos y Estrategias 🔄
La integración continua y el despliegue continuo son el corazón de DevOps moderno, permitiendo entregas rápidas, seguras y confiables.
🎯 ¿Qué es CI/CD?
Continuous Integration (CI)
Definición: Práctica de integrar cambios de código frecuentemente en un repositorio compartido, con validación automática.
Objetivos:
- Detectar errores temprano
- Reducir riesgos de integración
- Mejorar calidad del código
- Acelerar desarrollo
Continuous Delivery (CD) vs Continuous Deployment
Continuous Delivery
- Definición: Código siempre listo para desplegar
- Característica: Requiere intervención manual para desplegar a producción
- Uso: Entornos con alta regulación o procesos de aprobación
Continuous Deployment
- Definición: Despliegue automático a producción tras pasar todas las validaciones
- Característica: Sin intervención manual
- Uso: Equipos con alta confianza en automatización y testing
Developer → Commit → CI Pipeline → Automated Tests → Build → Deploy to Staging
↓
Manual Approval (CD)
↓
Auto Deploy (CD)
↓
Production
🏗️ Anatomía de un pipeline CI/CD
Etapas típicas de CI
CI Pipeline:
1. Trigger (push, PR)
2. Checkout code
3. Setup environment
4. Install dependencies
5. Lint/Format check
6. Unit tests
7. Integration tests
8. Security scan
9. Build artifacts
10. Store artifacts
Etapas típicas de CD
CD Pipeline:
1. Retrieve artifacts
2. Deploy to staging
3. Smoke tests
4. Integration tests
5. Performance tests
6. Security tests
7. Manual approval (opcional)
8. Deploy to production
9. Post-deployment verification
10. Monitoring/Alerting
🛠️ Plataformas de CI/CD
GitHub Actions
Ventajas: Integración nativa con GitHub, ecosistema grande, gratis para repos públicos Ideal para: Proyectos open source, equipos pequeños/medianos
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run unit tests
run: npm test -- --coverage
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run security audit
run: npm audit --audit-level=high
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
build:
needs: [test, security]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy-staging:
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: |
# Deploy using kubectl, helm, or API calls
echo "Deploying ${{ needs.build.outputs.image-tag }} to staging"
kubectl set image deployment/myapp myapp=${{ needs.build.outputs.image-tag }}
- name: Run smoke tests
run: |
curl -f https://staging.myapp.com/health || exit 1
deploy-production:
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
run: |
echo "Deploying to production"
kubectl set image deployment/myapp myapp=${{ needs.build.outputs.image-tag }}
- name: Verify deployment
run: |
kubectl rollout status deployment/myapp
curl -f https://myapp.com/health || exit 1
GitLab CI
Ventajas: Pipeline as code, integración completa, self-hosted Ideal para: Empresas que necesitan control total, equipos grandes
# .gitlab-ci.yml
stages:
- test
- security
- build
- deploy-staging
- deploy-production
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
KUBECONFIG: /tmp/kubeconfig
# Templates para reutilizar
.docker_template: &docker_template
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
.kubectl_template: &kubectl_template
image: bitnami/kubectl:latest
before_script:
- echo $KUBECONFIG_CONTENT | base64 -d > $KUBECONFIG
# Jobs
test:unit:
stage: test
image: node:18
services:
- postgres:15
variables:
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/test
POSTGRES_PASSWORD: postgres
script:
- npm ci
- npm run lint
- npm test -- --coverage
- npm run test:integration
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
security:sast:
stage: security
script:
- npm audit --audit-level=high
- npx snyk test --severity-threshold=high
allow_failure: false
security:container:
<<: *docker_template
stage: security
script:
- docker build -t temp-image .
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy image temp-image
allow_failure: true
build:
<<: *docker_template
stage: build
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
- develop
deploy:staging:
<<: *kubectl_template
stage: deploy-staging
script:
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE -n staging
- kubectl rollout status deployment/myapp -n staging
- curl -f https://staging.myapp.com/health
environment:
name: staging
url: https://staging.myapp.com
only:
- main
deploy:production:
<<: *kubectl_template
stage: deploy-production
script:
- kubectl set image deployment/myapp myapp=$DOCKER_IMAGE -n production
- kubectl rollout status deployment/myapp -n production
- curl -f https://myapp.com/health
environment:
name: production
url: https://myapp.com
when: manual
only:
- main
Jenkins
Ventajas: Extremadamente flexible, ecosystem huge, self-hosted Ideal para: Empresas con necesidades complejas, pipelines personalizados
// Jenkinsfile
pipeline {
agent any
environment {
DOCKER_IMAGE = "myapp:${BUILD_NUMBER}"
KUBECONFIG = credentials('kubeconfig')
SNYK_TOKEN = credentials('snyk-token')
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
retry(3)
}
triggers {
pollSCM('H/5 * * * *') // Poll every 5 minutes
}
stages {
stage('Parallel Tests') {
parallel {
stage('Unit Tests') {
agent {
docker {
image 'node:18'
}
}
steps {
sh 'npm ci'
sh 'npm run lint'
sh 'npm test -- --coverage'
publishTestResults testResultsPattern: 'test-results.xml'
publishCoverageGoberturaReports 'coverage/cobertura-coverage.xml'
}
}
stage('Security Scan') {
steps {
sh 'npm audit --audit-level=high'
sh 'npx snyk test --severity-threshold=high'
}
}
stage('Integration Tests') {
steps {
script {
docker.image('postgres:15').withRun('-e POSTGRES_PASSWORD=postgres') { db ->
docker.image('node:18').inside("--link ${db.id}:postgres") {
sh 'npm run test:integration'
}
}
}
}
}
}
}
stage('Build') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
def image = docker.build(DOCKER_IMAGE)
docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'main'
}
steps {
sh """
kubectl set image deployment/myapp myapp=${DOCKER_IMAGE} -n staging
kubectl rollout status deployment/myapp -n staging
"""
script {
def response = sh(
script: 'curl -s -o /dev/null -w "%{http_code}" https://staging.myapp.com/health',
returnStdout: true
).trim()
if (response != '200') {
error("Health check failed with status: ${response}")
}
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message "Deploy to production?"
ok "Deploy"
parameters {
choice(
name: 'DEPLOYMENT_STRATEGY',
choices: ['rolling', 'blue-green', 'canary'],
description: 'Deployment strategy'
)
}
}
steps {
script {
switch(params.DEPLOYMENT_STRATEGY) {
case 'rolling':
sh """
kubectl set image deployment/myapp myapp=${DOCKER_IMAGE} -n production
kubectl rollout status deployment/myapp -n production
"""
break
case 'blue-green':
sh 'bash scripts/blue-green-deploy.sh'
break
case 'canary':
sh 'bash scripts/canary-deploy.sh'
break
}
}
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(
channel: '#deployments',
color: 'good',
message: "✅ Pipeline succeeded for ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "❌ Pipeline failed for ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
)
emailext(
subject: "Pipeline Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: "Pipeline failed. Check console output at ${env.BUILD_URL}",
to: "devops-team@company.com"
)
}
}
}
🔄 Estrategias de branching para CI/CD
Git Flow + CI/CD
main ────●─────●─────●───── (Production releases)
│ │ │
release ─●─────●─────● (Release candidates)
│ │ │
develop ─●─●─●─●─●─●─●─●─●─ (Integration branch)
│ │ │ │ │ │
feature ──● │ │ │ │ │ (Feature development)
hotfix ─────● │ │ │ │ (Emergency fixes)
bugfix ───────● │ │ │ (Bug fixes)
feature ─────────●─● │ (New features)
feature ───────────● │ (New features)
CI/CD Mapping:
- Feature branches: CI only (tests, lint, security)
- Develop: CI + Deploy to dev environment
- Release: CI + Deploy to staging
- Main: CI + Deploy to production
- Hotfix: CI + Fast-track to production
GitHub Flow + CI/CD
main ──●─●─●─●─●─●─●─●─●─●─ (Always deployable)
│ │ │ │ │ │ │ │ │ │
feature─● │ │ │ │ │ │ │ │ │ (Short-lived features)
feature───● │ │ │ │ │ │ │ │ (Direct merge to main)
feature─────● │ │ │ │ │ │ │ (Feature flags for incomplete)
hotfix ───────● │ │ │ │ │ │ (Emergency fixes)
CI/CD Mapping:
- Feature branches: CI (tests, lint, security)
- Main: CI + Deploy to production
- Feature flags: Control feature rollout
Environment-based strategy
# Multi-environment approach
environments:
development:
branch: develop
auto_deploy: true
tests: [unit, lint]
staging:
branch: main
auto_deploy: true
tests: [unit, integration, e2e, security]
production:
branch: main
auto_deploy: false # Manual approval
tests: [all_previous, performance, smoke]
deployment_strategy: blue_green
📊 Métricas de CI/CD
DORA Metrics aplicadas
1. Deployment Frequency
# Tracking deployments
deployment_frequency:
measurement: "Deployments per day/week"
high_performers: "Multiple deploys per day"
medium_performers: "Weekly to monthly"
low_performers: "Monthly to every 6 months"
tracking:
- Git tags for releases
- Deployment pipeline executions
- Production change logs
2. Lead Time for Changes
# Time from commit to production
lead_time:
measurement: "Commit to production deployment"
high_performers: "Less than 1 hour"
medium_performers: "1 day to 1 week"
low_performers: "1 month to 6 months"
tracking:
- Git commit timestamps
- Pipeline start/end times
- Deployment completion
3. Change Failure Rate
# Percentage of failed deployments
change_failure_rate:
measurement: "Failed deployments / Total deployments"
high_performers: "0-15%"
medium_performers: "16-30%"
low_performers: "31-45%"
tracking:
- Deployment success/failure status
- Rollback events
- Incident correlation
4. Mean Time to Recovery (MTTR)
# Time to recover from failures
mttr:
measurement: "Time from failure detection to resolution"
high_performers: "Less than 1 hour"
medium_performers: "1 hour to 1 day"
low_performers: "1 day to 1 week"
tracking:
- Incident start/end times
- Rollback durations
- Fix deployment times
Pipeline-specific metrics
# Additional CI/CD metrics
pipeline_metrics:
build_success_rate:
formula: "Successful builds / Total builds"
target: "> 95%"
pipeline_duration:
measurement: "Average pipeline execution time"
targets:
ci_pipeline: "< 10 minutes"
cd_pipeline: "< 30 minutes"
full_pipeline: "< 45 minutes"
test_coverage:
targets:
unit_tests: "> 80%"
integration_tests: "> 70%"
e2e_tests: "> 60%"
security_scan_results:
critical_vulnerabilities: "0"
high_vulnerabilities: "< 5"
medium_vulnerabilities: "< 20"
🚧 Desafíos comunes y soluciones
1. Pipelines lentas
# Problema: Pipelines que toman más de 30 minutos
Soluciones:
paralelización:
- Ejecutar tests en paralelo
- Build matrix para múltiples versiones
- Parallel deployment a múltiples regions
optimización:
- Cache de dependencias
- Incremental builds
- Docker layer caching
- Artifact reuse
splitting:
- Separate CI y CD pipelines
- Fast feedback loop
- Async integration tests
2. Flaky tests
# Problema: Tests que fallan inconsistentemente
Soluciones:
detección:
- Track test failure rates
- Identify patterns
- Quarantine flaky tests
fixes:
- Proper test isolation
- Wait strategies en lugar de sleeps
- Mock external dependencies
- Deterministic test data
monitoring:
- Test reliability metrics
- Automated retry con límites
- Test result trends
3. Security integration
# Problema: Security como afterthought
Soluciones:
shift_left:
- Security scans en PR checks
- Dependency vulnerability checking
- SAST tools integration
- Infrastructure security scanning
automation:
- Automated security tests
- Policy as code validation
- Compliance checking
- Threat modeling integration
feedback:
- Security metrics tracking
- Vulnerability remediation tracking
- Security training integration
4. Environment drift
# Problema: Diferencias entre entornos
Soluciones:
infrastructure_as_code:
- Terraform/CloudFormation para todo
- Environment parity checking
- Immutable infrastructure
configuration_management:
- Centralized config management
- Environment-specific variables
- Config validation
testing:
- Environment smoke tests
- Configuration drift detection
- Automated environment recreation
🎯 Ejercicios prácticos
Ejercicio 1: Pipeline básico
- Configura pipeline con CI/CD básico
- Incluye stages: test, build, deploy
- Añade notificaciones de éxito/fallo
- Implementa artifacts passing entre stages
Ejercicio 2: Estrategia de branching
- Define estrategia de branching para tu equipo
- Configura reglas de protección de ramas
- Implementa CI/CD específico por rama
- Documenta el workflow para el equipo
Ejercicio 3: Métricas y monitoring
- Implementa tracking de DORA metrics
- Crea dashboard de pipeline metrics
- Configura alertas para pipelines fallidas
- Analiza y optimiza tiempos de pipeline
CI/CD efectivo es la base de entregas rápidas y confiables. En el próximo capítulo exploraremos las diferentes estrategias de deployment.