feat(deploy): add Docker and Kubernetes deployment

Docker:
- Multi-stage Dockerfile for API (.NET 10)
- Multi-stage Dockerfile for frontend (Angular + Nginx)
- docker-compose.yml with resource optimization
- Nginx reverse proxy configuration
- Health checks for all services

Kubernetes (k3s):
- Namespace and ConfigMap
- SQL Server StatefulSet with PVC
- API Deployment with HPA
- Frontend Deployment
- Services and Ingress
- Network policies for security
- Secrets management

Resource optimization:
- SQL Server Express with 1GB RAM limit
- API with 512MB limit
- Frontend with 128MB limit
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-07 23:00:41 -05:00
parent 874b67d07f
commit c0a47c2ba6
20 changed files with 1042 additions and 0 deletions

View File

@ -0,0 +1,10 @@
# Variables de entorno para Docker Compose
# Copiar a .env y ajustar valores
# SQL Server
DB_PASSWORD=Your_Str0ng_P@ssword!
# Opcional: cambiar puertos si hay conflictos
# API_PORT=5000
# FRONTEND_PORT=80
# DB_PORT=1433

View File

@ -0,0 +1,45 @@
# Backend API - Multi-stage build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Cache de NuGet packages - copiamos solo archivos de proyecto primero
COPY src/backend/Domain/Domain.csproj Domain/
COPY src/backend/Application/Application.csproj Application/
COPY src/backend/Adapters/Driving/Api/Adapters.Driving.Api.csproj Adapters/Driving/Api/
COPY src/backend/Adapters/Driven/Persistence/Adapters.Driven.Persistence.csproj Adapters/Driven/Persistence/
COPY src/backend/Host/Host.csproj Host/
# Restore
RUN dotnet restore Host/Host.csproj
# Copiar código fuente
COPY src/backend/Domain/ Domain/
COPY src/backend/Application/ Application/
COPY src/backend/Adapters/ Adapters/
COPY src/backend/Host/ Host/
# Build y publish
RUN dotnet publish Host/Host.csproj \
-c Release \
-o /app
# Runtime image
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
WORKDIR /app
# Instalar curl para healthcheck
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
# Copiar binarios
COPY --from=build /app .
# Configuración
ENV ASPNETCORE_URLS=http://+:5000 \
DOTNET_RUNNING_IN_CONTAINER=true
EXPOSE 5000
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
ENTRYPOINT ["dotnet", "Host.dll"]

View File

@ -0,0 +1,42 @@
# Frontend - Multi-stage optimizado
FROM node:22-alpine AS build
WORKDIR /app
# Aumentar memoria para build Angular
ENV NODE_OPTIONS="--max-old-space-size=4096"
# Cache de dependencias - solo package files primero
COPY src/frontend/package.json src/frontend/package-lock.json* ./
# Instalar con cache
RUN npm ci --legacy-peer-deps --prefer-offline
# Copiar código y compilar
COPY src/frontend/ .
RUN npm run build -- --configuration production
# Runtime con Nginx optimizado
FROM nginx:alpine AS runtime
# Instalar curl para healthcheck
RUN apk add --no-cache curl
# Copiar archivos compilados
COPY --from=build /app/dist/student-enrollment/browser /usr/share/nginx/html
# Configuración nginx optimizada
COPY deploy/docker/nginx.conf /etc/nginx/conf.d/default.conf
# Usuario no-root
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
touch /var/run/nginx.pid && \
chown nginx:nginx /var/run/nginx.pid
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]

View File

