Skip to content

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 consulte UBUNTU_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-run a 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

  1. Por qué dual-node
  2. Visión general de la arquitectura
  3. Red OOBI — obligatoria en ambos UCS
  4. Prerrequisitos por servidor
  5. Paso 1 — Preparación del host (ambos UCS)
  6. Paso 2 — Bootstrap del cluster k3s
  7. Paso 3 — Configuración de VLANs por nodo
  8. Paso 4 — Aplicar manifests
  9. Paso 5 — Verificar el deployment
  10. Observabilidad — Grafana y Prometheus en dual-node
  11. Solución de problemas
  12. 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_exporter corre 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 Prometheusnode_exporter, cAdvisor y kube-state-metrics se 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) en eth1.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:

  1. Verifica eth0 + eth1 + interfaz del ISP
  2. Crea las subinterfaces VLAN 40, 99, 101–120, 200–209 en eth1
  3. Levanta la subinterfaz eth1.40 del ISP y la persiste vía netplan
  4. Instala el servidor k3s, enlaza la API a eth0, deshabilita Traefik y ServiceLB
  5. Instala Helm, cert-manager, Multus
  6. Aplica al nodo las labels role=ngfw-dut + dut-data-plane=true
  7. 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:

  1. Verifica eth0 + eth1 (ambas obligatorias)
  2. Crea las subinterfaces VLAN 20, 30 en eth1
  3. Se une a k3s como agent por eth0 → UCS-2:6443
  4. 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:

  1. kubectl apply -k overlays/dual-node/ — manifests base de k8s con browser-engine/synthetic-load fijados a role=agents y Dashboard/Postgres/PgBouncer/Cloner fijados a role=ngfw-dut
  2. kubectl apply -k k8s/dut/ — overlay DUT (NADs, sondas SNMP, servidor NFS, DaemonSet de node-tuning, ServiceMonitor de cAdvisor, reglas de Prometheus de infraestructura)
  3. Parchea el nodeSelector del DaemonSet node-tuning a dut-data-plane=true (corre en ambos UCS)
  4. 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
  5. Vuelve a fijar browser-engine + synthetic-load a role=agents (los archivos de patch tienen como valor por defecto role=ngfw-dut por compatibilidad con single-node — dual-node lo sobrescribe)
  6. 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 :9100 para node_exporter, kubelet :10250 para cAdvisor y :8080 para kube-state-metrics
  • Grafana — el dashboard Test-Bed Infrastructure Health se adapta automáticamente a la topología de dos nodos mediante filtros por host y reglas de recording count(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. Vea MONITORING_TEST_VALIDITY.es.md para 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