# 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 ```dockerfile # 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 ```dockerfile # 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 ```nginx # 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 ```yaml # 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 ```bash cd deploy/docker # Crear archivo .env echo "DB_PASSWORD=Your_Str0ng_P@ssword!" > .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): ```bash # 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 ```yaml # Variables de entorno en el workflow K3S_HOST: 100.67.198.92 # IP del master (hp62a) NAMESPACE: student-enrollment DOMAIN: academia.ingeniumcodex.com ``` **Tiempo de despliegue:** ~3-5 minutos desde push hasta producción --- ## Despliegue Manual ### Backend ```bash 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 ```bash 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 ```bash # 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 ``` --- ## Despliegue en Kubernetes (k3s) ### Estructura de Manifiestos ``` deploy/k3s/ ├── namespace.yaml # Namespace dedicado ├── secrets.yaml # Credenciales BD ├── configmap.yaml # Configuración ├── sqlserver.yaml # Base de datos ├── api.yaml # Backend GraphQL ├── frontend.yaml # Frontend Angular ├── ingress.yaml # Traefik ingress ├── ingress-tls.yaml # TLS con cert-manager ├── hpa.yaml # Autoscaling ├── networkpolicy.yaml # Seguridad de red ├── kustomization.yaml # Kustomize config └── deploy.sh # Script de despliegue ``` ### Requisitos k3s - k3s instalado y funcionando - kubectl configurado - Acceso al cluster - (Opcional) cert-manager para TLS ### Despliegue Rápido ```bash 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 student-enrollment ``` ### Comandos del Script ```bash ./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 ```bash # Editar secrets antes de desplegar kubectl create secret generic student-secrets \ --namespace=student-enrollment \ --from-literal=db-password='TuPasswordSeguro123!' \ --from-literal=db-connection-string='Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=TuPasswordSeguro123!;TrustServerCertificate=True' \ --dry-run=client -o yaml > secrets.yaml ``` ### Acceso Local (Desarrollo) ```bash # 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) ```bash # 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 student-enrollment ``` ### Scaling Manual ```bash # Escalar API kubectl scale deployment student-api -n student-enrollment --replicas=3 # Escalar Frontend kubectl scale deployment student-frontend -n student-enrollment --replicas=2 ``` ### Monitoreo ```bash # Estado de pods kubectl get pods -n student-enrollment -w # Logs en tiempo real kubectl logs -n student-enrollment -l app=student-api -f # Eventos kubectl get events -n student-enrollment --sort-by='.lastTimestamp' # Recursos kubectl top pods -n student-enrollment ``` ### Rollback en k3s ```bash # Ver historial de deployments kubectl rollout history deployment/student-api -n student-enrollment # Rollback a versión anterior kubectl rollout undo deployment/student-api -n student-enrollment # Rollback a revisión específica kubectl rollout undo deployment/student-api -n student-enrollment --to-revision=2 ``` ### Troubleshooting ```bash # Pod no inicia kubectl describe pod -n student-enrollment # Conectar a pod kubectl exec -it -n student-enrollment -- /bin/sh # Verificar conectividad BD kubectl exec -it -n student-enrollment -- \ curl -v telnet://sqlserver:1433 # Verificar ingress kubectl describe ingress student-ingress -n student-enrollment ```