Infraestructura y Despliegue

Infraestructura y Despliegue

Arquitectura de Infraestructura

Entornos de Despliegue

El sistema DTEM está diseñado para operar en múltiples entornos:

1. Entorno de Desarrollo

  • Propósito: Desarrollo y pruebas unitarias
  • Recursos: Mínimos (2 cores, 4GB RAM)
  • Base de Datos: PostgreSQL local o Docker
  • Cache: Redis local
  • Monitoreo: Básico

2. Entorno de Staging/Testing

  • Propósito: Pruebas de integración y validación
  • Recursos: Medios (4 cores, 8GB RAM)
  • Base de Datos: PostgreSQL con datos de prueba
  • Cache: Redis cluster pequeño
  • Monitoreo: Completo

3. Entorno de Producción

  • Propósito: Operación real del sistema
  • Recursos: Altos (8+ cores, 16+ GB RAM)
  • Base de Datos: PostgreSQL cluster
  • Cache: Redis cluster
  • Monitoreo: Avanzado con alertas

Topología de Red

Internet
    │
    ▼
┌─────────────────┐
│   Load Balancer │ (Nginx/HAProxy)
│   (SSL/TLS)     │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  Web Servers    │ (Nginx + App)
│  (x3 instances) │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  App Servers    │ (Node.js/Java)
│  (x3 instances) │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  Database       │ (PostgreSQL)
│  (Master/Slave) │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│  Cache Layer    │ (Redis Cluster)
│  (x3 nodes)     │
└─────────────────┘

Despliegue con Docker

Docker Compose para Producción

# docker-compose.prod.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
      - ./app/dist:/usr/share/nginx/html
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - dtem-network

  app:
    build:
      context: ./app
      dockerfile: Dockerfile.prod
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://dtem_user:${DB_PASSWORD}@postgres:5432/dtem
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - postgres
      - redis
    restart: unless-stopped
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    networks:
      - dtem-network

  postgres:
    image: postgres:13
    environment:
      - POSTGRES_DB=dtem
      - POSTGRES_USER=dtem_user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '1.0'
          memory: 1G
    networks:
      - dtem-network

  redis:
    image: redis:6-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
    networks:
      - dtem-network

  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
    restart: unless-stopped
    networks:
      - dtem-network

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
    restart: unless-stopped
    networks:
      - dtem-network

volumes:
  postgres_data:
  redis_data:
  prometheus_data:
  grafana_data:

networks:
  dtem-network:
    driver: bridge

Dockerfile para Producción

# app/Dockerfile.prod
FROM node:18-alpine AS builder

WORKDIR /app

# Copiar package files
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# Copiar código fuente
COPY . .

# Construir aplicación
RUN npm run build

# Imagen de producción
FROM node:18-alpine AS production

# Crear usuario no root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001

WORKDIR /app

# Copiar dependencias y código construido
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./

# Crear directorios necesarios
RUN mkdir -p /app/logs /app/certificates
RUN chown -R nodejs:nodejs /app

USER nodejs

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/api/health || exit 1

CMD ["node", "dist/server.js"]

Despliegue con Kubernetes

Namespace y ConfigMap

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dtem

---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: dtem-config
  namespace: dtem
data:
  NODE_ENV: "production"
  DATABASE_HOST: "postgres-service"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "dtem"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  LOG_LEVEL: "info"

Secret

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: dtem-secrets
  namespace: dtem
type: Opaque
data:
  database-password: <base64-encoded-password>
  redis-password: <base64-encoded-password>
  jwt-secret: <base64-encoded-jwt-secret>
  encryption-key: <base64-encoded-encryption-key>

Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dtem-app
  namespace: dtem
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dtem-app
  template:
    metadata:
      labels:
        app: dtem-app
    spec:
      containers:
      - name: dtem-app
        image: dtem/app:latest
        ports:
        - containerPort: 8080
        env:
        - name: NODE_ENV
          valueFrom:
            configMapKeyRef:
              name: dtem-config
              key: NODE_ENV
        - name: DATABASE_URL
          value: "postgresql://dtem_user:$(DATABASE_PASSWORD)@$(DATABASE_HOST):$(DATABASE_PORT)/$(DATABASE_NAME)"
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dtem-secrets
              key: database-password
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dtem-secrets
              key: redis-password
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: dtem-secrets
              key: jwt-secret
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /api/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        volumeMounts:
        - name: certificates
          mountPath: /app/certificates
          readOnly: true
      volumes:
      - name: certificates
        secret:
          secretName: dtem-certificates

Service

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: dtem-app-service
  namespace: dtem
spec:
  selector:
    app: dtem-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

Ingress

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dtem-ingress
  namespace: dtem
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
  tls:
  - hosts:
    - dtem.yourdomain.com
    secretName: dtem-tls
  rules:
  - host: dtem.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: dtem-app-service
            port:
              number: 80

