- 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>
482 lines
9.3 KiB
Markdown
482 lines
9.3 KiB
Markdown
# Estructura de Namespaces
|
|
|
|
## Arquitectura de Namespaces
|
|
|
|
```
|
|
aiworker-cluster/
|
|
├── control-plane/ # Backend, API, MCP Server
|
|
├── agents/ # Claude Code agent pods
|
|
├── gitea/ # Gitea server
|
|
├── projects/
|
|
│ └── <project-name>/
|
|
│ ├── dev/ # Desarrollo continuo
|
|
│ ├── preview-*/ # Preview deployments por tarea
|
|
│ ├── staging/ # Staging environment
|
|
│ └── production/ # Production environment
|
|
└── monitoring/ # Prometheus, Grafana
|
|
```
|
|
|
|
## Namespace: control-plane
|
|
|
|
**Propósito**: Backend API, MCP Server, servicios core
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: control-plane
|
|
labels:
|
|
name: control-plane
|
|
environment: production
|
|
managed-by: aiworker
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: control-plane-quota
|
|
namespace: control-plane
|
|
spec:
|
|
hard:
|
|
requests.cpu: "4"
|
|
requests.memory: 8Gi
|
|
limits.cpu: "8"
|
|
limits.memory: 16Gi
|
|
persistentvolumeclaims: "5"
|
|
---
|
|
apiVersion: v1
|
|
kind: LimitRange
|
|
metadata:
|
|
name: control-plane-limits
|
|
namespace: control-plane
|
|
spec:
|
|
limits:
|
|
- max:
|
|
cpu: "2"
|
|
memory: 4Gi
|
|
min:
|
|
cpu: "100m"
|
|
memory: 128Mi
|
|
default:
|
|
cpu: "500m"
|
|
memory: 512Mi
|
|
defaultRequest:
|
|
cpu: "250m"
|
|
memory: 256Mi
|
|
type: Container
|
|
```
|
|
|
|
### Servicios en control-plane
|
|
|
|
- **Backend API**: Express + Bun
|
|
- **MCP Server**: Comunicación con agentes
|
|
- **MySQL**: Base de datos
|
|
- **Redis**: Cache y colas
|
|
- **BullMQ Workers**: Procesamiento de jobs
|
|
|
|
## Namespace: agents
|
|
|
|
**Propósito**: Pods de Claude Code agents
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: agents
|
|
labels:
|
|
name: agents
|
|
environment: production
|
|
managed-by: aiworker
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: agents-quota
|
|
namespace: agents
|
|
spec:
|
|
hard:
|
|
requests.cpu: "20"
|
|
requests.memory: 40Gi
|
|
limits.cpu: "40"
|
|
limits.memory: 80Gi
|
|
pods: "50"
|
|
---
|
|
apiVersion: v1
|
|
kind: LimitRange
|
|
metadata:
|
|
name: agents-limits
|
|
namespace: agents
|
|
spec:
|
|
limits:
|
|
- max:
|
|
cpu: "2"
|
|
memory: 4Gi
|
|
min:
|
|
cpu: "500m"
|
|
memory: 1Gi
|
|
default:
|
|
cpu: "1"
|
|
memory: 2Gi
|
|
defaultRequest:
|
|
cpu: "500m"
|
|
memory: 1Gi
|
|
type: Container
|
|
```
|
|
|
|
### Network Policy para Agents
|
|
|
|
```yaml
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: agents-network-policy
|
|
namespace: agents
|
|
spec:
|
|
podSelector: {}
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress:
|
|
# Permitir tráfico desde control-plane
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: control-plane
|
|
egress:
|
|
# Permitir salida a control-plane (MCP Server)
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: control-plane
|
|
# Permitir salida a gitea
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
name: gitea
|
|
# Permitir DNS
|
|
- to:
|
|
- namespaceSelector: {}
|
|
podSelector:
|
|
matchLabels:
|
|
k8s-app: kube-dns
|
|
ports:
|
|
- protocol: UDP
|
|
port: 53
|
|
# Permitir HTTPS externo (para Claude API)
|
|
- to:
|
|
- namespaceSelector: {}
|
|
ports:
|
|
- protocol: TCP
|
|
port: 443
|
|
```
|
|
|
|
## Namespace: gitea
|
|
|
|
**Propósito**: Servidor Git auto-alojado
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: gitea
|
|
labels:
|
|
name: gitea
|
|
environment: production
|
|
managed-by: aiworker
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: gitea-quota
|
|
namespace: gitea
|
|
spec:
|
|
hard:
|
|
requests.cpu: "2"
|
|
requests.memory: 4Gi
|
|
limits.cpu: "4"
|
|
limits.memory: 8Gi
|
|
persistentvolumeclaims: "2"
|
|
```
|
|
|
|
## Namespaces por Proyecto
|
|
|
|
### Estructura Dinámica
|
|
|
|
Para cada proyecto creado, se generan automáticamente 4 namespaces:
|
|
|
|
```typescript
|
|
// services/kubernetes/namespaces.ts
|
|
export async function createProjectNamespaces(projectName: string) {
|
|
const baseName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
|
|
const namespaces = [
|
|
`${baseName}-dev`,
|
|
`${baseName}-staging`,
|
|
`${baseName}-production`,
|
|
]
|
|
|
|
for (const ns of namespaces) {
|
|
await k8sClient.createNamespace({
|
|
name: ns,
|
|
labels: {
|
|
project: baseName,
|
|
'managed-by': 'aiworker',
|
|
},
|
|
})
|
|
|
|
// Aplicar resource quotas
|
|
await k8sClient.applyResourceQuota(ns, {
|
|
requests: { cpu: '2', memory: '4Gi' },
|
|
limits: { cpu: '4', memory: '8Gi' },
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
### Namespace: project-dev
|
|
|
|
**Propósito**: Desarrollo continuo, deploy automático de main/develop
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: my-project-dev
|
|
labels:
|
|
project: my-project
|
|
environment: dev
|
|
managed-by: aiworker
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: dev-quota
|
|
namespace: my-project-dev
|
|
spec:
|
|
hard:
|
|
requests.cpu: "1"
|
|
requests.memory: 2Gi
|
|
limits.cpu: "2"
|
|
limits.memory: 4Gi
|
|
pods: "5"
|
|
```
|
|
|
|
### Namespace: preview-task-{id}
|
|
|
|
**Propósito**: Preview deployment temporal para una tarea específica
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: preview-task-abc123
|
|
labels:
|
|
project: my-project
|
|
environment: preview
|
|
task-id: abc123
|
|
managed-by: aiworker
|
|
ttl: "168h" # 7 days
|
|
annotations:
|
|
created-at: "2026-01-19T12:00:00Z"
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: preview-quota
|
|
namespace: preview-task-abc123
|
|
spec:
|
|
hard:
|
|
requests.cpu: "500m"
|
|
requests.memory: 1Gi
|
|
limits.cpu: "1"
|
|
limits.memory: 2Gi
|
|
pods: "3"
|
|
```
|
|
|
|
**Limpieza automática**:
|
|
```typescript
|
|
// Cleanup job que corre diariamente
|
|
export async function cleanupOldPreviewNamespaces() {
|
|
const allNamespaces = await k8sClient.listNamespaces()
|
|
|
|
for (const ns of allNamespaces) {
|
|
if (ns.metadata?.labels?.environment === 'preview') {
|
|
const createdAt = new Date(ns.metadata.annotations?.['created-at'])
|
|
const ageHours = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60)
|
|
|
|
if (ageHours > 168) { // 7 days
|
|
await k8sClient.deleteNamespace(ns.metadata.name)
|
|
logger.info(`Deleted old preview namespace: ${ns.metadata.name}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Namespace: project-staging
|
|
|
|
**Propósito**: Staging environment, testing antes de producción
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: my-project-staging
|
|
labels:
|
|
project: my-project
|
|
environment: staging
|
|
managed-by: aiworker
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: staging-quota
|
|
namespace: my-project-staging
|
|
spec:
|
|
hard:
|
|
requests.cpu: "2"
|
|
requests.memory: 4Gi
|
|
limits.cpu: "4"
|
|
limits.memory: 8Gi
|
|
pods: "10"
|
|
```
|
|
|
|
### Namespace: project-production
|
|
|
|
**Propósito**: Production environment
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: my-project-production
|
|
labels:
|
|
project: my-project
|
|
environment: production
|
|
managed-by: aiworker
|
|
protected: "true"
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: production-quota
|
|
namespace: my-project-production
|
|
spec:
|
|
hard:
|
|
requests.cpu: "4"
|
|
requests.memory: 8Gi
|
|
limits.cpu: "8"
|
|
limits.memory: 16Gi
|
|
pods: "20"
|
|
---
|
|
# Pod Disruption Budget para alta disponibilidad
|
|
apiVersion: policy/v1
|
|
kind: PodDisruptionBudget
|
|
metadata:
|
|
name: app-pdb
|
|
namespace: my-project-production
|
|
spec:
|
|
minAvailable: 1
|
|
selector:
|
|
matchLabels:
|
|
app: my-project
|
|
```
|
|
|
|
## Namespace: monitoring
|
|
|
|
**Propósito**: Prometheus, Grafana, logs
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: monitoring
|
|
labels:
|
|
name: monitoring
|
|
environment: production
|
|
---
|
|
apiVersion: v1
|
|
kind: ResourceQuota
|
|
metadata:
|
|
name: monitoring-quota
|
|
namespace: monitoring
|
|
spec:
|
|
hard:
|
|
requests.cpu: "4"
|
|
requests.memory: 8Gi
|
|
limits.cpu: "8"
|
|
limits.memory: 16Gi
|
|
persistentvolumeclaims: "10"
|
|
```
|
|
|
|
## Gestión de Namespaces desde el Backend
|
|
|
|
```typescript
|
|
// services/kubernetes/namespaces.ts
|
|
import { KubeConfig, CoreV1Api } from '@kubernetes/client-node'
|
|
|
|
export class NamespaceManager {
|
|
private k8sApi: CoreV1Api
|
|
|
|
constructor() {
|
|
const kc = new KubeConfig()
|
|
kc.loadFromDefault()
|
|
this.k8sApi = kc.makeApiClient(CoreV1Api)
|
|
}
|
|
|
|
async createNamespace(name: string, labels: Record<string, string> = {}) {
|
|
await this.k8sApi.createNamespace({
|
|
metadata: {
|
|
name,
|
|
labels: {
|
|
'managed-by': 'aiworker',
|
|
...labels,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
async deleteNamespace(name: string) {
|
|
await this.k8sApi.deleteNamespace(name)
|
|
}
|
|
|
|
async listNamespaces(labelSelector?: string) {
|
|
const response = await this.k8sApi.listNamespace(undefined, undefined, undefined, undefined, labelSelector)
|
|
return response.body.items
|
|
}
|
|
|
|
async namespaceExists(name: string): Promise<boolean> {
|
|
try {
|
|
await this.k8sApi.readNamespace(name)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dashboard de Namespaces
|
|
|
|
En el frontend, mostrar todos los namespaces con sus recursos:
|
|
|
|
```typescript
|
|
// hooks/useNamespaces.ts
|
|
export function useNamespaces(projectId?: string) {
|
|
return useQuery({
|
|
queryKey: ['namespaces', projectId],
|
|
queryFn: async () => {
|
|
const { data } = await api.get('/namespaces', {
|
|
params: { projectId },
|
|
})
|
|
return data.namespaces
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
Vista en el dashboard:
|
|
- **Mapa de namespaces** por proyecto
|
|
- **Uso de recursos** (CPU, memoria) por namespace
|
|
- **Número de pods** activos
|
|
- **Botón de cleanup** para preview namespaces antiguos
|