academia/docs/DEPLOYMENT.md

513 lines
12 KiB
Markdown

# Manual de Despliegue
## Requisitos del Sistema
| Componente | Versión Mínima |
|------------|----------------|
| .NET SDK | 10.0 |
| Node.js | 22.x |
| SQL Server | 2017 |
| 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=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):
```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: academia
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 <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
```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 academia
```
### 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=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)
```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 academia
```
### Scaling Manual
```bash
# Escalar API
kubectl scale deployment student-api -n academia --replicas=3
# Escalar Frontend
kubectl scale deployment student-frontend -n academia --replicas=2
```
### Monitoreo
```bash
# 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
```bash
# 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
```bash
# 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
```