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