TLSStress.Art — Despliegue Multi-Nodo (4 servidores UCS dedicados)¶
Última verificación contra el código de producción: v3.7.0 (2026-05-12) — Ver ARCHITECTURE.md para los 37 MÓDULOs canónicos + 7 Test Kinds + arquitectura de safety DOM/CPOS/PIE-PA + la postura ZTP-prem 12/12 capas contra operador insider (25 reivindicaciones de patente, partición Tier A/B, Confidential Computing, audit log sellado en hash-chain, webhook de admission K8s, TPM 2.0 measured-boot, monitor DLP de egress, detector de anomalías comportamental). ADRs 0014, 0019-0025 cubren adiciones post-Freeze.
Autor: André Luiz Gallon | Versión: v3.7.0 | Uso interno
Contexto: esta guía cubre el escenario donde cada capa de la plataforma corre en un servidor físico dedicado (UCS). Para el despliegue single-node, consulte UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.es.md.
Instalación automatizada (recomendada)¶
Use el script de instalación automatizada para configurar cada servidor. El script instala k3s, configura las VLANs e instala las dependencias de Kubernetes — sin necesidad de experiencia previa en Kubernetes.
Ejecute una vez por servidor, en el orden indicado:
# Clonar el repositorio en cada servidor
git clone https://github.com/nollagluiz/AI_forSE.git && cd AI_forSE
# UCS-1 — servidor k3s + webservers persona (VLANs 101–120)
sudo ./scripts/k8s-install.sh --mode=multi-server --role=ngfw-dut --data-iface=eth1
# ↑ Imprime JOIN_TOKEN y SERVER_IP al final — guárdelos para los pasos siguientes.
# UCS-2 — agentes browser-engine (VLAN 20)
sudo ./scripts/k8s-install.sh --mode=multi-agent --role=playwright \
--server-ip=<IP-UCS-1> --token=<JOIN_TOKEN> --data-iface=eth1
# UCS-3 — agentes synthetic-load (VLAN 30)
sudo ./scripts/k8s-install.sh --mode=multi-agent --role=k6 \
--server-ip=<IP-UCS-1> --token=<JOIN_TOKEN> --data-iface=eth1
# UCS-4 — Dashboard, Postgres, Grafana (solo eth0, sin interfaz de datos)
sudo ./scripts/k8s-install.sh --mode=multi-agent --role=infra \
--server-ip=<IP-UCS-1> --token=<JOIN_TOKEN>
# De vuelta en UCS-1 — aplicar todos los manifests de Kubernetes
sudo ./scripts/k8s-install.sh --mode=multi-apply
Dry run: añada
--dry-runa cualquier comando para visualizar lo que se ejecutaría sin realizar cambios en el sistema.
El resto de esta guía explica cada paso en detalle, útil para personalizar la instalación o depurar problemas.
Visión general de la arquitectura¶
┌──────── OOBI (eth0) — 10.0.0.0/24 ────────────────────┐
│ K8s control plane · kubelet · Prometheus · DNS │
│ │
┌────────┴────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ UCS-1 │ │ UCS-2 │ │ UCS-3 │ │ UCS-4 │
│ role=ngfw-dut │ │ role=playwright │ │ role=k6 │ │ role=infra │
│ │ │ │ │ │ │ │
│ 20 Personas │ │ Agentes browser-engine│ │ Agentes synthetic-load │ │ Dashboard │
│ (Caddy HTTPS) │ │ (1–300 instancias)│ │ (1–1000 instancias)│ │ Postgres │
│ │ │ │ │ │ │ Grafana │
│ eth1 → VLANs │ │ eth1 → VLAN 20 │ │ eth1 → VLAN 30 │ │ cert-manager │
│ 101–120 │ │ 172.16.0.0/16 │ │ 172.17.0.0/16 │ │ (eth0 solo) │
└────────┬────────┘ └────────┬─────────┘ └────────┬─────────┘ └──────────────────┘
│ │ │
└────────────────────┴──────────────────────┘
│
┌────────┴────────┐
│ NGFW (DUT) │
│ Inspecciona TLS│
│ en todas VLANs │
└────────┬────────┘
│
┌────────┴────────┐
│ Nexus 9000 │
│ Trunk 802.1q │
└─────────────────┘
Mapa de nodos¶
| Servidor | Label K8s | Workloads | NICs requeridas | VLANs de datos |
|---|---|---|---|---|
| UCS-1 | role=ngfw-dut + dut-data-plane=true |
20 Synthetic + 10 Cloned slots (Caddy) | eth0 (OOBI) + eth1 (trunk) | VLANs 101–120 (10.1.x.0/27) |
| UCS-2 | role=playwright + dut-data-plane=true |
browser-engine agents (Deployment) | eth0 (OOBI) + eth1 (trunk) | VLAN 20 (172.16.0.0/16) |
| UCS-3 | role=k6 + dut-data-plane=true |
synthetic-load agents (Deployment) | eth0 (OOBI) + eth1 (trunk) | VLAN 30 (172.17.0.0/16) |
| UCS-4 | role=infra |
Dashboard, Postgres, PgBouncer, Grafana, cert-manager | eth0 solo | Ninguna |
La red OOBI (Out-Of-Band Infrastructure)¶
Qué es¶
OOBI es el nombre del plano de administración/control de la plataforma. En la práctica, es la eth0 en los cuatro servidores — la interfaz de red estándar del sistema operativo Ubuntu.
Qué circula por la OOBI¶
| Protocolo / Servicio | Puerto | Dirección |
|---|---|---|
| k3s API Server (kubectl) | 6443/TCP | UCS-2/3/4 → UCS-1 |
| kubelet (health/exec/logs) | 10250/TCP | Bidireccional |
| Flannel overlay (VXLAN) | 8472/UDP | Bidireccional |
| Prometheus scrape (métricas Caddy) | 9091/TCP | UCS-4 → UCS-1/2/3 |
| Dashboard → Postgres | 5432/TCP | UCS-4 interno |
| cert-manager → API Server | 6443/TCP | UCS-4 → UCS-1 |
| CoreDNS | 53/UDP | Intra-cluster |
Qué NO circula por la OOBI¶
El tráfico HTTPS de prueba entre agentes, NGFW y webservers no pasa por la OOBI. Ese tráfico usa la eth1 (VLAN trunk Nexus 9000) — completamente separado del plano K8s.
Requisitos de red OOBI¶
Los cuatro servidores UCS deben estar en el mismo segmento L2 (o L3 con enrutamiento adecuado) por eth0:
UCS-1 eth0: 10.0.0.11/24 (k3s server — API server)
UCS-2 eth0: 10.0.0.12/24 (k3s agent)
UCS-3 eth0: 10.0.0.13/24 (k3s agent)
UCS-4 eth0: 10.0.0.14/24 (k3s agent)
Gateway: 10.0.0.1/24
Atención: la subnet OOBI es solo para administración. No use el mismo rango de IPs de la OOBI para las VLANs de datos (eth1).
Parte 1 — Prerrequisitos¶
1.1 Sistema operativo¶
Ubuntu Server 22.04 LTS en los cuatro servidores. Cada servidor debe tener: - Conectividad OOBI (eth0) con los demás - Acceso SSH desde la estación de trabajo del operador - curl y git instalados
1.2 Verificar conectividad OOBI¶
Desde UCS-1 (que será el server k3s), probar alcance a los demás:
# En UCS-1
ping -c2 10.0.0.12 # UCS-2
ping -c2 10.0.0.13 # UCS-3
ping -c2 10.0.0.14 # UCS-4
1.3 Sincronizar reloj (NTP)¶
# En todos los servidores
sudo timedatectl set-ntp true
timedatectl status # confirmar "synchronized: yes"
Parte 2 — Instalar k3s en modo multi-nodo¶
2.1 UCS-1 — servidor k3s (control plane)¶
# Instalar k3s server con flannel en eth0 y labels del nodo
curl -sfL https://get.k3s.io | sh -s - server \
--flannel-iface=eth0 \
--node-label=role=ngfw-dut \
--node-label=dut-data-plane=true \
--write-kubeconfig-mode 644
# Esperar que el nodo quede Ready
sudo kubectl get nodes -w
Obtener el token de join:
sudo cat /var/lib/rancher/k3s/server/node-token
# Guardar el valor — será usado por los agentes k3s
2.2 UCS-2 — agente browser engine¶
export K3S_URL="https://10.0.0.11:6443"
export K3S_TOKEN="SU_TOKEN_AQUI"
curl -sfL https://get.k3s.io | sh -s - agent \
--server "$K3S_URL" \
--token "$K3S_TOKEN" \
--flannel-iface=eth0 \
--node-label=role=playwright \
--node-label=dut-data-plane=true
2.3 UCS-3 — agente synthetic-load engine¶
export K3S_URL="https://10.0.0.11:6443"
export K3S_TOKEN="SU_TOKEN_AQUI"
curl -sfL https://get.k3s.io | sh -s - agent \
--server "$K3S_URL" \
--token "$K3S_TOKEN" \
--flannel-iface=eth0 \
--node-label=role=k6 \
--node-label=dut-data-plane=true
2.4 UCS-4 — agente Infra¶
export K3S_URL="https://10.0.0.11:6443"
export K3S_TOKEN="SU_TOKEN_AQUI"
curl -sfL https://get.k3s.io | sh -s - agent \
--server "$K3S_URL" \
--token "$K3S_TOKEN" \
--flannel-iface=eth0 \
--node-label=role=infra
UCS-4 no recibe
dut-data-plane=trueya que no se conecta al NGFW.
2.5 Verificar todos los nodos¶
# En UCS-1
kubectl get nodes -o wide
Salida esperada:
NAME STATUS ROLES AGE VERSION INTERNAL-IP
ucs-1 Ready control-plane,master 5m v1.29.x+k3s1 10.0.0.11
ucs-2 Ready <none> 3m v1.29.x+k3s1 10.0.0.12
ucs-3 Ready <none> 2m v1.29.x+k3s1 10.0.0.13
ucs-4 Ready <none> 1m v1.29.x+k3s1 10.0.0.14
Verificar los labels:
kubectl get nodes --show-labels | grep -E "role=|dut-data"
Parte 3 — Configurar VLANs en las interfaces de datos (eth1)¶
El tráfico de prueba usa eth1 con 802.1q. Cada servidor necesita las subinterfaces correspondientes a su rol.
3.1 UCS-1 — VLANs de personas (101–120)¶
# Habilitar 802.1q
sudo modprobe 8021q
echo "8021q" | sudo tee -a /etc/modules
# Configurar las 20 sub-interfaces
for vlan in $(seq 101 120); do
i=$((vlan - 100))
sudo ip link add link eth1 name "eth1.${vlan}" type vlan id "${vlan}"
sudo ip addr add "10.1.${i}.1/27" dev "eth1.${vlan}"
sudo ip link set "eth1.${vlan}" up
done
3.2 UCS-2 — VLAN 20 (browser engine)¶
sudo modprobe 8021q
sudo ip link add link eth1 name eth1.20 type vlan id 20
sudo ip addr add 172.16.0.1/16 dev eth1.20
sudo ip link set eth1.20 up
3.3 UCS-3 — VLAN 30 (synthetic-load engine)¶
sudo modprobe 8021q
sudo ip link add link eth1 name eth1.30 type vlan id 30
sudo ip addr add 172.17.0.1/16 dev eth1.30
sudo ip link set eth1.30 up
3.4 UCS-4 — Sin VLANs de datos¶
UCS-4 (infra) no tiene eth1 de datos. Solo se necesita eth0 (OOBI).
Parte 4 — Instalar prerrequisitos del clúster¶
4.1 Multus CNI¶
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml
kubectl -n kube-system rollout status daemonset/kube-multus-ds
4.2 cert-manager¶
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl -n cert-manager rollout status deployment/cert-manager
kubectl -n cert-manager rollout status deployment/cert-manager-webhook
4.3 Stack de Prometheus (Grafana + AlertManager)¶
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set grafana.adminPassword=admin \
--wait
Parte 5 — Clonar el repositorio¶
git clone https://github.com/nollagluiz/AI_forSE.git
cd AI_forSE
Parte 6 — Despliegue con overlay Kustomize multi-nodo¶
El overlay k8s/overlays/multi-node/ añade nodeSelector a cada Deployment/StatefulSet, fijando cada workload en el servidor correcto.
k8s/overlays/multi-node/
├── kustomization.yaml # overlay principal
├── patch-playwright-nodesel.yaml # web-agent → role=playwright (UCS-2)
├── patch-k6-nodesel.yaml # k6-agent → role=k6 (UCS-3)
└── patch-infra-nodesel.yaml # dashboard/postgres/pgbouncer → role=infra (UCS-4)
6.1 Aplicar el overlay¶
kubectl apply -k k8s/overlays/multi-node/
Verificar que los pods fueron programados en los nodos correctos:
kubectl get pods -n web-agents -o wide
Salida esperada:
NAME NODE ...
web-agent-xxx ucs-2 (role=playwright)
k6-agent-xxx ucs-3 (role=k6)
dashboard-xxx ucs-4 (role=infra)
postgres-0 ucs-4 (role=infra)
pgbouncer-xxx ucs-4 (role=infra)
Parte 7 — Despliegue del overlay DUT (personas + PKI + NGFW)¶
# 4 fases automáticas
bash scripts/k8s-dut-up.sh up
| Fase | Qué hace |
|---|---|
| 1 — Base | namespace, quotas, ConfigMaps, Deployments (usan nodeSelector del overlay) |
| 2 — DUT overlay | PSA labels, Stakater Reloader, NetworkPolicy DUT, patch macvlan en agentes |
| 3 — Platform | CoreDNS personas, cert-manager PKI, SNMP exporter, Prometheus rules |
| 4 — Personas | 20 namespaces de personas con webservers Caddy |
Parte 8 — Tuning del host (OBLIGATORIO para personas)¶
Las stacks de Synthetic Personas y Cloned Personas requieren este tuning para entregar el throughput de prueba objetivo. Sin él, los buffers UDP del kernel limitan QUIC a ~30 Mbps por réplica, y cwnd del TCP se resetea en cada ventana idle de HTTP/2 (~1 ms RTT extra por cycle).
El DaemonSet node-tuning aplica los mismos sysctls en runtime, pero no sobrevive un reboot hasta que el pod vuelva a subir. El script scripts/host-tuning.sh graba los valores en /etc/sysctl.d/, instala una unit systemd para el CPU governor y (opcionalmente) activa cpuManagerPolicy: static para asignación exclusiva de cores — convirtiendo al DaemonSet en una red de seguridad y no la única línea de defensa tras reboot.
Ejecutar en CADA host UCS que escala una Synthetic Persona, slot Cloned-Persona o el Cloner. En despliegue de 4 nodos: UCS-1 (personas + slots) y UCS-4 (Cloner + NFS server):
# UCS-1 (role=ngfw-dut) — 20 personas + 10 slots cloned-persona
sudo scripts/host-tuning.sh apply --enable-cpu-pinning
# UCS-4 (role=infra) — Cloner + NFS server
sudo scripts/host-tuning.sh apply --enable-cpu-pinning
# Verificar
sudo scripts/host-tuning.sh status
--enable-cpu-pinning activa cpuManagerPolicy: static en el kubelet (k3s y vanilla auto-detectados) y reinicia el kubelet — planee una ventana. Sin la flag el script aún aplica sysctls, módulos, governor y THP — el pinning es el único paso que toca workloads en ejecución.
UCS-2 (browser engine) y UCS-3 (synthetic-load engine) corren solo pods de agente. Se benefician de los sysctls pero no necesitan pinning. Ejecutar el script ahí también, sin --enable-cpu-pinning:
sudo scripts/host-tuning.sh apply # solo sysctls + módulos + governor
8.1 Expandir DaemonSet node-tuning a los nodos data-plane¶
El DaemonSet node-tuning usa por defecto role=ngfw-dut (solo UCS-1). En multi-nodo, los agentes en UCS-2 y UCS-3 también necesitan tuning de kernel en runtime:
kubectl patch daemonset node-tuning -n web-agents \
--type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"dut-data-plane":"true"}}}}}'
Verificar que el DaemonSet corre en UCS-1, 2 y 3 (pero no UCS-4):
kubectl get pods -n web-agents -l app=node-tuning -o wide
Parte 9 — Verificación completa¶
9.1 Pods por nodo¶
kubectl get pods -A -o wide | awk 'NR==1 || /web-agents|persona/'
9.2 Agentes alcanzan los sitios vía NGFW¶
kubectl exec -n web-agents deploy/web-agent -- \
curl -sk https://shop.persona.internal | head -5
9.3 Métricas disponibles en Prometheus¶
kubectl port-forward svc/kube-prometheus-stack-prometheus -n monitoring 9090:9090 &
# http://localhost:9090 — buscar: caddy_http_requests_total
9.4 Grafana¶
kubectl port-forward svc/kube-prometheus-stack-grafana -n monitoring 3000:80 &
# http://localhost:3000 admin/admin
Parte 10 — StorageClass para Postgres en multi-nodo¶
En el escenario multi-nodo, el PVC de Postgres sigue siendo provisionado en UCS-4 gracias al nodeSelector: role=infra. El provisionador local-path funciona correctamente cuando el pod está fijado al nodo via nodeSelector.
No se requiere Longhorn u otro provisionador distribuido.
kubectl get pvc -n web-agents
# STORAGECLASS debe ser "local-path"
# postgres-0 debe estar en ucs-4
Troubleshooting¶
Pod no programado — evento "0/4 nodes are available"¶
kubectl describe pod <nombre-del-pod> -n web-agents
# Ver sección "Events" — "didn't match nodeSelector"
# Verificar labels de los nodos
kubectl get nodes --show-labels | grep role
Personas no responden vía NGFW¶
- Verificar sub-interfaces VLAN en UCS-1:
ip link show | grep eth1 - Verificar que el NGFW tiene ruta de retorno para VLANs 101–120.
- Verificar NetworkPolicy:
kubectl get networkpolicy -A
node-tuning no corre en UCS-2 o UCS-3¶
kubectl get ds node-tuning -n web-agents -o jsonpath='{.spec.template.spec.nodeSelector}'
# Debe retornar: {"dut-data-plane":"true"}
# Si retorna {"role":"ngfw-dut"}, reaplicar el patch de la Parte 8
Referencia rápida¶
# Verificar nodos y labels
kubectl get nodes --show-labels
# Distribución de pods por nodo
kubectl get pods -n web-agents -o wide
# Escalar agentes browser-engine (ej: 50 réplicas)
kubectl scale deployment web-agent -n web-agents --replicas=50
# Escalar agentes k6 (ej: 200 réplicas)
kubectl scale deployment k6-agent -n web-agents --replicas=200
# Verificar DaemonSet de tuning
kubectl get pods -n web-agents -l app=node-tuning -o wide
# Estado general
bash scripts/k8s-dut-up.sh status
Cloner de sitios públicos + almacenamiento cross-node (multi-node)¶
En un cluster multi-node de 4 servidores el cloner se ejecuta en UCS-4 (role=infra), co-localizado con el Dashboard. Los 10 slots de cloned-persona se ejecutan en UCS-1 (role=ngfw-dut), junto a las personas sintéticas. Esos dos niveles viven en máquinas físicas diferentes, así que el contenido clonado debe atravesar una red real entre el escritor y los lectores.
Arquitectura de almacenamiento (lea esto antes de instalar multi-node)¶
El volumen compartido cloned-sites está respaldado por un servidor NFS in-cluster (k8s/dut/35-nfs-server.yaml) programado en role=infra (UCS-4) — el mismo nodo que el cloner. Once pares estáticos PV/PVC (k8s/dut/36-cloned-sites-pvs.yaml) — un escritor read-write en web-agents, diez lectores read-only en clone-persona-1..10 — apuntan todos al mismo export NFS.
UCS-4 (role=infra): [pod cloner] [pod nfs-server]
│ │
│ escribe │ /var/lib/agent-cluster/cloned-sites
│ vía PVC │ (hostPath, persistente)
▼ ▼
NFSv4 sobre OOBI (eth0, ClusterIP Service:2049)
▲
│ lectura vía PVC read-only
│
UCS-1 (role=ngfw-dut): [clone-persona-1..10]
Puntos críticos:
- Todo el tráfico NFS usa el plano de control OOBI (eth0 / red de pods → ClusterIP
nfs-server.web-agents.svc.cluster.local). El plano de datos (net1macvlan VLAN 40 ISP y VLANs 200–209 de las subnets de slots) nunca transporta I/O de almacenamiento — queda exclusivo para tráfico agente ↔ NGFW ↔ persona, manteniendo las mediciones de timing limpias. - El disco subyacente del servidor NFS es el hostPath en UCS-4. La capacidad depende del disco de UCS-4, no de lo que pidan los PVCs (50 GiB por convención).
- El
nodeAffinitydel cloner (role=infra) y elpreferredAffinitydel servidor NFS (role=infra) DEBEN coincidir. Si mueve el cloner a otro nodo, mueva también el servidor NFS. - El DaemonSet CNI DHCP (
k8s/dut/30-cni-dhcp-daemon.yaml) corre en todos los nodos — necesario en UCS-4 (donde está el cloner) para que el IPAM de VLAN 40 invoque el helper dhcp vía/run/cni/dhcp.sock.
Objetivo¶
El cloner descarga sitios públicos reales vía VLAN 40 (acceso directo a internet, sin NGFW en la ruta) y escribe en NFS. Los 10 pods de slots en UCS-1 montan el mismo export NFS de solo lectura y sirven el contenido vía Caddy. La flota de agentes accede a esos slots a través del NGFW, ejercitando inspección TLS real con contenido de sitios conocidos.
Verificación rápida¶
# Cloner en UCS-4
kubectl get pod -n web-agents -l app.kubernetes.io/name=cloner -o wide
# Servidor NFS en UCS-4 (mismo nodo)
kubectl get pod -n web-agents -l app.kubernetes.io/name=nfs-server -o wide
# DHCP daemon en todos los nodos
kubectl get ds cni-dhcp-daemon -n web-agents
# IP de VLAN 40 e internet
kubectl exec -n web-agents deploy/cloner -- ip addr show net1
kubectl exec -n web-agents deploy/cloner -- ping -c 3 8.8.8.8
# Slots montando el NFS
kubectl get pvc -A | grep cloned-sites
Troubleshooting¶
| Síntoma | Verificación |
|---|---|
| Cloner en otro nodo | kubectl get pod -o wide → NODE debe ser UCS-4 |
net1 sin IP |
VLAN 40 en el trunk Nexus; eth1.40 en UCS-4; DHCP daemon Ready |
| Sin internet | kubectl exec deploy/cloner -- ping -c 3 -I net1 8.8.8.8 |
Slots con MountVolume.SetUp failed |
Servidor NFS Ready y en el MISMO nodo que el cloner |
| Slots Ready pero sirviendo vacío | Cloner escribió en otro nodo antes que NFS levantara; reinicie NFS y luego el cloner |
PVC cloned-sites Pending |
kubectl describe pv — verifica el binding estático |
Véase también¶
docs/CLONER.md— referencia completa de arquitecturadocs/CLONER_OPERATIONS.md— playbook operativok8s/dut/35-nfs-server.yaml+36-cloned-sites-pvs.yaml— almacenamiento cross-nodeoverlays/multi-node/kustomization.yaml— fija el cloner enrole=infra
Próximos pasos¶
- Consulte UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.es.md para el quickstart single-node
- Consulte DUT_TESTBED.md para la configuración del NGFW físico
- Consulte PERFORMANCE_TUNING.md para ajustes finos de rendimiento
- Consulte K6_FLEET.md para operar la flota k6
TLSStress.Art v3.7.0 — Autor: André Luiz Gallon