Skip to content

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-run a 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=true ya 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

  1. Verificar sub-interfaces VLAN en UCS-1:
    ip link show | grep eth1
    
  2. Verificar que el NGFW tiene ruta de retorno para VLANs 101–120.
  3. 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 (net1 macvlan 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 nodeAffinity del cloner (role=infra) y el preferredAffinity del 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 arquitectura
  • docs/CLONER_OPERATIONS.md — playbook operativo
  • k8s/dut/35-nfs-server.yaml + 36-cloned-sites-pvs.yaml — almacenamiento cross-node
  • overlays/multi-node/kustomization.yaml — fija el cloner en role=infra

Próximos pasos


TLSStress.Art v3.7.0 — Autor: André Luiz Gallon