academia/docs/DEPLOYMENT.md

12 KiB

Manual de Despliegue

Requisitos del Sistema

Componente Versión Mínima
.NET SDK 10.0
Node.js 22.x
SQL Server 2022
Docker 24.x
Docker Compose 2.x

Variables de Entorno

Backend (.NET)

Variable Descripción Ejemplo
ConnectionStrings__DefaultConnection Connection string SQL Server Server=db;Database=StudentEnrollment;...
ASPNETCORE_ENVIRONMENT Ambiente Production
ASPNETCORE_URLS URLs de escucha http://+:8080
JWT_SECRET_KEY REQUERIDO - Secret JWT (mín. 32 chars) your-super-secret-key-minimum-32-chars
JWT_ISSUER Emisor JWT StudentEnrollmentApi
JWT_AUDIENCE Audiencia JWT StudentEnrollmentApp
JWT_EXPIRATION_MINUTES Expiración token 60

Frontend (Angular)

Variable Descripción Ejemplo
API_URL URL del backend GraphQL https://api.example.com/graphql

Desarrollo Local (SQLite)

Variable Descripción Ejemplo
USE_SQLITE Usar SQLite en lugar de SQL Server true
ConnectionStrings__DefaultConnection Path a archivo SQLite Data Source=./data/dev.db

Despliegue con Docker

1. Estructura de Archivos

deploy/
└── docker/
    ├── Dockerfile.api
    ├── Dockerfile.frontend
    ├── docker-compose.yml
    └── nginx.conf

2. Dockerfile Backend

# deploy/docker/Dockerfile.api
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

COPY src/backend/ .
RUN dotnet restore Host/Host.csproj
RUN dotnet publish Host/Host.csproj -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .

# Non-root user
RUN adduser --disabled-password --gecos '' appuser
USER appuser

EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost:5000/health || exit 1

ENTRYPOINT ["dotnet", "Host.dll"]

3. Dockerfile Frontend

# deploy/docker/Dockerfile.frontend
FROM node:22-alpine AS build
WORKDIR /app

COPY src/frontend/package*.json ./
RUN npm ci

COPY src/frontend/ .
RUN npm run build -- --configuration production

FROM nginx:alpine
COPY --from=build /app/dist/student-enrollment/browser /usr/share/nginx/html
COPY deploy/docker/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

4. Nginx Configuration