Horizontal Pod Autoscaler

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: dtem-hpa
  namespace: dtem
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: dtem-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Configuración de Base de Datos

PostgreSQL Master-Slave

# postgresql-master.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-master
  namespace: dtem
spec:
  serviceName: postgres-master
  replicas: 1
  selector:
    matchLabels:
      app: postgres-master
  template:
    metadata:
      labels:
        app: postgres-master
    spec:
      containers:
      - name: postgres
        image: postgres:13
        env:
        - name: POSTGRES_DB
          value: dtem
        - name: POSTGRES_USER
          value: dtem_user
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dtem-secrets
              key: database-password
        - name: POSTGRES_REPLICATION_USER
          value: replicator
        - name: POSTGRES_REPLICATION_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dtem-secrets
              key: replication-password
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        - name: postgres-config
          mountPath: /etc/postgresql/postgresql.conf
          subPath: postgresql.conf
  volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Configuración de Redis Cluster

# redis-cluster.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: dtem
spec:
  serviceName: redis-cluster
  replicas: 6
  selector:
    matchLabels:
      app: redis-cluster
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      containers:
      - name: redis
        image: redis:6-alpine
        command:
        - redis-server
        - /etc/redis/redis.conf
        ports:
        - containerPort: 6379
        - containerPort: 16379
        volumeMounts:
        - name: redis-data
          mountPath: /data
        - name: redis-config
          mountPath: /etc/redis
        resources:
          requests:
            memory: "256Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "200m"
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

Monitoreo y Logging

Prometheus Configuration

# monitoring/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "dtem_rules.yml"

scrape_configs:
  - job_name: 'dtem-app'
    static_configs:
      - targets: ['app:8080']
    metrics_path: '/api/metrics'
    scrape_interval: 10s

  - job_name: 'postgres'
    static_configs:
      - targets: ['postgres:5432']

  - job_name: 'redis'
    static_configs:
      - targets: ['redis:6379']

  - job_name: 'nginx'
    static_configs:
      - targets: ['nginx:80']

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093

Grafana Dashboard

{
  "dashboard": {
    "title": "DTEM System Dashboard",
    "panels": [
      {
        "title": "Request Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(http_requests_total[5m])",
            "legendFormat": "{{method}} {{status}}"
          }
        ]
      },
      {
        "title": "Response Time",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))",
            "legendFormat": "95th percentile"
          }
        ]
      },
      {
        "title": "Database Connections",
        "type": "singlestat",
        "targets": [
          {
            "expr": "pg_stat_database_numbackends",
            "legendFormat": "Active Connections"
          }
        ]
      }
    ]
  }
}

Pipeline de CI/CD

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy DTEM

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'
    - run: npm ci
    - run: npm run test
    - run: npm run lint

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    - uses: docker/setup-buildx-action@v2
    - uses: docker/login-action@v2
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    - uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ghcr.io/${{ github.repository }}:latest

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
    - uses: actions/checkout@v3
    - name: Deploy to Kubernetes
      run: |
        echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
        export KUBECONFIG=kubeconfig
        kubectl set image deployment/dtem-app dtem-app=ghcr.io/${{ github.repository }}:latest -n dtem
        kubectl rollout status deployment/dtem-app -n dtem

Backup y Recuperación

Script de Backup Automatizado

#!/bin/bash
# backup-k8s.sh

NAMESPACE="dtem"
BACKUP_DIR="/backup/k8s"
DATE=$(date +%Y%m%d_%H%M%S)

# Crear directorio de backup
mkdir -p $BACKUP_DIR

# Backup de base de datos
kubectl exec -n $NAMESPACE postgres-master-0 -- pg_dump -U dtem_user dtem > $BACKUP_DIR/postgres_$DATE.sql

# Backup de Redis
kubectl exec -n $NAMESPACE redis-cluster-0 -- redis-cli --rdb - > $BACKUP_DIR/redis_$DATE.rdb

# Backup de configuraciones
kubectl get configmaps -n $NAMESPACE -o yaml > $BACKUP_DIR/configmaps_$DATE.yaml
kubectl get secrets -n $NAMESPACE -o yaml > $BACKUP_DIR/secrets_$DATE.yaml

# Comprimir backup
tar -czf $BACKUP_DIR/dtem_backup_$DATE.tar.gz $BACKUP_DIR/*_$DATE.*

# Eliminar archivos individuales
rm $BACKUP_DIR/*_$DATE.*

# Subir a almacenamiento en la nube (opcional)
# aws s3 cp $BACKUP_DIR/dtem_backup_$DATE.tar.gz s3://your-backup-bucket/

echo "Backup completado: dtem_backup_$DATE.tar.gz"

Próxima sección: Mantenimiento y Operaciones