@ -0,0 +1,108 @@
# Docker Compose - Student Enrollment System
# Optimizado para bajo consumo de RAM
# Usa archivo .env en la raíz del proyecto para configuración
services:
db:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: student-db
env_file:
- ../../.env
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=${DB_PASSWORD}
- MSSQL_MEMORY_LIMIT_MB=${DB_MEMORY_LIMIT_MB:-512}
- MSSQL_AGENT_ENABLED=false
- MSSQL_PID=Express
ports:
- "${DB_PORT:-1433}:1433"
volumes:
- sqlserver-data:/var/opt/mssql:delegated
deploy:
resources:
limits:
cpus: "1"
memory: ${DB_MEMORY_LIMIT:-1280M}
reservations:
cpus: "0.5"
memory: 1024M
healthcheck:
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -Q "SELECT 1" -C || exit 1
interval: 15s
timeout: 5s
retries: 10
start_period: 30s
networks:
- student-network
restart: unless-stopped
api:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile.api
args:
- DOTNET_CLI_TELEMETRY_OPTOUT=1
container_name: student-api
env_file:
- ../../.env
environment:
- DB_HOST=db
- DB_PORT=1433
- ConnectionStrings__DefaultConnection=Server=db;Database=${DB_NAME:-StudentEnrollment};User Id=${DB_USER:-sa};Password=${DB_PASSWORD};TrustServerCertificate=True;Max Pool Size=50;Min Pool Size=2
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Development}
- CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:8080,http://localhost:4200}
- DOTNET_gcServer=0
- DOTNET_GCHeapCount=2
- DOTNET_gcConcurrent=1
ports:
- "${API_PORT:-5000}:5000"
deploy:
resources:
limits:
cpus: "2"
memory: 512M
reservations:
cpus: "0.5"
memory: 256M
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
networks:
- student-network
restart: unless-stopped
frontend:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile.frontend
container_name: student-frontend
ports:
- "${FRONTEND_PORT:-8080}:80"
deploy:
resources:
limits:
cpus: "1"
memory: 128M
reservations:
cpus: "0.25"
memory: 64M
depends_on:
api:
condition: service_healthy
networks:
- student-network
restart: unless-stopped
volumes:
sqlserver-data:
driver: local
networks:
student-network:
driver: bridge

97
deploy/docker/nginx.conf Normal file
View File

@ -0,0 +1,97 @@
# Nginx optimizado para rendimiento local
# worker_processes auto usa todos los CPUs disponibles
upstream api_backend {
server student-api:5000;
keepalive 32;
}
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Buffers optimizados
client_body_buffer_size 16k;
client_max_body_size 8m;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
# Compresión Gzip
gzip on;
gzip_vary on;
gzip_min_length 256;
gzip_comp_level 5;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/json
application/javascript
application/xml
application/xml+rss
image/svg+xml;
# Brotli deshabilitado - requiere nginx-mod-http-brotli
# brotli on;
# brotli_comp_level 4;
# SPA routing
location / {
try_files $uri $uri/ /index.html;
# Cache para HTML (corto)
add_header Cache-Control "no-cache, must-revalidate";
}
# Proxy GraphQL con conexiones persistentes
location /graphql {
proxy_pass http://api_backend;
proxy_http_version 1.1;
# Keepalive
proxy_set_header Connection "";
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
# Headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts optimizados
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Health check
location /health {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# Cache agresivo para assets estáticos
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 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;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Desactivar logs de assets para reducir I/O
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
access_log off;
}
}

