TLSStress.Art — Deployment Dual-Node en 2 Servidores UCS¶
Modo de deployment: dual-node — UCS-1 ejecuta la flota de agentes (browser-engine + synthetic-load) y UCS-2 ejecuta todo lo demás (personas, servicios, observabilidad). Para una configuración en un único servidor consulte
UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.es.md; para la topología distribuida en cuatro servidores consulteUBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.es.md.Objetivo: desplegar un TLSStress.Art sobre exactamente dos servidores Cisco UCS — uno dedicado a la generación de carga y otro dedicado a webservers + servicios + observabilidad — para ofrecer un punto intermedio de escala entre single-node y el multi-node de cuatro servidores.
Ú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.
Para quién es esta guía: ingenieros de redes y sistemas que operan un test bed de NGFW, disponen de dos chasis UCS y desean aislamiento de hardware entre los generadores de carga y los webservers que se están midiendo.
Tiempo estimado: 60–90 minutos para el bootstrap inicial del cluster; 15 minutos para teardown + redeploy.
Instalación automatizada (recomendada)¶
Ejecute el script de instalación una vez por servidor en este orden exacto (UCS-2 primero porque aloja el control plane de k3s):
# Clonar el repositorio en cada servidor
git clone https://github.com/nollagluiz/AI_forSE.git && cd AI_forSE
# UCS-2 — servidor k3s + personas + servicios + observabilidad
sudo ./scripts/k8s-install.sh --mode=dual-server --data-iface=eth1
# ↑ Imprime JOIN_TOKEN y SERVER_IP al final — guárdelos para UCS-1.
# UCS-1 — agentes browser-engine + synthetic-load
sudo ./scripts/k8s-install.sh --mode=dual-agent \
--server-ip=<IP-OOBI-de-UCS-2> --token=<JOIN_TOKEN> --data-iface=eth1
# De vuelta en UCS-2 — aplicar todos los manifests de Kubernetes
sudo ./scripts/k8s-install.sh --mode=dual-apply
Dry run: añada
--dry-runa cualquier comando para previsualizar sin aplicar cambios.
El resto de esta guía explica cada paso en detalle y resulta útil para personalizar la configuración o diagnosticar problemas en la instalación automatizada.
Tabla de contenidos¶
- Por qué dual-node
- Visión general de la arquitectura
- Red OOBI — obligatoria en ambos UCS
- Prerrequisitos por servidor
- Paso 1 — Preparación del host (ambos UCS)
- Paso 2 — Bootstrap del cluster k3s
- Paso 3 — Configuración de VLANs por nodo
- Paso 4 — Aplicar manifests
- Paso 5 — Verificar el deployment
- Observabilidad — Grafana y Prometheus en dual-node
- Solución de problemas
- Referencia — archivo de overlay
1. Por qué dual-node¶
Single-node es la disposición más simple, pero todas las cargas de trabajo compiten por la misma CPU, memoria y colas de NIC — bajo carga es imposible saber si el cuello de botella está en el NGFW o en el propio test bed. Multi-node resuelve eso por completo, pero requiere cuatro chasis UCS.
Dual-node es el punto medio:
- Aislamiento de hardware: los generadores de carga (browser-engine + synthetic-load) y los webservers (personas + cloned-personas) corren en chasis UCS físicos distintos, de modo que la única ruta de contención entre ellos pasa por el NGFW bajo prueba — exactamente la variable que se quiere medir.
- Dos roles en lugar de cuatro: la mitad del espacio en rack, la mitad del cableado y la mitad de la sobrecarga operativa que requiere multi-node.
- Misma observabilidad: dashboards de Grafana y alertas de Prometheus idénticos a los de single-node y multi-node —
node_exportercorre en ambos UCS vía DaemonSet, y la alerta de cuello de botella del test bed se adapta automáticamente a la topología de dos nodos.
Elija dual-node cuando disponga de dos chasis y desee mediciones limpias del NGFW sin necesidad de levantar un cluster de cuatro servidores.
2. Visión general de la arquitectura¶
Topología física¶
┌──────────────────────────────────────────────────┐
│ Red OOBI — eth0 en AMBOS UCS │
│ 10.0.0.0/24 (ejemplo) │
│ k3s API :6443 · kubelet · scrape de Prometheus │
└────────┬──────────────────────┬───────────────────┘
│ │
┌────────┴────────┐ ┌────────┴────────────────────┐
│ UCS-1 │ │ UCS-2 │
│ role=agents │ │ role=ngfw-dut │
│ │ │ │
│ browser engine │ │ 20 pods Caddy persona │
│ synthetic-load engine │ │ 10 slots cloned-persona │
│ │ │ Dashboard · Postgres │
│ │ │ PgBouncer · Cloner · NFS │
│ │ │ Grafana · Prometheus │
│ │ │ SNMP exporter │
│ eth0 (OOBI) │ │ eth0 (OOBI) │
│ eth1 (trunk) │ │ eth1 (trunk) │
└────────┬─────────┘ └────────────┬─────────────────┘
│ │
VLAN 20 (172.16/16) VLAN 99 mgmt (192.168.90/24)
VLAN 30 (172.17/16) VLAN 40 ISP (DHCP vía macvlan)
VLANs 101–120 (10.1.x/27)
VLANs 200–209 (10.2.x/27)
│ │
└─────────────┬─────────────┘
│
┌────────────┴────────────┐
│ Cisco Nexus 9000 │
│ Trunk de VLANs · ECMP │
│ DSCP AF41 · MTU 9216 │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ NGFW (Device Under Test)│
│ TLS leg-1: agentes→NGFW│
│ TLS leg-2: NGFW→Caddy │
└─────────────────────────┘
Distribución de cargas de trabajo¶
| Carga de trabajo | UCS-1 (role=agents) |
UCS-2 (role=ngfw-dut) |
|---|---|---|
Agentes browser engine (web-agent) |
✓ | — |
Agentes synthetic-load engine (k6-agent) |
✓ | — |
| Personas sintéticas (20× Caddy) | — | ✓ |
| Slots cloned-persona (10× Caddy) | — | ✓ |
| Dashboard, Postgres, PgBouncer | — | ✓ |
| Cloner | — | ✓ |
| Servidor NFS (cloned-sites) | — | ✓ |
| SNMP exporter | — | ✓ |
| Prometheus + Grafana | — | ✓ |
node_exporter (métricas por host) |
✓ DaemonSet | ✓ DaemonSet |
node-tuning (sysctl + BBR + CPU governor) |
✓ DaemonSet (dut-data-plane=true) |
✓ DaemonSet (dut-data-plane=true) |
cni-dhcp (DHCP del ISP del cloner) |
— | ✓ DaemonSet |
Por qué role=ngfw-dut en UCS-2 (en lugar de role=infra)¶
Las personas sintéticas (personas/_generated/*/deployment.yaml), las cloned-personas (k8s/clone-personas/20-slots.yaml) y el SNMP exporter (k8s/dut/60-snmp-exporter.yaml) tienen todas hardcodeado nodeSelector: role=ngfw-dut. Reutilizar esa label en UCS-2 evita tener que tocar veintitantos manifests. El overlay dual-node se encarga entonces de redirigir las cargas de servicios adicionales (Dashboard, Postgres, PgBouncer, Cloner) — que el overlay multi-node envía a role=infra — al mismo nodo role=ngfw-dut.
3. Red OOBI — obligatoria en ambos UCS¶
La red OOBI (out-of-band infrastructure) es el segmento dedicado al control plane y va por eth0 en cada UCS, independientemente del modo de deployment.
Qué corre sobre OOBI¶
- Servidor API de k3s (
:6443) — UCS-1 lo alcanza en UCS-2 para unirse al cluster - kubelet (
:10250) — UCS-2 hace scrape a UCS-1 para obtener el estado de los pods - flannel (CNI) — el tráfico pod-a-pod entre nodos usa VXLAN sobre eth0
- Scrape de Prometheus —
node_exporter,cAdvisorykube-state-metricsse alcanzan todos por eth0 - Renovaciones de cert-manager — webhook + tráfico de challenge ACME
- Dashboard / kubectl — acceso del operador
Qué NO corre sobre OOBI¶
- Tráfico de prueba del NGFW — va por el trunk de VLANs en
eth1(data plane) - Polling SNMP — VLAN 99 (
192.168.90.0/24) eneth1.99 - Salida a internet del cloner — VLAN 40 en
eth1.40
Requisitos por UCS¶
| UCS-1 (agentes) | UCS-2 (servicios) | |
|---|---|---|
| IP de eth0 | estática, subnet OOBI | estática, subnet OOBI |
| Alcanzable desde el eth0 del peer | ✓ (kubelet ↔ control plane) | ✓ |
| MTU | 1500 (por defecto) | 1500 (por defecto) |
| Ruta por defecto vía OOBI | ✓ — salida a internet para descarga de imágenes y la instalación de k3s | ✓ |
Si falta eth0 en cualquiera de los UCS, flannel de k3s no puede establecer el control plane, UCS-1 no puede unirse al cluster y Prometheus en UCS-2 no puede hacer scrape de las métricas de host de UCS-1. La verificación previa de k8s-install.sh rechaza tanto dual-server como dual-agent si eth0 no está presente.
4. Prerrequisitos por servidor¶
Hardware¶
| UCS-1 | UCS-2 | |
|---|---|---|
| CPU | ≥ 16 cores físicos | ≥ 32 cores físicos |
| RAM | ≥ 64 GB | ≥ 128 GB |
| NICs | eth0 (OOBI) + eth1 (trunk al Nexus) | eth0 (OOBI) + eth1 (trunk al Nexus) |
| Disco | ≥ 200 GB libres en / |
≥ 500 GB libres en / (NFS + Postgres + retención de Prometheus) |
UCS-2 es el nodo más pesado — corre las personas, todos los servicios y el stack de observabilidad. Planifique 2× los recursos de UCS-1.
Sistema operativo¶
- Ubuntu 22.04 LTS o 24.04 LTS, amd64
- acceso de root (o sudo)
- acceso a internet vía la ruta por defecto OOBI durante la instalación (k3s + Helm + imágenes de contenedor)
Red — Nexus 9000¶
VLANs que deben estar configuradas en el trunk hacia ambos UCS:
| VLAN | UCS-1 | UCS-2 | Subnet | Uso |
|---|---|---|---|---|
| 20 | ✓ | — | 172.16.0.0/16 | Agentes browser engine |
| 30 | ✓ | — | 172.17.0.0/16 | Agentes synthetic-load engine |
| 40 | — | ✓ | DHCP desde el ISP | Salida a internet del cloner (macvlan) |
| 99 | — | ✓ | 192.168.90.0/24 | Polling SNMP — Nexus (.2), NGFW (.3) |
| 101–120 | — | ✓ | 10.1.{1..20}.0/27 | Webservers de personas sintéticas |
| 200–209 | — | ✓ | 10.2.{1..10}.0/27 | Slots de cloned-persona |
En estricto rigor: la VLAN 20 y la VLAN 30 deben llegar solo a UCS-1; las VLANs 40, 99, 101–120 y 200–209 deben llegar solo a UCS-2. El trunk del Nexus puede transportarlas todas y dejar que cada lado ignore las que no consume — el trunk es idéntico para ambos puertos.
Paso 1 — Preparación del host (ambos UCS)¶
Preparación idéntica en UCS-1 y UCS-2:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
curl git jq openssl ca-certificates gnupg lsb-release \
vlan iproute2 ethtool net-tools
# Verificar ambas NICs
ip link show eth0
ip link show eth1
Luego ejecute el script de host-tuning del proyecto en ambos UCS — sysctls para QUIC + TCP de alto fan-out, BBR + FQ, CPU governor, conntrack, puertos:
sudo scripts/host-tuning.sh apply
sudo scripts/host-tuning.sh status
El paso apply es idempotente y persiste a través de drop-ins de systemd-sysctl. En cada ejecución de medición, la alerta TestBedSysctlMissing del dashboard se disparará si cualquiera de los UCS regresa a los valores por defecto del kernel.
Paso 2 — Bootstrap del cluster k3s¶
UCS-2 primero (es el servidor k3s)¶
sudo ./scripts/k8s-install.sh --mode=dual-server --data-iface=eth1
El script:
- Verifica eth0 + eth1 + interfaz del ISP
- Crea las subinterfaces VLAN 40, 99, 101–120, 200–209 en
eth1 - Levanta la subinterfaz
eth1.40del ISP y la persiste vía netplan - Instala el servidor k3s, enlaza la API a
eth0, deshabilita Traefik y ServiceLB - Instala Helm, cert-manager, Multus
- Aplica al nodo las labels
role=ngfw-dut+dut-data-plane=true - Imprime el JOIN_TOKEN y SERVER_IP
Guarde las credenciales impresas — UCS-1 las necesita.
UCS-1 (agentes)¶
sudo ./scripts/k8s-install.sh --mode=dual-agent \
--server-ip=<IP-OOBI-de-UCS-2> --token=<JOIN_TOKEN> --data-iface=eth1
El script:
- Verifica eth0 + eth1 (ambas obligatorias)
- Crea las subinterfaces VLAN 20, 30 en
eth1 - Se une a k3s como agent por eth0 → UCS-2:6443
- Se aplica las labels
role=agents+dut-data-plane=true
Verifique desde UCS-2:
kubectl get nodes -o wide
# Salida esperada:
# NAME STATUS ROLES AGE VERSION LABELS
# ucs-1 Ready agent 1m v1.31.x role=agents,dut-data-plane=true,...
# ucs-2 Ready master 3m v1.31.x role=ngfw-dut,dut-data-plane=true,...
Paso 3 — Configuración de VLANs por nodo¶
El script de instalación realiza esto automáticamente. Para verificar:
UCS-1:
ip -br link | grep eth1\\.
# eth1.20 UP (172.16.0.1/16)
# eth1.30 UP (172.17.0.1/16)
UCS-2:
ip -br link | grep eth1\\.
# eth1.40 UP (sin IP — DHCP vía macvlan del pod cloner)
# eth1.99 UP (192.168.90.1/24)
# eth1.101 UP (10.1.1.1/27)
# … hasta eth1.120 (10.1.20.1/27)
# eth1.200 UP (10.2.1.1/27)
# … hasta eth1.209 (10.2.10.1/27)
La persistencia de VLANs entre reinicios la maneja netplan (el script escribe /etc/netplan/99-*.yaml).
Paso 4 — Aplicar manifests¶
Desde UCS-2:
# Crear el configmap de la CA del NGFW (una sola vez)
kubectl create configmap ngfw-ca -n web-agents --from-file=ngfw-ca.crt=<ruta-al-cert>
# Crear los secrets de la aplicación a partir de .env
kubectl create secret generic web-agent-secrets -n web-agents --from-env-file=.env
# Aplicar todo
sudo ./scripts/k8s-install.sh --mode=dual-apply
Esto aplica:
kubectl apply -k overlays/dual-node/— manifests base de k8s con browser-engine/synthetic-load fijados arole=agentsy Dashboard/Postgres/PgBouncer/Cloner fijados arole=ngfw-dutkubectl apply -k k8s/dut/— overlay DUT (NADs, sondas SNMP, servidor NFS, DaemonSet de node-tuning, ServiceMonitor de cAdvisor, reglas de Prometheus de infraestructura)- Parchea el
nodeSelectordel DaemonSetnode-tuningadut-data-plane=true(corre en ambos UCS) - Parchea los deployments de browser-engine + synthetic-load vía
40-playwright-patch.yaml/50-k6-patch.yaml— anotación de Multus net1 macvlan, confianza en la CA del NGFW, REJECT_INVALID_CERTS=true - Vuelve a fijar browser-engine + synthetic-load a
role=agents(los archivos de patch tienen como valor por defectorole=ngfw-dutpor compatibilidad con single-node — dual-node lo sobrescribe) - Espera a que todos los pods queden Ready
Paso 5 — Verificar el deployment¶
# Pods en UCS-1 (agentes)
kubectl get pods -n web-agents -o wide --field-selector spec.nodeName=ucs-1
# Esperado: solo web-agent-* y k6-agent-*
# Pods en UCS-2 (servicios + personas)
kubectl get pods -n web-agents -o wide --field-selector spec.nodeName=ucs-2
# Esperado: dashboard, postgres, pgbouncer, cloner, nfs-server,
# persona-shop-*, persona-news-*, …, clone-persona-1-*, …
# Personas sintéticas + clonadas en sus propios namespaces
kubectl get pods --all-namespaces -o wide | grep -E "persona-|clone-persona-"
# Multus net1 conectado en los agentes
kubectl exec -n web-agents deploy/web-agent -- ip -br addr | grep net1
# Esperado: una dirección 172.16.x.x
# Verificar que el servidor NFS sea alcanzable desde el cloner
kubectl exec -n web-agents deploy/cloner -- mount | grep nfs
# Smoke test end-to-end — los agentes alcanzan una persona a través del NGFW
kubectl exec -n web-agents deploy/web-agent -- \
curl -sf https://shop.persona.local/ | head -1
6. Observabilidad — Grafana y Prometheus en dual-node¶
La observabilidad corre íntegramente en UCS-2 (role=ngfw-dut):
- Prometheus — hace scrape a ambos UCS por OOBI (eth0) en el puerto
:9100paranode_exporter, kubelet:10250para cAdvisor y:8080parakube-state-metrics - Grafana — el dashboard
Test-Bed Infrastructure Healthse adapta automáticamente a la topología de dos nodos mediante filtros por host y reglas de recordingcount(node_exporter) - Alertas — la alerta compuesta
TestBedInfrastructureBottleneck, las alertas a nivel de host (HostUDPBufferOverflow,HostConntrackNearFull, etc.) y las alertas a nivel de pod (PodCPUThrottled,OOMKilled) funcionan todas out-of-the-box. VeaMONITORING_TEST_VALIDITY.es.mdpara el catálogo completo de alertas
El DaemonSet node_exporter no tiene nodeSelector y usa tolerations: operator: Exists, por lo que cubre ambos UCS sin configuración adicional. La alerta NodeExporterCoverageIncomplete se dispara automáticamente si cualquiera de los nodos deja de reportar métricas de host.
Para abrir Grafana desde su workstation de operador (asumiendo que tiene acceso kubectl a UCS-2):
kubectl port-forward -n web-agents svc/grafana 3000:3000
# luego visite http://localhost:3000
7. Solución de problemas¶
UCS-1 no logra unirse al cluster¶
Síntomas: la instalación dual-agent se queda colgada en Joining k3s cluster as agent.
# En UCS-1 — verificar la conectividad OOBI hacia UCS-2
ping <IP-OOBI-de-UCS-2>
nc -vz <IP-OOBI-de-UCS-2> 6443
# En UCS-1 — verificar el estado y los logs del servicio k3s-agent
sudo systemctl status k3s-agent
sudo journalctl -u k3s-agent -n 100
Causas más comunes: discrepancia de subnet OOBI, firewall bloqueando el 6443, o JOIN_TOKEN incorrecto.
Los pods de browser engine se quedan en Pending¶
kubectl describe pod -n web-agents -l app=web-agent | grep -A4 Events
Si aparece node(s) didn't match Pod's node affinity/selector, el paso de re-pinning de los agentes no se ejecutó. Aplíquelo manualmente:
kubectl patch deployment web-agent -n web-agents --type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"role":"agents"}}}}}'
kubectl patch deployment k6-agent -n web-agents --type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"role":"agents"}}}}}'
Las personas se quedan en Pending¶
kubectl describe pod -n persona-shop persona-shop-* | grep -A4 Events
Si vuelve a aparecer node(s) didn't match, UCS-2 no tiene la label role=ngfw-dut. Vuelva a etiquetar:
kubectl label node ucs-2 role=ngfw-dut dut-data-plane=true --overwrite
El cloner no logra alcanzar internet¶
El cloner usa eth1.40 con macvlan y DHCP. Verifique en UCS-2:
ip link show eth1.40
sudo dhclient -v eth1.40 # solo como diagnóstico manual — el pod tiene su propio DHCP
Si eth1.40 no existe, vuelva a ejecutar setup_isp_iface (forma parte de dual-server).
node_exporter solo reporta un nodo¶
kubectl get pods -n web-agents -l app.kubernetes.io/name=node-exporter -o wide
Tanto ucs-1 como ucs-2 deberían aparecer. Si no es así, verifique:
kubectl describe daemonset node-exporter -n web-agents | grep -A3 Tolerations
Las tolerations deben ser operator: Exists (matchea cualquier taint). Si un nodo tiene un taint personalizado que lo bloquea, agregue una toleration o quite el taint.
8. Referencia — archivo de overlay¶
El overlay dual-node vive en overlays/dual-node/kustomization.yaml. Hereda de k8s/ y agrega seis strategic-merge patches:
patches:
# browser-engine + K6 → UCS-1
- target: { kind: Deployment, name: web-agent } # role=agents
- target: { kind: Deployment, name: k6-agent } # role=agents
# Servicios → UCS-2 (reutiliza role=ngfw-dut)
- target: { kind: Deployment, name: dashboard } # role=ngfw-dut
- target: { kind: StatefulSet, name: postgres } # role=ngfw-dut
- target: { kind: Deployment, name: pgbouncer } # role=ngfw-dut
- target: { kind: Deployment, name: cloner } # role=ngfw-dut
Para aplicar manualmente (sin el script de instalación):
kubectl apply -k overlays/dual-node/
kubectl apply -k k8s/dut/
kubectl patch daemonset node-tuning -n web-agents \
--type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"dut-data-plane":"true"}}}}}'
Véase también¶
UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.es.md— alternativa en un único servidorUBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.es.md— alternativa en cuatro servidoresMONITORING_TEST_VALIDITY.es.md— alertas de observabilidad y validez de las pruebasPERFORMANCE_TUNING_HOST.md— tuning del kernel del hostSYSTEM_OVERVIEW.md— referencia de arquitectura para los tres modos de deployment