Saltar al contenido principal
Tutoriales#DevOps#CI/CD#GitHub Actions#deployment#automation

DevOps y CI/CD: Pipeline Moderno con GitHub Actions

SD
Por Samuel Diaz
5 de diciembre de 202415 min de lectura
X (Twitter)
LinkedIn
Telegram
WhatsApp
Email
Copiar enlace
407 vistas

DevOps y CI/CD: Pipeline Moderno con GitHub Actions

Un pipeline de CI/CD bien diseñado es fundamental para el desarrollo moderno. Aprende a crear workflows automatizados que mejoren la calidad y velocidad de entrega.

Arquitectura del Pipeline

Estructura Básica

name: CI/CD Pipeline

on:

push:

branches: [ main, develop ]

pull_request:

branches: [ main ]

env:

REGISTRY: ghcr.io

IMAGE_NAME: ${{ github.repository }}

jobs:

test:

runs-on: ubuntu-latest

services:

postgres:

image: postgres:15

env:

POSTGRES_PASSWORD: postgres

POSTGRES_DB: test_db

options: >-

--health-cmd pg_isready

--health-interval 10s

--health-timeout 5s

--health-retries 5

ports:

- 5432:5432

steps:

- name: Checkout code

uses: actions/checkout@v4

- name: Setup Node.js

uses: actions/setup-node@v4

with:

node-version: '18'

cache: 'yarn'

- name: Install dependencies

run: yarn install --frozen-lockfile

- name: Run linting

run: yarn lint

- name: Run type checking

run: yarn type-check

- name: Run tests

run: yarn test:coverage

env:

DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db

- name: Upload coverage

uses: codecov/codecov-action@v3

Testing Automatizado

Configuración de Tests

// jest.config.js

module.exports = {

testEnvironment: 'jsdom',

setupFilesAfterEnv: ['/src/test/setup.ts'],

testMatch: [

'/src/**/__tests__/**/*.{js,jsx,ts,tsx}',

'/src/**/*.{test,spec}.{js,jsx,ts,tsx}'

],

collectCoverageFrom: [

'src/**/*.{js,jsx,ts,tsx}',

'!src/**/*.d.ts',

'!src/test/**',

],

coverageThreshold: {

global: {

branches: 80,

functions: 80,

lines: 80,

statements: 80

}

}

};

Tests de Integración

// src/test/integration/api.test.ts

import { createMocks } from 'node-mocks-http';

import handler from '@/pages/api/users';

import { prisma } from '@/lib/prisma';

describe('/api/users', () => {

beforeEach(async () => {

await prisma.user.deleteMany();

});

it('should create a new user', async () => {

const { req, res } = createMocks({

method: 'POST',

body: {

email: 'test@example.com',

name: 'Test User',

},

});

await handler(req, res);

expect(res._getStatusCode()).toBe(201);

const user = await prisma.user.findUnique({

where: { email: 'test@example.com' }

});

expect(user).toBeTruthy();

expect(user?.name).toBe('Test User');

});

});

Deployment Automatizado

Build y Deploy

build-and-deploy:

needs: test

runs-on: ubuntu-latest

if: github.ref == 'refs/heads/main'

steps:

- name: Checkout code

uses: actions/checkout@v4

- name: Setup Docker Buildx

uses: docker/setup-buildx-action@v3

- name: Login to Container Registry

uses: docker/login-action@v3

with:

registry: ${{ env.REGISTRY }}

username: ${{ github.actor }}

password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata

id: meta

uses: docker/metadata-action@v5

with:

images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

tags: |

type=ref,event=branch

type=ref,event=pr

type=sha,prefix={{branch}}-

- name: Build and push Docker image

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

- name: Deploy to production

uses: appleboy/ssh-action@v1.0.0

with:

host: ${{ secrets.HOST }}

username: ${{ secrets.USERNAME }}

key: ${{ secrets.SSH_KEY }}

script: |

docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main

docker stop myapp || true

docker rm myapp || true

docker run -d --name myapp -p 3000:3000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main

Monitoreo y Alertas

Health Checks

// src/pages/api/health.ts

import { NextApiRequest, NextApiResponse } from 'next';

import { prisma } from '@/lib/prisma';

import { redis } from '@/lib/redis';

export default async function handler(

req: NextApiRequest,

res: NextApiResponse

) {

if (req.method !== 'GET') {

return res.status(405).json({ error: 'Method not allowed' });

}

const checks = {

database: false,

redis: false,

timestamp: new Date().toISOString(),

};

try {

// Check database

await prisma.$queryRaw`SELECT 1`;

checks.database = true;

} catch (error) {

console.error('Database health check failed:', error);

}

try {

// Check Redis

await redis.ping();

checks.redis = true;

} catch (error) {

console.error('Redis health check failed:', error);

}

const isHealthy = checks.database && checks.redis;

const statusCode = isHealthy ? 200 : 503;

res.status(statusCode).json({

status: isHealthy ? 'healthy' : 'unhealthy',

checks,

});

}

Monitoring con Prometheus

// src/lib/metrics.ts

import { register, Counter, Histogram, Gauge } from 'prom-client';

export const httpRequestsTotal = new Counter({

name: 'http_requests_total',

help: 'Total number of HTTP requests',

labelNames: ['method', 'route', 'status_code'],

});

export const httpRequestDuration = new Histogram({

name: 'http_request_duration_seconds',

help: 'Duration of HTTP requests in seconds',

labelNames: ['method', 'route'],

buckets: [0.1, 0.5, 1, 2, 5],

});

export const activeConnections = new Gauge({

name: 'active_connections',

help: 'Number of active connections',

});

// Middleware para métricas

export function metricsMiddleware(req: any, res: any, next: any) {

const start = Date.now();

res.on('finish', () => {

const duration = (Date.now() - start) / 1000;

httpRequestsTotal

.labels(req.method, req.route?.path || req.url, res.statusCode)

.inc();

httpRequestDuration

.labels(req.method, req.route?.path || req.url)

.observe(duration);

});

next();

}

Seguridad en CI/CD

Secrets Management

- name: Deploy with secrets

env:

DATABASE_URL: ${{ secrets.DATABASE_URL }}

JWT_SECRET: ${{ secrets.JWT_SECRET }}

STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}

run: |

echo "Deploying with secure environment variables"

# Deploy commands here

Security Scanning

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 scan results

uses: github/codeql-action/upload-sarif@v2

with:

sarif_file: 'trivy-results.sarif'

Mejores Prácticas

1. **Fail Fast**: Ejecutar tests rápidos primero

2. **Paralelización**: Ejecutar jobs en paralelo cuando sea posible

3. **Caching**: Usar cache para dependencias y builds

4. **Rollback**: Implementar estrategias de rollback automático

5. **Monitoring**: Monitorear métricas de deployment

Conclusión

Un pipeline de CI/CD bien implementado es la base de un desarrollo ágil y confiable. Invierte tiempo en configurarlo correctamente desde el inicio.

Newsletter Técnico

Recibe los últimos artículos sobre desarrollo web, ciberseguridad y tecnología directamente en tu inbox.

Sin spam. Cancela tu suscripción en cualquier momento.

Comentarios (3)

MG

María González

14 de diciembre de 2024

Excelente artículo! Me ha ayudado mucho a entender estos conceptos. ¿Podrías hacer un seguimiento sobre implementación práctica?

SD
Samuel Diaz
14 dic

¡Gracias María! Definitivamente haré un artículo de seguimiento con ejemplos prácticos. ¡Excelente sugerencia!

CR

Carlos Ruiz

13 de diciembre de 2024

Los ejemplos de código están muy claros. He implementado algunas de estas técnicas en mi proyecto y funcionan perfectamente.

Artículos Relacionados