63
deploy/docker/start.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash
# Script de inicio rápido - Docker Compose optimizado
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$SCRIPT_DIR"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "${GREEN}╔═══════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ Student Enrollment System - Docker Deploy ║${NC}"
echo -e "${GREEN}╚═══════════════════════════════════════════════╝${NC}"
# Verificar Docker
if ! command -v docker &> /dev/null; then
echo -e "${RED}Error: Docker no está instalado${NC}"
exit 1
fi
# Limpiar contenedores anteriores si existen
echo -e "\n${YELLOW}► Limpiando contenedores anteriores...${NC}"
docker compose down --remove-orphans 2>/dev/null || true
# Build con cache
echo -e "\n${YELLOW}► Construyendo imágenes (con cache)...${NC}"
DOCKER_BUILDKIT=1 docker compose build --parallel
# Iniciar servicios
echo -e "\n${YELLOW}► Iniciando servicios...${NC}"
docker compose up -d
# Esperar a que los servicios estén listos
echo -e "\n${YELLOW}► Esperando servicios...${NC}"
echo -n " SQL Server: "
timeout 60 bash -c 'until docker compose exec -T db /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "${DB_PASSWORD:-Your_Str0ng_P@ssword!}" -Q "SELECT 1" -C &>/dev/null; do sleep 2; done' && echo -e "${GREEN}${NC}" || echo -e "${RED}${NC}"
echo -n " API .NET: "
timeout 60 bash -c 'until curl -sf http://localhost:5000/health &>/dev/null; do sleep 2; done' && echo -e "${GREEN}${NC}" || echo -e "${RED}${NC}"
echo -n " Frontend: "
timeout 30 bash -c 'until curl -sf http://localhost/ &>/dev/null; do sleep 2; done' && echo -e "${GREEN}${NC}" || echo -e "${RED}${NC}"
# Resumen
echo -e "\n${GREEN}╔═══════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ ✓ Sistema desplegado correctamente ║${NC}"
echo -e "${GREEN}╠═══════════════════════════════════════════════╣${NC}"
echo -e "${GREEN}║ Frontend: http://localhost ║${NC}"
echo -e "${GREEN}║ API GraphQL: http://localhost:5000/graphql ║${NC}"
echo -e "${GREEN}║ SQL Server: localhost:1433 ║${NC}"
echo -e "${GREEN}╚═══════════════════════════════════════════════╝${NC}"
echo -e "\n${YELLOW}Comandos útiles:${NC}"
echo " docker compose logs -f # Ver logs"
echo " docker compose ps # Ver estado"
echo " docker compose down # Detener todo"
echo " docker compose restart api # Reiniciar API"

86
deploy/k3s/api.yaml Normal file
View File

@ -0,0 +1,86 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: student-api
namespace: student-enrollment
labels:
app: student-api
spec:
replicas: 2
selector:
matchLabels:
app: student-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: student-api
spec:
containers:
- name: api
image: docker.io/library/academia-api:v2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
name: http
env:
- name: ASPNETCORE_ENVIRONMENT
valueFrom:
configMapKeyRef:
name: student-config
key: ASPNETCORE_ENVIRONMENT
- name: ASPNETCORE_URLS
valueFrom:
configMapKeyRef:
name: student-config
key: ASPNETCORE_URLS
- name: ConnectionStrings__DefaultConnection
valueFrom:
secretKeyRef:
name: student-secrets
key: db-connection-string
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
---
apiVersion: v1
kind: Service
metadata:
name: student-api
namespace: student-enrollment
spec:
selector:
app: student-api
ports:
- port: 5000
targetPort: 5000
name: http
type: ClusterIP

11
deploy/k3s/configmap.yaml Normal file
View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: student-config
namespace: student-enrollment
data:
ASPNETCORE_ENVIRONMENT: "Production"
ASPNETCORE_URLS: "http://+:5000"
# Frontend config
API_URL: "https://academia.ingeniumcodex.com/graphql"
HEALTH_CHECK_URL: "https://academia.ingeniumcodex.com/health"

158
deploy/k3s/deploy.sh Executable file
View File

