🚀 Developer Cookbook - FASE 3: CI/CD y Automatización
Recetas prácticas para construir, desplegar y operar infraestructura moderna
📚 Tabla de Contenidos
CI/CD y Automatización
Receta 3.6: GitHub Actions - Pipelines Completos
CI/CD Pipeline Completo:
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18.x'
AWS_REGION: us-east-1
ECR_REPOSITORY: myapp
ECS_SERVICE: myapp-api
ECS_CLUSTER: myapp-cluster
jobs:
# ===== LINTING & CODE QUALITY =====
lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Prettier check
run: npm run format:check
- name: TypeScript check
run: npm run type-check
# ===== UNIT TESTS =====
test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests with coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# ===== INTEGRATION TESTS =====
integration-tests:
name: Integration Tests
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
ports:
- 5432:5432
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run migrations
run: npm run migrate
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
REDIS_URL: redis://localhost:6379
# ===== SECURITY SCAN =====
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: NPM Audit
run: npm audit --audit-level=high
# ===== BUILD & PUSH DOCKER IMAGE =====
build:
name: Build and Push Image
runs-on: ubuntu-latest
needs: [lint, test, security]
if: github.ref == 'refs/heads/main'
outputs:
image: ${{ steps.build-image.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
--build-arg NODE_ENV=production \
--cache-from $ECR_REGISTRY/$ECR_REPOSITORY:latest \
-t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
-t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
.
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ steps.build-image.outputs.image }}
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
# ===== DEPLOY TO STAGING =====
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
environment:
name: staging
url: https://staging.myapp.com
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Download task definition
run: |
aws ecs describe-task-definition \
--task-definition ${{ env.ECS_SERVICE }}-staging \
--query taskDefinition > task-definition.json
- name: Fill in new image ID
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api
image: ${{ needs.build.outputs.image }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}-staging
cluster: ${{ env.ECS_CLUSTER }}-staging
wait-for-service-stability: true
- name: Run smoke tests
run: |
sleep 30
curl -f https://staging.myapp.com/health || exit 1
# ===== DEPLOY TO PRODUCTION =====
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [build, deploy-staging]
environment:
name: production
url: https://myapp.com
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Download task definition
run: |
aws ecs describe-task-definition \
--task-definition ${{ env.ECS_SERVICE }} \
--query taskDefinition > task-definition.json
- name: Fill in new image ID
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api
image: ${{ needs.build.outputs.image }}
- name: Deploy to ECS (Blue/Green)
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
codedeploy-appspec: appspec.yaml
codedeploy-application: myapp
codedeploy-deployment-group: myapp-production
- name: Notify Slack
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: 'Production deployment ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Deployment Strategies:
# appspec.yaml (CodeDeploy - Blue/Green)
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "api"
ContainerPort: 3000
PlatformVersion: "LATEST"
Hooks:
- BeforeInstall: "LambdaFunctionToValidateBeforeInstall"
- AfterInstall: "LambdaFunctionToValidateAfterInstall"
- AfterAllowTestTraffic: "LambdaFunctionToValidateAfterTestTraffic"
- BeforeAllowTraffic: "LambdaFunctionToValidateBeforeTrafficShift"
- AfterAllowTraffic: "LambdaFunctionToValidateAfterTrafficShift"
Feature Flags:
# feature_flags.py
from typing import Dict, Any
import os
class FeatureFlags:
"""Sistema de feature flags"""
def __init__(self):
# En producción: usar LaunchDarkly, Split.io, etc.
self.flags = {
'new_checkout_ui': {
'enabled': True,
'rollout_percentage': 50, # 50% de usuarios
'rollout_users': ['beta-user-1', 'beta-user-2']
},
'async_processing': {
'enabled': True,
'rollout_percentage': 100
},
'experimental_algorithm': {
'enabled': False
}
}
def is_enabled(self, flag_name: str, user_id: str = None, context: Dict[str, Any] = None) -> bool:
"""Verificar si feature está habilitado"""
if flag_name not in self.flags:
return False
flag = self.flags[flag_name]
# Flag completamente deshabilitado
if not flag.get('enabled', False):
return False
# Usuarios específicos
if user_id and user_id in flag.get('rollout_users', []):
return True
# Rollout por porcentaje
rollout_pct = flag.get('rollout_percentage', 100)
if rollout_pct < 100 and user_id:
# Hash consistente del user_id
import hashlib
hash_value = int(hashlib.md5(user_id.encode()).hexdigest(), 16)
return (hash_value % 100) < rollout_pct
return rollout_pct == 100
# Uso
ff = FeatureFlags()
@app.route('/checkout')
def checkout():
user_id = request.user.id
if ff.is_enabled('new_checkout_ui', user_id):
return render_template('checkout_v2.html')
else:
return render_template('checkout_v1.html')
¡Felicidades!
Has completado la FASE 3: Infraestructura y DevOps del roadmap.
Lo que has aprendido:
- Docker - Containerización y multi-stage builds
- Docker Compose - Aplicaciones multi-container
- Kubernetes - Orquestación a escala
- Helm - Package manager para K8s
- AWS - Servicios principales (ECS, RDS, S3, CloudFront)
- Serverless - Lambda, API Gateway, SAM
- Terraform - Infraestructura como código
- CI/CD - GitHub Actions pipelines completos
- Deployment strategies - Blue/Green, Canary
- Feature Flags - Despliegues graduales
Próximos pasos:
FASE 4: Seguridad y Observabilidad
- Ciberseguridad para desarrolladores
- Code review y análisis estático
- Monitoreo y observabilidad
- SLIs, SLOs, SLAs
Versión: 1.0 Fecha: 2024 Autor: Roadmap del Desarrollador del Futuro Licencia: Uso educativo