- CLAUDE.md for AI agents to understand the codebase - GITEA-GUIDE.md centralizes all Gitea operations (API, Registry, Auth) - DEVELOPMENT-WORKFLOW.md explains complete dev process - ROADMAP.md, NEXT-SESSION.md for planning - QUICK-REFERENCE.md, TROUBLESHOOTING.md for daily use - 40+ detailed docs in /docs folder - Backend as submodule from Gitea Everything documented for autonomous operation. Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
475 lines
9.8 KiB
Markdown
475 lines
9.8 KiB
Markdown
# Networking e Ingress
|
|
|
|
## Arquitectura de Red
|
|
|
|
```
|
|
Internet
|
|
│
|
|
▼
|
|
[LoadBalancer] (Cloud Provider)
|
|
│
|
|
▼
|
|
[Nginx Ingress Controller]
|
|
│
|
|
├──► api.aiworker.dev ──► Backend (control-plane)
|
|
├──► git.aiworker.dev ──► Gitea (gitea)
|
|
├──► app.aiworker.dev ──► Frontend (control-plane)
|
|
├──► *.preview.aiworker.dev ──► Preview Deployments
|
|
├──► staging-*.aiworker.dev ──► Staging Envs
|
|
└──► *.aiworker.dev ──► Production Apps
|
|
```
|
|
|
|
## Ingress Configuration
|
|
|
|
### Wildcard Certificate
|
|
|
|
```yaml
|
|
# k8s/ingress/wildcard-certificate.yaml
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: wildcard-aiworker
|
|
namespace: ingress-nginx
|
|
spec:
|
|
secretName: wildcard-aiworker-tls
|
|
issuerRef:
|
|
name: letsencrypt-prod
|
|
kind: ClusterIssuer
|
|
commonName: "*.aiworker.dev"
|
|
dnsNames:
|
|
- "aiworker.dev"
|
|
- "*.aiworker.dev"
|
|
- "*.preview.aiworker.dev"
|
|
```
|
|
|
|
### Backend Ingress
|
|
|
|
```yaml
|
|
# k8s/ingress/backend-ingress.yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: backend-ingress
|
|
namespace: control-plane
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
nginx.ingress.kubernetes.io/websocket-services: "aiworker-backend"
|
|
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
|
nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.aiworker.dev"
|
|
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
|
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
|
|
spec:
|
|
ingressClassName: nginx
|
|
tls:
|
|
- hosts:
|
|
- api.aiworker.dev
|
|
secretName: backend-tls
|
|
rules:
|
|
- host: api.aiworker.dev
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: aiworker-backend
|
|
port:
|
|
number: 3000
|
|
```
|
|
|
|
### Frontend Ingress
|
|
|
|
```yaml
|
|
# k8s/ingress/frontend-ingress.yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: frontend-ingress
|
|
namespace: control-plane
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
|
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
|
nginx.ingress.kubernetes.io/configuration-snippet: |
|
|
more_set_headers "X-Frame-Options: DENY";
|
|
more_set_headers "X-Content-Type-Options: nosniff";
|
|
more_set_headers "X-XSS-Protection: 1; mode=block";
|
|
spec:
|
|
ingressClassName: nginx
|
|
tls:
|
|
- hosts:
|
|
- app.aiworker.dev
|
|
secretName: frontend-tls
|
|
rules:
|
|
- host: app.aiworker.dev
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: aiworker-frontend
|
|
port:
|
|
number: 80
|
|
```
|
|
|
|
### Preview Deployments Ingress Template
|
|
|
|
```typescript
|
|
// services/kubernetes/ingress.ts
|
|
export function generatePreviewIngress(params: {
|
|
taskId: string
|
|
projectName: string
|
|
namespace: string
|
|
}) {
|
|
const shortId = params.taskId.slice(0, 8)
|
|
const host = `task-${shortId}.preview.aiworker.dev`
|
|
|
|
return {
|
|
apiVersion: 'networking.k8s.io/v1',
|
|
kind: 'Ingress',
|
|
metadata: {
|
|
name: `${params.projectName}-preview`,
|
|
namespace: params.namespace,
|
|
annotations: {
|
|
'cert-manager.io/cluster-issuer': 'letsencrypt-prod',
|
|
'nginx.ingress.kubernetes.io/ssl-redirect': 'true',
|
|
'nginx.ingress.kubernetes.io/auth-type': 'basic',
|
|
'nginx.ingress.kubernetes.io/auth-secret': 'preview-basic-auth',
|
|
'nginx.ingress.kubernetes.io/auth-realm': 'Preview Environment',
|
|
},
|
|
labels: {
|
|
environment: 'preview',
|
|
task: params.taskId,
|
|
project: params.projectName,
|
|
},
|
|
},
|
|
spec: {
|
|
ingressClassName: 'nginx',
|
|
tls: [
|
|
{
|
|
hosts: [host],
|
|
secretName: `${params.projectName}-preview-tls`,
|
|
},
|
|
],
|
|
rules: [
|
|
{
|
|
host,
|
|
http: {
|
|
paths: [
|
|
{
|
|
path: '/',
|
|
pathType: 'Prefix',
|
|
backend: {
|
|
service: {
|
|
name: `${params.projectName}-preview`,
|
|
port: {
|
|
number: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
}
|
|
}
|
|
```
|
|
|
|
## Service Mesh (Opcional)
|
|
|
|
Si necesitas más control sobre el tráfico, considera usar Istio o Linkerd:
|
|
|
|
### Istio Gateway
|
|
|
|
```yaml
|
|
apiVersion: networking.istio.io/v1beta1
|
|
kind: Gateway
|
|
metadata:
|
|
name: aiworker-gateway
|
|
namespace: istio-system
|
|
spec:
|
|
selector:
|
|
istio: ingressgateway
|
|
servers:
|
|
- port:
|
|
number: 443
|
|
name: https
|
|
protocol: HTTPS
|
|
tls:
|
|
mode: SIMPLE
|
|
credentialName: wildcard-aiworker-tls
|
|
hosts:
|
|
- "*.aiworker.dev"
|
|
---
|
|
apiVersion: networking.istio.io/v1beta1
|
|
kind: VirtualService
|
|
metadata:
|
|
name: backend-vs
|
|
namespace: control-plane
|
|
spec:
|
|
hosts:
|
|
- "api.aiworker.dev"
|
|
gateways:
|
|
- istio-system/aiworker-gateway
|
|
http:
|
|
- match:
|
|
- uri:
|
|
prefix: /api
|
|
route:
|
|
- destination:
|
|
host: aiworker-backend
|
|
port:
|
|
number: 3000
|
|
```
|
|
|
|
## DNS Configuration
|
|
|
|
### Cloudflare DNS Records
|
|
|
|
```bash
|
|
# A records
|
|
api.aiworker.dev A <loadbalancer-ip>
|
|
git.aiworker.dev A <loadbalancer-ip>
|
|
app.aiworker.dev A <loadbalancer-ip>
|
|
|
|
# Wildcard for preview and dynamic environments
|
|
*.preview.aiworker.dev A <loadbalancer-ip>
|
|
*.aiworker.dev A <loadbalancer-ip>
|
|
```
|
|
|
|
### External DNS (Automated)
|
|
|
|
```yaml
|
|
# k8s/external-dns/external-dns-deployment.yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: external-dns
|
|
namespace: kube-system
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: external-dns
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: external-dns
|
|
spec:
|
|
serviceAccountName: external-dns
|
|
containers:
|
|
- name: external-dns
|
|
image: registry.k8s.io/external-dns/external-dns:v0.14.0
|
|
args:
|
|
- --source=ingress
|
|
- --domain-filter=aiworker.dev
|
|
- --provider=cloudflare
|
|
env:
|
|
- name: CF_API_TOKEN
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: cloudflare-api-token
|
|
key: token
|
|
```
|
|
|
|
## Network Policies
|
|
|
|
### Isolate Preview Environments
|
|
|
|
```yaml
|
|
# k8s/network-policies/preview-isolation.yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: preview-isolation
|
|
namespace: agents
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
environment: preview
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress:
|
|
# Allow from ingress controller
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: ingress-nginx
|
|
# Allow from control-plane
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: control-plane
|
|
egress:
|
|
# Allow to gitea
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: gitea
|
|
# Allow to external HTTPS (npm, apt, etc)
|
|
- to:
|
|
- namespaceSelector: {}
|
|
ports:
|
|
- protocol: TCP
|
|
port: 443
|
|
# Allow DNS
|
|
- to:
|
|
- namespaceSelector: {}
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- protocol: UDP
|
|
port: 53
|
|
```
|
|
|
|
### Allow Backend to All
|
|
|
|
```yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: backend-egress
|
|
namespace: control-plane
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app: aiworker-backend
|
|
policyTypes:
|
|
- Egress
|
|
egress:
|
|
- {} # Allow all egress
|
|
```
|
|
|
|
## Load Balancing
|
|
|
|
### Session Affinity for WebSocket
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: aiworker-backend
|
|
namespace: control-plane
|
|
annotations:
|
|
service.beta.kubernetes.io/external-traffic: OnlyLocal
|
|
spec:
|
|
selector:
|
|
app: aiworker-backend
|
|
ports:
|
|
- name: http
|
|
port: 3000
|
|
targetPort: 3000
|
|
sessionAffinity: ClientIP
|
|
sessionAffinityConfig:
|
|
clientIP:
|
|
timeoutSeconds: 3600
|
|
type: ClusterIP
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
```yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: backend-ingress
|
|
namespace: control-plane
|
|
annotations:
|
|
nginx.ingress.kubernetes.io/rate-limit: "100"
|
|
nginx.ingress.kubernetes.io/rate-limit-burst: "200"
|
|
nginx.ingress.kubernetes.io/rate-limit-key: "$binary_remote_addr"
|
|
spec:
|
|
# ... spec
|
|
```
|
|
|
|
## Health Checks
|
|
|
|
### Liveness and Readiness Probes
|
|
|
|
```yaml
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /api/health
|
|
port: 3000
|
|
httpHeaders:
|
|
- name: X-Health-Check
|
|
value: liveness
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
timeoutSeconds: 5
|
|
failureThreshold: 3
|
|
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /api/health/ready
|
|
port: 3000
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
failureThreshold: 3
|
|
```
|
|
|
|
### Health Endpoint Implementation
|
|
|
|
```typescript
|
|
// api/routes/health.ts
|
|
import { Router } from 'express'
|
|
import { getDatabase } from '../../config/database'
|
|
import { getRedis } from '../../config/redis'
|
|
|
|
const router = Router()
|
|
|
|
router.get('/health', async (req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
})
|
|
|
|
router.get('/health/ready', async (req, res) => {
|
|
try {
|
|
// Check DB
|
|
const db = getDatabase()
|
|
await db.execute('SELECT 1')
|
|
|
|
// Check Redis
|
|
const redis = getRedis()
|
|
await redis.ping()
|
|
|
|
res.json({
|
|
status: 'ready',
|
|
services: {
|
|
database: 'connected',
|
|
redis: 'connected',
|
|
},
|
|
})
|
|
} catch (error) {
|
|
res.status(503).json({
|
|
status: 'not ready',
|
|
error: error.message,
|
|
})
|
|
}
|
|
})
|
|
|
|
export default router
|
|
```
|
|
|
|
## Monitoring Traffic
|
|
|
|
```bash
|
|
# Ver logs de Nginx Ingress
|
|
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller --tail=100 -f
|
|
|
|
# Ver métricas
|
|
kubectl top pods -n ingress-nginx
|
|
|
|
# Ver configuración generada
|
|
kubectl exec -n ingress-nginx <pod> -- cat /etc/nginx/nginx.conf
|
|
```
|