# deploy/docker/nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # Gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;

    # SPA routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Proxy GraphQL
    location /graphql {
        proxy_pass http://api:5000/graphql;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }

    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

5. Docker Compose

# deploy/docker/docker-compose.yml
services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=${DB_PASSWORD}
    ports:
      - "1433:1433"
    volumes:
      - sqlserver-data:/var/opt/mssql
    healthcheck:
      test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $${SA_PASSWORD} -Q "SELECT 1" -C
      interval: 10s
      timeout: 3s
      retries: 10

  api:
    build:
      context: ../..
      dockerfile: deploy/docker/Dockerfile.api
    environment:
      - ConnectionStrings__DefaultConnection=Server=db;Database=StudentEnrollment;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True
      - ASPNETCORE_ENVIRONMENT=Production
    ports:
      - "5000:5000"
    depends_on:
      db:
        condition: service_healthy

  frontend:
    build:
      context: ../..
      dockerfile: deploy/docker/Dockerfile.frontend
    ports:
      - "80:80"
    depends_on:
      - api

volumes:
  sqlserver-data:

6. Ejecutar

cd deploy/docker

# Crear archivo .env
echo "DB_PASSWORD=Asde71.4Asde71.4" > .env

# Build e iniciar
docker-compose up -d --build

# Ver logs
docker-compose logs -f

# Detener
docker-compose down

Desarrollo Local (Sin Docker)

Script que levanta backend + frontend con SQLite (sin necesidad de SQL Server):

# Iniciar todo
./scripts/dev-start.sh start

# Ver estado
./scripts/dev-start.sh status

# Detener
./scripts/dev-start.sh stop

# Reiniciar
./scripts/dev-start.sh restart

Características:

  • Backend usa SQLite en ./data/dev.db
  • No requiere Docker ni SQL Server
  • Frontend en puerto 4200, Backend en puerto 5000
  • Hot reload habilitado
  • PIDs guardados para cleanup automático

CI/CD Pipeline (Gitea Actions)

Ubicación: .gitea/workflows/deploy.yaml

Trigger: Push a rama main

Flujo Automático

  1. Checkout código en Gitea runner
  2. Setup SSH hacia K3s master
  3. Sync código con rsync (excluye node_modules, dist, etc.)
  4. Build imágenes en paralelo (API + Frontend)
  5. Import a K3s containerd (docker save | k3s ctr images import)
  6. Apply manifiestos con Kustomize
  7. Rolling restart de deployments
  8. Health checks con curl
  9. Rollback automático si falla

Secretos Requeridos en Gitea

Secreto Descripción
K3S_SSH_KEY Clave SSH privada para conectar a K3s master
K3S_SUDO_PASS Password de sudo en K3s host

Configuración

# Variables de entorno en el workflow
K3S_HOST: 100.67.198.92          # IP del master (hp62a)
NAMESPACE: academia
DOMAIN: academia.ingeniumcodex.com

Tiempo de despliegue: ~3-5 minutos desde push hasta producción


Despliegue Manual

Backend

cd src/backend/Host

# Build producción
dotnet publish -c Release -o ./publish

# Configurar connection string
export ConnectionStrings__DefaultConnection="Server=..."

# Ejecutar migraciones
dotnet ef database update --project ../Adapters/Driven/Persistence

# Iniciar
cd publish
dotnet Host.dll

Frontend

cd src/frontend

# Build producción
ng build --configuration production

# Los archivos quedan en dist/student-enrollment/browser/
# Servir con cualquier servidor web (nginx, apache, etc.)

Checklist Pre-Producción

Seguridad

  • Connection strings en variables de entorno (no en código)
  • JWT_SECRET_KEY configurado (mínimo 32 caracteres)
  • HTTPS habilitado
  • CORS configurado solo para dominios permitidos
  • Rate limiting activo (30 mutations/min, 100 queries/min)
  • Security headers configurados
  • Logs sin datos sensibles (Serilog filtra tokens/passwords)

Performance

  • Response compression habilitado
  • Output caching configurado
  • Bundle Angular optimizado (< 200KB transferidos)
  • Índices de BD aplicados

Monitoreo

  • Health checks funcionando (/health)
  • Logs centralizados
  • Métricas de aplicación

Base de Datos

  • Backup configurado
  • Migraciones aplicadas
  • Datos seed cargados (5 profesores, 10 materias)

URLs de Verificación

Servicio URL Esperado
API Health http://api:8080/health 200 OK
GraphQL Playground http://api:8080/graphql Banana Cake Pop
Frontend http://frontend:80 App Angular

URLs de Producción (K3s)

Servicio URL
Frontend https://academia.ingeniumcodex.com
API GraphQL https://academia.ingeniumcodex.com/graphql
Health Check https://academia.ingeniumcodex.com/health
Login https://academia.ingeniumcodex.com/login
Registro https://academia.ingeniumcodex.com/register
Dashboard https://academia.ingeniumcodex.com/dashboard

Rollback

# Docker
docker-compose down
docker-compose up -d --no-build  # Usa imágenes anteriores

# Manual
# Restaurar versión anterior de DLLs/archivos
# Rollback de migraciones si es necesario:
dotnet ef database update <MigrationName>

Despliegue en Kubernetes (k3s)

Estructura de Manifiestos

deploy/k3s/
├── namespace.yaml      # Namespace: academia
├── secrets.yaml        # Credenciales BD
├── configmap.yaml      # Configuración
├── sqlserver.yaml      # Base de datos
├── api.yaml            # Backend GraphQL
├── frontend.yaml       # Frontend Angular
├── ingress.yaml        # Traefik IngressRoute + TLS
├── networkpolicy.yaml  # Seguridad de red (incluido en kustomize)
├── hpa.yaml            # Autoscaling (opcional, no incluido)
├── kustomization.yaml  # Kustomize config
└── deploy.sh           # Script de despliegue

Nota: networkpolicy.yaml está incluido en kustomization.yaml y aplica las siguientes reglas:

  • Default deny: Bloquea todo tráfico entrante por defecto
  • Frontend: Solo acepta tráfico desde Ingress
  • API: Solo acepta tráfico desde Frontend e Ingress
  • SQL Server: Solo acepta conexiones desde API

Requisitos k3s

  • k3s instalado y funcionando
  • kubectl configurado
  • Acceso al cluster
  • (Opcional) cert-manager para TLS

Despliegue Rápido

cd deploy/k3s

# Opción 1: Con script
./deploy.sh all        # Build + Deploy

# Opción 2: Con kustomize
kubectl apply -k .

# Verificar estado
kubectl get all -n academia

Comandos del Script

./deploy.sh build      # Construir imágenes Docker
./deploy.sh deploy     # Desplegar a k3s
./deploy.sh status     # Ver estado del cluster
./deploy.sh logs api   # Ver logs del API
./deploy.sh forward    # Port-forward para desarrollo
./deploy.sh delete     # Eliminar deployment

Configurar Secrets

# Editar secrets antes de desplegar
kubectl create secret generic student-secrets \
  --namespace=academia \
  --from-literal=db-password='Asde71.4Asde71.4' \
  --from-literal=db-connection-string='Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=Asde71.4Asde71.4;TrustServerCertificate=True' \
  --dry-run=client -o yaml > secrets.yaml

Acceso Local (Desarrollo)

# Agregar entrada en /etc/hosts
echo "127.0.0.1 students.local" | sudo tee -a /etc/hosts

# Port forward
./deploy.sh forward

# Acceder en:
# - Frontend: http://localhost:8080
# - API: http://localhost:5000/graphql

Habilitar TLS (Producción)

# 1. Instalar cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

# 2. Editar ingress-tls.yaml con tu dominio y email

# 3. Aplicar ingress con TLS
kubectl apply -f ingress-tls.yaml -n academia

Scaling Manual

# Escalar API
kubectl scale deployment student-api -n academia --replicas=3

# Escalar Frontend
kubectl scale deployment student-frontend -n academia --replicas=2

Monitoreo

# Estado de pods
kubectl get pods -n academia -w

# Logs en tiempo real
kubectl logs -n academia -l app=student-api -f

# Eventos
kubectl get events -n academia --sort-by='.lastTimestamp'

# Recursos
kubectl top pods -n academia

Rollback en k3s

# Ver historial de deployments
kubectl rollout history deployment/student-api -n academia

# Rollback a versión anterior
kubectl rollout undo deployment/student-api -n academia

# Rollback a revisión específica
kubectl rollout undo deployment/student-api -n academia --to-revision=2

Troubleshooting

# Pod no inicia
kubectl describe pod <pod-name> -n academia

# Conectar a pod
kubectl exec -it <pod-name> -n academia -- /bin/sh

# Verificar conectividad BD
kubectl exec -it <api-pod> -n academia -- \
  curl -v telnet://sqlserver:1433

# Verificar ingress
kubectl describe ingress student-ingress -n academia