@ -0,0 +1,158 @@
#!/bin/bash
# Script de despliegue para k3s
# Uso: ./deploy.sh [build|deploy|status|logs|delete]
set -e
NAMESPACE="student-enrollment"
REGISTRY="${REGISTRY:-localhost:5000}"
VERSION="${VERSION:-latest}"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
build_images() {
log_info "Building Docker images..."
cd "$(dirname "$0")/../.."
# Build API
log_info "Building API image..."
docker build -t ${REGISTRY}/student-enrollment/api:${VERSION} \
-f deploy/docker/Dockerfile.api .
# Build Frontend
log_info "Building Frontend image..."
docker build -t ${REGISTRY}/student-enrollment/frontend:${VERSION} \
-f deploy/docker/Dockerfile.frontend .
log_info "Images built successfully"
# Push si hay registry
if [[ "$REGISTRY" != "localhost:5000" ]]; then
log_info "Pushing images to registry..."
docker push ${REGISTRY}/student-enrollment/api:${VERSION}
docker push ${REGISTRY}/student-enrollment/frontend:${VERSION}
fi
}
deploy() {
log_info "Deploying to k3s..."
cd "$(dirname "$0")"
# Aplicar con kustomize
kubectl apply -k .
log_info "Waiting for deployments..."
kubectl rollout status deployment/sqlserver -n ${NAMESPACE} --timeout=120s || true
kubectl rollout status deployment/student-api -n ${NAMESPACE} --timeout=60s
kubectl rollout status deployment/student-frontend -n ${NAMESPACE} --timeout=60s
log_info "Deployment complete!"
status
}
status() {
log_info "Cluster status:"
echo ""
kubectl get all -n ${NAMESPACE}
echo ""
kubectl get ingress -n ${NAMESPACE}
echo ""
log_info "Pod status:"
kubectl get pods -n ${NAMESPACE} -o wide
}
logs() {
local component="${1:-api}"
case $component in
api)
kubectl logs -n ${NAMESPACE} -l app=student-api -f --tail=100
;;
frontend)
kubectl logs -n ${NAMESPACE} -l app=student-frontend -f --tail=100
;;
db|sqlserver)
kubectl logs -n ${NAMESPACE} -l app=sqlserver -f --tail=100
;;
*)
log_error "Unknown component: $component"
echo "Usage: $0 logs [api|frontend|db]"
;;
esac
}
delete() {
log_warn "Deleting deployment..."
read -p "Are you sure? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
kubectl delete -k . || true
kubectl delete namespace ${NAMESPACE} || true
log_info "Deployment deleted"
else
log_info "Cancelled"
fi
}
port_forward() {
log_info "Port forwarding..."
log_info "Frontend: http://localhost:8080"
log_info "API: http://localhost:5000/graphql"
kubectl port-forward -n ${NAMESPACE} svc/student-frontend 8080:80 &
kubectl port-forward -n ${NAMESPACE} svc/student-api 5000:5000 &
wait
}
case "${1:-help}" in
build)
build_images
;;
deploy)
deploy
;;
status)
status
;;
logs)
logs "$2"
;;
delete)
delete
;;
forward|port-forward)
port_forward
;;
all)
build_images
deploy
;;
*)
echo "Student Enrollment - k3s Deployment Script"
echo ""
echo "Usage: $0 <command>"
echo ""
echo "Commands:"
echo " build Build Docker images"
echo " deploy Deploy to k3s cluster"
echo " status Show deployment status"
echo " logs Show logs (api|frontend|db)"
echo " forward Port forward services"
echo " delete Delete deployment"
echo " all Build and deploy"
echo ""
echo "Environment variables:"
echo " REGISTRY Docker registry (default: localhost:5000)"
echo " VERSION Image version tag (default: latest)"
;;
esac

66
deploy/k3s/frontend.yaml Normal file
View File

@ -0,0 +1,66 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: student-frontend
namespace: student-enrollment
labels:
app: student-frontend
spec:
replicas: 2
selector:
matchLabels:
app: student-frontend
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: student-frontend
spec:
containers:
- name: frontend
image: docker.io/library/academia-frontend:v2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
runAsNonRoot: false
allowPrivilegeEscalation: false
---
apiVersion: v1
kind: Service
metadata:
name: student-frontend
namespace: student-enrollment
spec:
selector:
app: student-frontend
ports:
- port: 80
targetPort: 80
name: http
type: ClusterIP

61
deploy/k3s/hpa.yaml Normal file
View File

@ -0,0 +1,61 @@
---
# Horizontal Pod Autoscaler para API
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: student-api-hpa
namespace: student-enrollment
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: student-api
minReplicas: 2
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
---
# HPA para Frontend
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: student-frontend-hpa
namespace: student-enrollment
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: student-frontend
minReplicas: 2
maxReplicas: 4
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80

82
deploy/k3s/ingress.yaml Normal file
View File

