513 lines
12 KiB
Markdown
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 | 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=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
|
|
```
|