Técnico15 min

CI/CD Completo con GitHub Actions y AWS

Guía paso a paso para implementar un pipeline CI/CD profesional usando GitHub Actions, incluyendo tests, builds, y despliegues automatizados a AWS.

MG

María González

VP of Engineering

Pipeline CI/CD Moderno con GitHub Actions

En InfraUX, automatizamos todo. Este es nuestro enfoque para construir pipelines CI/CD robustos que despliegan a AWS con confianza.

Arquitectura del Pipeline

1graph LR 2 A[Push to GitHub] --> B[GitHub Actions] 3 B --> C{Branch?} 4 C -->|main| D[Build & Test] 5 C -->|develop| E[Build & Test] 6 C -->|feature/*| F[Build & Test] 7 D --> G[Deploy to Prod] 8 E --> H[Deploy to Staging] 9 F --> I[Preview Deploy] 10 G --> J[CloudFront Invalidation] 11 H --> J 12 I --> J

Configuración Base del Workflow

1# .github/workflows/deploy.yml 2name: Deploy to AWS 3 4on: 5 push: 6 branches: [main, develop] 7 pull_request: 8 branches: [main] 9 10env: 11 AWS_REGION: us-east-1 12 ECR_REPOSITORY: infraux-app 13 ECS_SERVICE: infraux-service 14 ECS_CLUSTER: infraux-cluster 15 CONTAINER_NAME: infraux-container 16 17jobs: 18 test: 19 runs-on: ubuntu-latest 20 steps: 21 - uses: actions/checkout@v4 22 23 - name: Setup Node.js 24 uses: actions/setup-node@v4 25 with: 26 node-version: '20' 27 cache: 'npm' 28 29 - name: Install dependencies 30 run: npm ci 31 32 - name: Run linter 33 run: npm run lint 34 35 - name: Run tests 36 run: npm run test:ci 37 38 - name: Upload coverage 39 uses: codecov/codecov-action@v3 40 with: 41 token: ${{ secrets.CODECOV_TOKEN }}

Build y Push a ECR

1 build: 2 needs: test 3 runs-on: ubuntu-latest 4 outputs: 5 image: ${{ steps.image.outputs.image }} 6 7 steps: 8 - uses: actions/checkout@v4 9 10 - name: Configure AWS credentials 11 uses: aws-actions/configure-aws-credentials@v4 12 with: 13 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 14 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 15 aws-region: ${{ env.AWS_REGION }} 16 17 - name: Login to Amazon ECR 18 id: login-ecr 19 uses: aws-actions/amazon-ecr-login@v2 20 21 - name: Build, tag, and push image 22 id: image 23 env: 24 ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} 25 IMAGE_TAG: ${{ github.sha }} 26 run: | 27 docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . 28 docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG 29 echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

Deploy a ECS con Blue/Green

1 deploy: 2 needs: build 3 runs-on: ubuntu-latest 4 environment: 5 name: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }} 6 url: ${{ steps.deploy.outputs.url }} 7 8 steps: 9 - uses: actions/checkout@v4 10 11 - name: Configure AWS credentials 12 uses: aws-actions/configure-aws-credentials@v4 13 with: 14 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 15 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 16 aws-region: ${{ env.AWS_REGION }} 17 18 - name: Download task definition 19 run: | 20 aws ecs describe-task-definition \ 21 --task-definition ${{ env.ECS_SERVICE }} \ 22 --query taskDefinition > task-definition.json 23 24 - name: Update task definition 25 id: task-def 26 uses: aws-actions/amazon-ecs-render-task-definition@v1 27 with: 28 task-definition: task-definition.json 29 container-name: ${{ env.CONTAINER_NAME }} 30 image: ${{ needs.build.outputs.image }} 31 32 - name: Deploy to ECS 33 uses: aws-actions/amazon-ecs-deploy-task-definition@v1 34 with: 35 task-definition: ${{ steps.task-def.outputs.task-definition }} 36 service: ${{ env.ECS_SERVICE }} 37 cluster: ${{ env.ECS_CLUSTER }} 38 wait-for-service-stability: true

Estrategias Avanzadas

1. Cache de Dependencias

1- name: Cache node modules 2 uses: actions/cache@v3 3 with: 4 path: ~/.npm 5 key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 6 restore-keys: | 7 ${{ runner.os }}-node-

2. Matrix Strategy para Tests

1test: 2 strategy: 3 matrix: 4 node-version: [18, 20] 5 os: [ubuntu-latest, windows-latest] 6 runs-on: ${{ matrix.os }} 7 steps: 8 - uses: actions/setup-node@v4 9 with: 10 node-version: ${{ matrix.node-version }}

3. Despliegue Canary

1- name: Deploy Canary 2 run: | 3 # Actualizar solo el 10% del tráfico 4 aws ecs update-service \ 5 --cluster ${{ env.ECS_CLUSTER }} \ 6 --service ${{ env.ECS_SERVICE }} \ 7 --deployment-configuration \ 8 "maximumPercent=110,minimumHealthyPercent=90,deploymentCircuitBreaker={enable=true,rollback=true}" 9 10 # Esperar 5 minutos 11 sleep 300 12 13 # Verificar métricas 14 ERRORS=$(aws cloudwatch get-metric-statistics \ 15 --namespace AWS/ECS \ 16 --metric-name HTTPCode_Target_5XX_Count \ 17 --dimensions Name=ServiceName,Value=${{ env.ECS_SERVICE }} \ 18 --start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \ 19 --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \ 20 --period 300 \ 21 --statistics Sum \ 22 --query 'Datapoints[0].Sum' \ 23 --output text) 24 25 if [ "$ERRORS" -gt "10" ]; then 26 echo "Too many errors, rolling back" 27 exit 1 28 fi

Seguridad en el Pipeline

1. Escaneo de Vulnerabilidades

1- name: Run Trivy vulnerability scanner 2 uses: aquasecurity/trivy-action@master 3 with: 4 image-ref: ${{ needs.build.outputs.image }} 5 format: 'sarif' 6 output: 'trivy-results.sarif' 7 8- name: Upload Trivy scan results 9 uses: github/codeql-action/upload-sarif@v2 10 with: 11 sarif_file: 'trivy-results.sarif'

2. Secrets Scanning

1- name: Scan for secrets 2 uses: trufflesecurity/trufflehog@main 3 with: 4 path: ./ 5 base: ${{ github.event.repository.default_branch }} 6 head: HEAD

Notificaciones y Monitoreo

1- name: Notify Slack 2 if: always() 3 uses: 8398a7/action-slack@v3 4 with: 5 status: ${{ job.status }} 6 text: | 7 Deployment ${{ job.status }} 8 Branch: ${{ github.ref }} 9 Commit: ${{ github.sha }} 10 Author: ${{ github.actor }} 11 webhook_url: ${{ secrets.SLACK_WEBHOOK }} 12 13- name: Create deployment record 14 uses: actions/github-script@v7 15 with: 16 script: | 17 await github.rest.repos.createDeployment({ 18 owner: context.repo.owner, 19 repo: context.repo.repo, 20 ref: context.sha, 21 environment: '${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}', 22 description: 'Deployed via GitHub Actions', 23 auto_merge: false, 24 required_contexts: [] 25 });

Optimizaciones de Performance

TécnicaMejoraImplementación
Docker Layer Caching-70% build timeBuildKit + cache mounts
Parallel Jobs-50% total timeMatrix strategy
Artifact Caching-30% install timeactions/cache
Self-hosted Runners-80% queue timeEC2 spot instances

Ejemplo Completo: Next.js a S3/CloudFront

1name: Deploy Next.js to AWS 2 3on: 4 push: 5 branches: [main] 6 7jobs: 8 deploy: 9 runs-on: ubuntu-latest 10 11 steps: 12 - uses: actions/checkout@v4 13 14 - name: Setup Node.js 15 uses: actions/setup-node@v4 16 with: 17 node-version: '20' 18 cache: 'npm' 19 20 - name: Install and Build 21 run: | 22 npm ci 23 npm run build 24 npm run export 25 env: 26 NEXT_PUBLIC_API_URL: ${{ secrets.API_URL }} 27 28 - name: Configure AWS 29 uses: aws-actions/configure-aws-credentials@v4 30 with: 31 aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 32 aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 33 aws-region: us-east-1 34 35 - name: Deploy to S3 36 run: | 37 aws s3 sync ./out s3://${{ secrets.S3_BUCKET }} \ 38 --delete \ 39 --cache-control "public, max-age=31536000, immutable" \ 40 --exclude "*.html" \ 41 --exclude "*.json" 42 43 aws s3 sync ./out s3://${{ secrets.S3_BUCKET }} \ 44 --delete \ 45 --cache-control "public, max-age=0, must-revalidate" \ 46 --exclude "*" \ 47 --include "*.html" \ 48 --include "*.json" 49 50 - name: Invalidate CloudFront 51 run: | 52 aws cloudfront create-invalidation \ 53 --distribution-id ${{ secrets.CLOUDFRONT_ID }} \ 54 --paths "/*"

Métricas de Nuestro Pipeline

<div class="info-box"> 📊 **Estadísticas de InfraUX:** - Deploys diarios: 45+ - Tiempo promedio: 3.2 minutos - Tasa de éxito: 99.7% - Rollbacks automáticos: 0.3% </div>

Best Practices

  1. Versionado Semántico Automático

    1- name: Bump version 2 uses: mathieudutour/github-tag-action@v6.1 3 with: 4 github_token: ${{ secrets.GITHUB_TOKEN }} 5 default_bump: patch
  2. Environments y Aprobaciones

    1environment: 2 name: production 3 url: https://infraux.com
  3. Rollback Automático

    1- name: Rollback on failure 2 if: failure() 3 run: | 4 aws deploy stop-deployment \ 5 --deployment-id ${{ steps.deploy.outputs.deployment-id }} \ 6 --auto-rollback-enabled

Conclusión

Un pipeline CI/CD bien diseñado es la columna vertebral de cualquier equipo de desarrollo moderno. Con GitHub Actions y AWS, puedes construir flujos de trabajo que no solo automatizan, sino que también protegen y optimizan tus despliegues.

<div class="success-box"> 🚀 **Pro tip:** Comienza simple y evoluciona. No necesitas todas estas características desde el día 1. Construye incrementalmente basándote en las necesidades de tu equipo. </div>
#cicd#github-actions#aws#devops#automation