@ -0,0 +1,82 @@
---
# Middleware para redirección HTTP → HTTPS
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: student-enrollment
spec:
redirectScheme:
permanent: true
scheme: https
---
# Middleware para security headers
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: security-headers
namespace: student-enrollment
spec:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
customResponseHeaders:
X-Robots-Tag: "noindex, nofollow"
---
# IngressRoute HTTP - Redirección a HTTPS
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: academia-http-redirect
namespace: student-enrollment
spec:
entryPoints:
- web
routes:
- kind: Rule
match: Host(`academia.ingeniumcodex.com`)
middlewares:
- name: redirect-https
namespace: student-enrollment
services:
- name: student-frontend
port: 80
---
# IngressRoute HTTPS - Principal con TLS
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: academia-https
namespace: student-enrollment
spec:
entryPoints:
- websecure
routes:
# API GraphQL
- kind: Rule
match: Host(`academia.ingeniumcodex.com`) && PathPrefix(`/graphql`)
middlewares:
- name: security-headers
namespace: student-enrollment
services:
- name: student-api
port: 5000
# Health check
- kind: Rule
match: Host(`academia.ingeniumcodex.com`) && PathPrefix(`/health`)
services:
- name: student-api
port: 5000
# Frontend (catch-all)
- kind: Rule
match: Host(`academia.ingeniumcodex.com`)
middlewares:
- name: security-headers
namespace: student-enrollment
services:
- name: student-frontend
port: 80
tls:
certResolver: letsencrypt

View File

@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: student-enrollment
resources:
- namespace.yaml
- secrets.yaml
- configmap.yaml
- sqlserver.yaml
- api.yaml
- frontend.yaml
- ingress.yaml
commonLabels:
app.kubernetes.io/name: student-enrollment
app.kubernetes.io/part-of: interrapidisimo
app.kubernetes.io/managed-by: kustomize
# Dominio: https://academia.ingeniumcodex.com
# TLS configurado con Traefik + Let's Encrypt

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: student-enrollment
labels:
app.kubernetes.io/name: student-enrollment
app.kubernetes.io/part-of: interrapidisimo

View File

@ -0,0 +1,83 @@
---
# Network Policy - Solo permitir tráfico necesario
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: student-enrollment
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Permitir tráfico al frontend desde ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-ingress
namespace: student-enrollment
spec:
podSelector:
matchLabels:
app: student-frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 80
---
# Permitir tráfico al API desde frontend e ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-ingress
namespace: student-enrollment
spec:
podSelector:
matchLabels:
app: student-api
policyTypes:
- Ingress
ingress:
# Desde ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 5000
# Desde frontend (para nginx proxy)
- from:
- podSelector:
matchLabels:
app: student-frontend
ports:
- protocol: TCP
port: 5000
---
# Permitir tráfico a SQL Server solo desde API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-sqlserver-from-api
namespace: student-enrollment
spec:
podSelector:
matchLabels:
app: sqlserver
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: student-api
ports:
- protocol: TCP
port: 1433

11
deploy/k3s/secrets.yaml Normal file
View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: student-secrets
namespace: student-enrollment
type: Opaque
stringData:
# IMPORTANTE: Cambiar en producción
# Generar con: openssl rand -base64 32
db-password: "Your_Str0ng_P@ssword!"
db-connection-string: "Server=sqlserver;Database=StudentEnrollment;User Id=sa;Password=Your_Str0ng_P@ssword!;TrustServerCertificate=True"

85
deploy/k3s/sqlserver.yaml Normal file
View File

@ -0,0 +1,85 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sqlserver-pvc
namespace: student-enrollment
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqlserver
namespace: student-enrollment
labels:
app: sqlserver
spec:
replicas: 1
selector:
matchLabels:
app: sqlserver
strategy:
type: Recreate
template:
metadata:
labels:
app: sqlserver
spec:
containers:
- name: sqlserver
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
name: sql
env:
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: student-secrets
key: db-password
volumeMounts:
- name: sqlserver-data
mountPath: /var/opt/mssql
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
tcpSocket:
port: 1433
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 1433
initialDelaySeconds: 30
periodSeconds: 5
volumes:
- name: sqlserver-data
persistentVolumeClaim:
claimName: sqlserver-pvc
---
apiVersion: v1
kind: Service
metadata:
name: sqlserver
namespace: student-enrollment
spec:
selector:
app: sqlserver
ports:
- port: 1433
targetPort: 1433
name: sql
type: ClusterIP

2
start.backend.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
cd src/backend && dotnet run --project Host

2
start.frontend.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
cd src/frontend && npx ng serve

2
start.webclient.sh Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
xdg-open http://localhost:4200