Skip to content

TLSStress.Art — Despliegue en Nodo Único (Single-Node) en Ubuntu con k3s

Modo de despliegue: nodo único (single-node) — todos los componentes (agentes, webservers, dashboard, observabilidad) corren en un solo servidor Ubuntu. Es la forma más rápida de comenzar. Para un despliegue multi-servidor donde cada capa tiene una máquina dedicada, consulte UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.es.md.

Ú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.

Objetivo: instalar y operar el banco de pruebas completo de inspección TLS para NGFW en un único servidor Ubuntu usando k3s. El stack incluye agentes browser-engine + synthetic-load, dashboard, base de datos, 30 webservers (20 Synthetic + 10 Cloned slots) persona y observabilidad completa con Prometheus/Grafana.

Para quién es esta guía: ingenieros de redes con manejo básico de Linux que estén dando sus primeros pasos en Kubernetes.

Tiempo estimado: 30–45 min (Modo Simple) | 60–90 min (Modo DUT completo).

Autoría: André Luiz Gallon — agallon@Cisco.com


Instalación automatizada (recomendada)

Si desea comenzar rápidamente, use el script de instalación automatizada. Instala k3s, Helm, cert-manager, Multus, configura las interfaces VLAN y aplica todos los manifests de Kubernetes en el orden correcto — sin necesidad de conocimientos previos en Kubernetes:

# Clonar el repositorio
git clone https://github.com/nollagluiz/AI_forSE.git
cd AI_forSE

# Single-node: todo en un único servidor
sudo ./scripts/k8s-install.sh --mode=single --data-iface=eth1

El script realizará: 1. Validar los requisitos previos (SO, RAM, disco, interfaces de red) 2. Instalar k3s, Helm, cert-manager y Multus CNI 3. Configurar subinterfaces VLAN en eth1 (VLANs 20, 30, 99, 101–120) 4. Aplicar todos los manifests de Kubernetes en el orden correcto 5. Esperar a que los pods estén listos y mostrar la URL del Dashboard

Opciones: ejecute sudo ./scripts/k8s-install.sh --help para ver todos los parámetros disponibles, incluyendo --dry-run para visualizar lo que se ejecutaría sin realizar cambios.

El resto de esta guía explica cada paso en detalle, útil para personalizar la instalación o depurar problemas.


Modos de operación

Modo Simple Modo DUT
Propósito Probar agentes contra sitios reales de internet Banco de pruebas NGFW — tráfico pasa por el firewall físico
Tráfico Agentes → Internet (HTTP/2 + HTTP/3) Agentes → NGFW → Caddy webservers en VLANs aisladas
Personas No aplica 30 webservers simulados (20 Synthetic + 10 Cloned slots)
Observabilidad Básica (dashboard + Postgres) Completa (Prometheus + Grafana + SNMP del NGFW)
NICs requeridas 1 (gestión + datos por la misma interfaz) 2 (eth0 = gestión K8s, eth1 = trunk VLAN al Nexus 9000)
Pasos Parte A + Parte B Parte A + Parte C

Requisitos previos

Item Modo Simple Modo DUT
Sistema operativo Ubuntu 22.04 LTS o 24.04 LTS igual
Arquitectura x86_64 o arm64 igual
RAM 16 GB 64 GB
vCPU 4 16+
Disco 30 GB libres 120 GB libres
NICs 1 2 (eth0 gestión, eth1 trunk al switch)
Acceso usuario con sudo igual
Red Acceso a internet eth0 con internet; eth1 conectada al Nexus 9000
Software extra Helm ≥ 3.14, Multus CNI, NGFW físico
Kubernetes extra Nginx Ingress, metrics-server, cert-manager + Multus CNI, Prometheus stack

Si la máquina es un servidor sin monitor, al final usa ssh -L <puerto>:localhost:<puerto> user@servidor para acceder a la UI desde el navegador de tu equipo local.


Parte A — Preparación del host (ambos modos)

Paso 0 — Paquetes base

# Actualizar e instalar dependencias del sistema
sudo apt update && sudo apt upgrade -y
sudo apt install -y \
  curl ca-certificates iptables openssl jq git \
  iproute2 ethtool net-tools

# Instalar Helm (necesario para observabilidad en Modo DUT)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version

Paso 1 — Instalar k3s

k3s es una distribución de Kubernetes completa empaquetada en un solo binario. Se instala en segundos y no requiere Docker.

# Instalar k3s deshabilitando Traefik (usaremos Nginx Ingress)
curl -sfL https://get.k3s.io | \
  INSTALL_K3S_EXEC="--disable=traefik --write-kubeconfig-mode=644" sh -

# Verificar que el nodo arrancó correctamente
sudo systemctl status k3s --no-pager | head -10
sudo k3s kubectl get nodes

Resultado esperado: el hostname con STATUS=Ready y ROLES=control-plane,master.

Configurar kubectl para el usuario actual (sin necesidad de sudo):

# Copiar el kubeconfig al directorio del usuario
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
chmod 600 ~/.kube/config

# Agregar la variable al perfil de shell
echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
source ~/.bashrc

# Verificar acceso sin sudo
kubectl get nodes
kubectl get ns

Paso 2 — Instalar Nginx Ingress Controller

# Aplicar el manifest del controlador de Ingress
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml

# Esperar a que el controlador esté listo (hasta 3 minutos)
kubectl -n ingress-nginx wait --for=condition=available \
  deployment/ingress-nginx-controller --timeout=180s

# Verificar el servicio (el EXTERNAL-IP puede quedar en <pending> en un nodo único — es normal)
kubectl -n ingress-nginx get svc

Paso 3 — Instalar metrics-server

El HorizontalPodAutoscaler necesita métricas de CPU y memoria para escalar la flota de agentes.

# Instalar metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# k3s usa certificados auto-firmados en kubelet — agregar flag de aceptación
kubectl -n kube-system patch deployment metrics-server --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

# Esperar a que esté disponible
kubectl -n kube-system wait --for=condition=available \
  deployment/metrics-server --timeout=180s

# Validar — debe mostrar CPU y memoria del nodo en segundos
kubectl top nodes

Paso 4 — Instalar cert-manager y clonar el repositorio

cert-manager emite los certificados TLS para el webserver Caddy y para las 30 personas (20 Sintéticas + 10 slots Clonados).

# Instalar cert-manager v1.16.0
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.0/cert-manager.yaml

# Esperar a que todos los pods de cert-manager estén listos
kubectl -n cert-manager wait --for=condition=available deployment --all --timeout=120s

# Clonar el repositorio del proyecto
git clone https://github.com/nollagluiz/AI_forSE.git
cd AI_forSE
# Usar la rama principal — nunca una rama de feature antigua

Las imágenes del proyecto son públicas en GitHub Container Registry (GHCR). En entornos con acceso a internet, k3s las descarga automáticamente. Para entornos air-gapped, ver la sección Importación air-gapped al final.


Parte B — Modo Simple

Los pasos de esta parte cubren el stack base: agentes browser engine, agentes synthetic-load engine, dashboard y Postgres. Los agentes navegan sitios reales de internet sin pasar por ningún NGFW.

Paso 5B — Crear los Secrets

Los manifests del proyecto usan placeholders. Los secrets reales se crean con kubectl:

# Crear el namespace principal
kubectl create namespace web-agents

# Generar contraseña aleatoria para Postgres
DB_PASS=$(openssl rand -hex 24)

# Secret de Postgres
kubectl -n web-agents create secret generic postgres-credentials \
  --from-literal=POSTGRES_USER=agent_dashboard \
  --from-literal=POSTGRES_PASSWORD="$DB_PASS" \
  --from-literal=POSTGRES_DB=agent_dashboard

# Token compartido entre dashboard y agentes browser-engine
TOKEN=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic web-agent-secrets \
  --from-literal=CONTROLLER_TOKEN="$TOKEN"

# Secret para la flota synthetic-load
K6_SECRET=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic k6-agent-secrets \
  --from-literal=DASHBOARD_SECRET="$K6_SECRET"

# Secrets del dashboard (DATABASE_URL, token de API, credencial admin, sesión)
ADMIN_PASS=$(openssl rand -hex 16)
SESSION=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic dashboard-secrets \
  --from-literal=DATABASE_URL="postgresql://agent_dashboard:${DB_PASS}@postgres.web-agents.svc.cluster.local:5432/agent_dashboard?sslmode=disable" \
  --from-literal=AGENT_API_TOKEN="$TOKEN" \
  --from-literal=ADMIN_BASIC_AUTH="admin:$ADMIN_PASS" \
  --from-literal=SESSION_SECRET="$SESSION"

# Mostrar la contraseña admin — guárdala, la necesitarás más adelante
echo "============================================="
echo "Credencial admin del dashboard:"
echo "    Usuario: admin"
echo "    Password: $ADMIN_PASS"
echo "============================================="

Guarda el usuario y contraseña. Los usarás en /login y para las llamadas a la API admin.

Paso 6B — Aplicar los manifests base

# Desde el directorio raíz del repositorio clonado
# Aplicar todos los manifests del stack base en orden
kubectl apply -f k8s/

# Esperar a Postgres
kubectl -n web-agents wait --for=condition=ready \
  pod -l app.kubernetes.io/name=postgres --timeout=180s

# Esperar al dashboard
kubectl -n web-agents wait --for=condition=available \
  deployment/dashboard --timeout=240s

# Ver el estado de todos los pods
kubectl -n web-agents get pods

Resultado esperado: pods postgres-0, dashboard-*, web-agent-* y k6-agent-* en estado Running.

Paso 7B — Aplicar migraciones de la base de datos

# Obtener el nombre del pod de Postgres
PG_POD=$(kubectl -n web-agents get pod \
  -l app.kubernetes.io/name=postgres \
  -o jsonpath='{.items[0].metadata.name}')

# Verificar que Postgres responde
kubectl -n web-agents exec "$PG_POD" -- psql -U agent_dashboard -d agent_dashboard -c '\l'

Las migraciones se aplican automáticamente a través del init container del dashboard. Si ves que el dashboard reinicia con errores de DB, verifica que el secret postgres-credentials está creado correctamente antes de que el pod arranque.

Paso 8B — Acceder al dashboard

En una terminal separada, abre el port-forward:

# Mantener este comando corriendo en segundo plano o en otra terminal
kubectl -n web-agents port-forward svc/dashboard 3000:3000

Luego abre el navegador: - Ubuntu desktop: http://localhost:3000/ - Servidor remoto: en tu máquina local ejecuta ssh -L 3000:localhost:3000 user@ip-del-servidor y abre http://localhost:3000/

Login: admin + la contraseña generada en el Paso 5B.

Paso 9B — Registrar targets y activar la flota browser engine

# Leer la contraseña admin desde el secret (si no la guardaste)
ADMIN_PASS=$(kubectl -n web-agents get secret dashboard-secrets \
  -o jsonpath='{.data.ADMIN_BASIC_AUTH}' | base64 -d | cut -d: -f2-)
ADMIN="admin:$ADMIN_PASS"

# Registrar sitios destino de ejemplo
for URL in \
  "https://www.cloudflare.com" \
  "https://www.fastly.com" \
  "https://www.akamai.com"; do
  curl -s -u "$ADMIN" \
    -H 'Content-Type: application/json' \
    -d "{\"url\":\"$URL\",\"weight\":1}" \
    http://localhost:3000/api/admin/targets
  echo
done

# Activar la flota con 3 agentes browser-engine iniciales
curl -s -u "$ADMIN" \
  -X PUT \
  -H 'Content-Type: application/json' \
  -d '{
    "enabled": true,
    "cycleIntervalMs": 30000,
    "jitterMs": 3000,
    "navigationTimeoutMs": 45000,
    "desiredAgentCount": 3
  }' \
  http://localhost:3000/api/admin/config

Paso 10B — Activar la flota synthetic-load engine

# Escalar la flota synthetic-load a 5 agentes para pruebas iniciales
kubectl -n web-agents scale deployment/k6-agent --replicas=5

# Ver el estado del rollout
kubectl -n web-agents rollout status deployment/k6-agent

# Verificar que los pods están corriendo
kubectl -n web-agents get pods -l app.kubernetes.io/name=k6-agent

La flota synthetic-load engine soporta hasta 1.000 réplicas. El HPA la gestiona automáticamente según desiredAgentCount configurado en el dashboard.

Paso 11B — Escalar hasta 300 agentes browser engine

# Escalar directamente a 50 agentes (o cualquier número deseado)
kubectl -n web-agents scale deployment/web-agent --replicas=50

# Monitorear el rollout
kubectl -n web-agents get deploy/web-agent -w

# Para escalar al máximo (requiere 64 GB+ de RAM)
kubectl -n web-agents scale deployment/web-agent --replicas=300

# Ver el HPA en acción
kubectl -n web-agents get hpa/web-agent -w

Capacidad de RAM: cada agente browser engine reserva ~512 MB y puede usar hasta 1 GB. Para 50 réplicas necesitas al menos 25 GB libres. Si hay pods en Pending, reduce el número de réplicas.

Paso 12B — Validación rápida

# Contar agentes corriendo en el cluster
kubectl -n web-agents get pods -l app.kubernetes.io/name=web-agent \
  --no-headers | wc -l

# Ver el consumo de recursos
kubectl top nodes
kubectl -n web-agents top pods

# Consultar ciclos de los últimos 5 minutos directamente en Postgres
PG_POD=$(kubectl -n web-agents get pod \
  -l app.kubernetes.io/name=postgres \
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n web-agents exec "$PG_POD" -- \
  psql -U agent_dashboard -d agent_dashboard -c \
  "SELECT count(*) AS runs_5m,
          round(avg(total_duration_ms))::int AS avg_ms
     FROM runs
    WHERE started_at > now() - interval '5 minutes';"

En el dashboard (http://localhost:3000/dashboard) los gráficos de Mbps, Protocolos negociados y Frecuencia real vs. configurada crecen proporcionalmente al número de agentes activos.


Parte C — Modo DUT (banco de pruebas NGFW)

El Modo DUT configura dos legs TLS separados: - Leg 1: Agentes → NGFW (agentes confían en la CA del NGFW) - Leg 2: NGFW → Caddy webservers (NGFW confía en la PKI de cert-manager)

El tráfico de datos fluye por net1 (macvlan, bypass de iptables). Las métricas van por eth0 (red K8s estándar).

Paso 5C — Instalar Multus CNI

Multus permite agregar interfaces de red adicionales (macvlan) a los pods para el tráfico de datos por VLAN.

# Instalar Multus CNI en modo "thick" (soporta NetworkAttachmentDefinitions)
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml

# Esperar a que el DaemonSet esté listo en todos los nodos
kubectl -n kube-system rollout status daemonset/kube-multus-ds --timeout=120s

# Verificar que el CRD de NetworkAttachmentDefinition está disponible
kubectl get crd network-attachment-definitions.k8s.cni.cncf.io

Paso 6C — Etiquetar el nodo DUT

El nodo que conecta al NGFW debe etiquetarse para que los manifests DUT se desplieguen en el lugar correcto.

# Obtener el nombre del nodo
kubectl get nodes

# Etiquetar el nodo (reemplaza <nombre-del-nodo> con el nombre real)
kubectl label node <nombre-del-nodo> role=ngfw-dut

# Verificar la etiqueta
kubectl get nodes --show-labels | grep ngfw-dut

Paso 7C — Configurar las interfaces VLAN en el host

El script netsetup-dut.sh crea las subinterfaces VLAN en la NIC de datos (eth1) que Multus usará para los NetworkAttachmentDefinitions.

# Configurar las VLANs en la NIC de datos (por defecto eth1)
# VLAN 20 (172.16.0.0/16): agentes browser-engine
# VLAN 30 (172.17.0.0/16): agentes synthetic-load
sudo DUT_DATA_IFACE=eth1 scripts/netsetup-dut.sh setup

# Configurar las VLANs para las 30 personas: 20 Sintéticas (VLANs 101-120, 10.1.x.0/27)
# y 10 slots de Personas Clonadas (VLANs 200-209, 10.2.{1..10}.0/27)
sudo scripts/netsetup-personas.sh setup

# Verificar las interfaces creadas
ip link show | grep eth1

Paso 8C — Preparar la CA del NGFW

Los agentes deben confiar en la CA de interceptación del NGFW (Leg 1 TLS). Esta CA la exportas desde la interfaz de administración del NGFW.

# Copiar el certificado CA del NGFW al servidor (desde tu máquina local)
# scp ngfw-ca.crt user@servidor:/tmp/ngfw-ca.crt

# Crear el ConfigMap que los agentes montan como CA de confianza
kubectl -n web-agents create configmap ngfw-ca \
  --from-file=ngfw-ca.crt=/tmp/ngfw-ca.crt \
  --dry-run=client -o yaml | kubectl apply -f -

# Verificar el contenido del ConfigMap
kubectl -n web-agents get configmap ngfw-ca -o yaml | grep -A5 "ngfw-ca.crt"

Paso 9C — Crear todos los Secrets (DUT)

Además de los secrets del Modo Simple, el Modo DUT requiere secrets adicionales.

# Crear el namespace y los secrets base (igual que Paso 5B)
kubectl create namespace web-agents

DB_PASS=$(openssl rand -hex 24)
TOKEN=$(openssl rand -hex 32)
K6_SECRET=$(openssl rand -hex 32)
ADMIN_PASS=$(openssl rand -hex 16)
SESSION=$(openssl rand -hex 32)

kubectl -n web-agents create secret generic postgres-credentials \
  --from-literal=POSTGRES_USER=agent_dashboard \
  --from-literal=POSTGRES_PASSWORD="$DB_PASS" \
  --from-literal=POSTGRES_DB=agent_dashboard

kubectl -n web-agents create secret generic web-agent-secrets \
  --from-literal=CONTROLLER_TOKEN="$TOKEN"

kubectl -n web-agents create secret generic k6-agent-secrets \
  --from-literal=DASHBOARD_SECRET="$K6_SECRET"

kubectl -n web-agents create secret generic dashboard-secrets \
  --from-literal=DATABASE_URL="postgresql://agent_dashboard:${DB_PASS}@postgres.web-agents.svc.cluster.local:5432/agent_dashboard?sslmode=disable" \
  --from-literal=AGENT_API_TOKEN="$TOKEN" \
  --from-literal=ADMIN_BASIC_AUTH="admin:$ADMIN_PASS" \
  --from-literal=SESSION_SECRET="$SESSION"

# Secret exclusivo del Modo DUT — comunidad SNMP del NGFW
kubectl -n web-agents create secret generic dut-snmp-secrets \
  --from-literal=SNMP_COMMUNITY=public
# Reemplaza "public" por la comunidad real configurada en el NGFW

# ConfigMap de SNMP (módulos de scraping para el NGFW Cisco)
kubectl create configmap snmp-config \
  --from-file=snmp.yml=observability/snmp/snmp.yml \
  -n web-agents --dry-run=client -o yaml | kubectl apply -f -

echo "============================================="
echo "Credencial admin del dashboard:"
echo "    Usuario: admin"
echo "    Password: $ADMIN_PASS"
echo "============================================="

Paso 10C — Ejecutar el script de despliegue DUT completo

El script k8s-dut-up.sh orquesta las cuatro fases del despliegue DUT. Antes de ejecutarlo, verifica los prerequisitos:

# Verificar prerequisitos sin aplicar cambios
scripts/k8s-dut-up.sh preflight

Si el preflight pasa sin errores:

# Despliegue completo en 4 fases
# Fase 1: stack base (k8s/)
# Fase 2: overlay DUT — NADs, Caddy, SNMP exporter, políticas de red (k8s/dut/)
# Fase 3: platform — PKI de personas, CoreDNS zona persona.internal, dashboards Grafana
# Fase 4: flota de 30 personas — 20 Sintéticas + 10 slots Clonados (30 namespaces + deployments + certs)
scripts/k8s-dut-up.sh up

El script también aplica los parches estratégicos en los deployments de agentes para agregar la interfaz macvlan (net1) y el ConfigMap de la CA del NGFW.

Tiempo estimado: 5–15 minutos dependiendo de la velocidad de descarga de imágenes.

Paso 11C — Verificar el stack DUT

# Ver el estado de los pods DUT
scripts/k8s-dut-up.sh status

# Ver todos los pods del namespace principal
kubectl -n web-agents get pods

# Verificar que los agentes tienen la interfaz net1 (macvlan)
kubectl exec -n web-agents deploy/web-agent -- ip addr show net1

# Verificar las rutas — debe aparecer la VLAN de agentes vía net1
kubectl exec -n web-agents deploy/web-agent -- ip route

# Verificar el certificado TLS del webserver Caddy (Leg 2)
kubectl exec -n web-agents deploy/webserver -- \
  openssl s_client -connect localhost:443 -showcerts 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# Verificar el SNMP exporter puede alcanzar el NGFW
# (reemplaza 192.168.90.2 por la IP de gestión SNMP del NGFW)
kubectl exec -n web-agents deploy/snmp-exporter -- \
  wget -qO- 'http://localhost:9116/snmp?module=cisco_nexus&target=192.168.90.2' \
  | head -20

Paso 12C — Verificar la flota de personas

# Ver todas las personas desplegadas (20 namespaces persona-*)
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster

# Verificar una persona en particular (por ejemplo, "shop")
kubectl -n persona-shop get pods
kubectl -n persona-shop get certificate  # debe mostrar Ready=True

# Ver los NetworkAttachmentDefinitions de las personas
kubectl get net-attach-def -A | grep persona

# Monitorear el arranque de todas las personas en tiempo real
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster -w

Las personas con init containers (como shop) tardan 2–5 minutos adicionales mientras se inicializa el contenido. Esto es normal.

Paso 13C — Crear el secret de la persona shop

La persona shop (Saleor) requiere un secret de base de datos que se crea después de que el namespace persona-shop existe (lo crea la Fase 4).

# Crear el secret de la base de datos de la persona shop
# Reemplaza los valores con los de tu instancia de Postgres para Saleor
kubectl -n persona-shop create secret generic persona-shop-db \
  --from-literal=url='postgres://saleor:saleor_password@postgres.persona-shop.svc.cluster.local:5432/saleor'

# Reiniciar el pod de la persona shop para que tome el secret
kubectl -n persona-shop rollout restart deployment/shop

# Verificar que arranca correctamente
kubectl -n persona-shop rollout status deployment/shop --timeout=120s

Paso 14C — Acceder a Grafana y configurar observabilidad

# Port-forward para Grafana (en una terminal aparte)
kubectl -n monitoring port-forward svc/grafana 3001:80

# Abrir en el navegador: http://localhost:3001
# Login: admin / prom-operator

Dashboards disponibles en Grafana: - TLSStress.Art — throughput, latencia, distribución de protocolos (H2 vs H3) - NGFW DUT — métricas SNMP del firewall (CPU, sesiones, throughput) - Persona Fleet — estado de los 30 webservers persona (20 Sintéticas + 10 slots Clonados)

# Sincronizar el estado de las personas en el dashboard
ADMIN_PASS=$(kubectl -n web-agents get secret dashboard-secrets \
  -o jsonpath='{.data.ADMIN_BASIC_AUTH}' | base64 -d | cut -d: -f2-)

# Port-forward del dashboard en otra terminal si no está activo
# kubectl -n web-agents port-forward svc/dashboard 3000:3000

curl -s -u "admin:$ADMIN_PASS" \
  -X POST http://localhost:3000/api/admin/personas/sync

Paso 15C — Configurar el NGFW (referencia)

Para completar el banco de pruebas, el NGFW debe confiar en los certificados de los webservers Caddy (Leg 2) y estar configurado para hacer SSL inspection.

# Exportar la CA raíz del cert-manager (la que firmó los certs de los webservers)
kubectl get secret persona-ca -n web-agents \
  -o jsonpath='{.data.ca\.crt}' | base64 -d > persona-ca.pem

# Ver el contenido del certificado
openssl x509 -in persona-ca.pem -noout -subject -issuer -dates

# Mostrar los SANs configurados (IPs de webservers personas: 10.1.x.0/27 + 10.2.x.0/27)
openssl x509 -in persona-ca.pem -noout -text | grep -A5 "Subject Alternative"

Importar persona-ca.pem en el NGFW como CA de confianza para validación de certificados de servidor (Leg 2: NGFW → Caddy). Consultar la documentación de tu NGFW para el procedimiento exacto de importación.

Referencia de scripts del Nexus 9000 (switch de datos):

# Aplicar tuning de red al Nexus 9000 (EEE off, MTU 9216, QoS, ECMP)
# Desde NX-OS o vía NAPALM:
# scripts/nexus/01-apply-tuning.nxos

# Verificar configuración aplicada
# scripts/nexus/02-verify.nxos

# Revertir cambios si es necesario
# scripts/nexus/03-rollback.nxos

Comandos del día a día

Gestión de la flota de agentes

# Ver todos los pods del stack principal
kubectl -n web-agents get pods

# Escalar agentes browser-engine (1 a 300)
kubectl -n web-agents scale deployment/web-agent --replicas=50

# Escalar agentes synthetic-load (1 a 1000)
kubectl -n web-agents scale deployment/k6-agent --replicas=100

# Ver el HPA en tiempo real
kubectl -n web-agents get hpa -w

# Ver consumo de recursos por pod
kubectl top nodes
kubectl -n web-agents top pods

# Logs en vivo del dashboard
kubectl -n web-agents logs deploy/dashboard -f

# Logs de todos los agentes browser-engine (máximo 50 streams simultáneos)
kubectl -n web-agents logs \
  -l app.kubernetes.io/name=web-agent -f --max-log-requests 50

# Logs de todos los agentes synthetic-load
kubectl -n web-agents logs \
  -l app.kubernetes.io/name=k6-agent -f --max-log-requests 50

Gestión del dashboard y la base de datos

# Port-forward al dashboard (mantener corriendo en una terminal)
kubectl -n web-agents port-forward svc/dashboard 3000:3000

# Acceso directo a Postgres con psql
kubectl -n web-agents exec -it \
  $(kubectl -n web-agents get pod \
    -l app.kubernetes.io/name=postgres \
    -o jsonpath='{.items[0].metadata.name}') \
  -- psql -U agent_dashboard -d agent_dashboard

# Reiniciar el dashboard sin pérdida de datos
kubectl -n web-agents rollout restart deploy/dashboard

# Reiniciar todos los agentes browser-engine
kubectl -n web-agents rollout restart deploy/web-agent

# Reiniciar todos los agentes synthetic-load
kubectl -n web-agents rollout restart deploy/k6-agent

Consultas de estado y métricas

# Resumen de throughput de los últimos 5 minutos
PG_POD=$(kubectl -n web-agents get pod \
  -l app.kubernetes.io/name=postgres \
  -o jsonpath='{.items[0].metadata.name}')

kubectl -n web-agents exec "$PG_POD" -- \
  psql -U agent_dashboard -d agent_dashboard -c \
  "SELECT
     count(*)                              AS runs_5m,
     round(avg(total_duration_ms))::int   AS avg_ms,
     (sum(transferred_bytes)/1024/1024)::int AS mb
   FROM runs
   WHERE started_at > now() - interval '5 minutes';"

# Agentes activos en los últimos 90 segundos
kubectl -n web-agents exec "$PG_POD" -- \
  psql -U agent_dashboard -d agent_dashboard -c \
  "SELECT count(*) AS live_agents
   FROM agents
   WHERE last_seen_at > now() - interval '90 seconds';"

Gestión de personas (Modo DUT)

# Ver estado de todas las personas
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster

# Ver certificados de una persona
kubectl -n persona-shop get certificate
kubectl -n persona-news get certificate

# Reiniciar una persona específica
kubectl -n persona-shop rollout restart deployment/shop

# Ver logs de una persona
kubectl -n persona-blog logs deploy/blog -f

# Verificar conectividad TLS a una persona desde un agente
kubectl exec -n web-agents deploy/web-agent -- \
  curl -sv --max-time 5 https://shop.persona.internal 2>&1 | grep -E "SSL|TLS|Connected"

Observabilidad (Modo DUT)

# Port-forward a Grafana
kubectl -n monitoring port-forward svc/grafana 3001:80
# Abrir: http://localhost:3001 (admin / prom-operator)

# Port-forward a Prometheus directamente
kubectl -n monitoring port-forward svc/prometheus-kube-prometheus-prometheus 9090:9090
# Abrir: http://localhost:9090

# Ver métricas del SNMP exporter
kubectl -n web-agents port-forward svc/snmp-exporter 9116:9116
# Abrir: http://localhost:9116

# Verificar targets de Prometheus (que todos sean UP)
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health: .health}'

Solución de problemas

Síntoma Causa probable Corrección
kubectl top nodes falla: "metrics not available" metrics-server sin el flag --kubelet-insecure-tls Repetir el kubectl patch del Paso 3 y esperar 60 s
Pods en ImagePullBackOff Imagen no disponible o credenciales GHCR faltantes Verificar acceso a internet; las imágenes son públicas en ghcr.io/nollagluiz/
Pods en Pending por minutos Falta de CPU o RAM en el nodo Reducir --replicas, o revisar kubectl describe pod <pod> para ver el motivo
Dashboard arranca pero da HTTP 500 Migraciones de BD no aplicadas o DATABASE_URL incorrecta Verificar el secret dashboard-secrets y reiniciar con kubectl rollout restart deploy/dashboard
Agentes en Running pero sin ciclos en el dashboard Cluster no habilitado o sin targets registrados Repetir el Paso 9B para registrar targets y activar la flota
Login admin rechazado (401) Contraseña incorrecta Leer desde el secret: kubectl -n web-agents get secret dashboard-secrets -o jsonpath='{.data.ADMIN_BASIC_AUTH}' \| base64 -d
Connection refused en localhost:3000 El kubectl port-forward se interrumpió Volver a ejecutar el port-forward en una terminal activa
Pods DUT en Pending con node(s) didn't match Pod's node affinity Nodo no etiquetado con role=ngfw-dut kubectl label node <nombre> role=ngfw-dut
Agentes sin interfaz net1 Multus no instalado o NADs no aplicados Verificar Multus: kubectl get crd network-attachment-definitions.k8s.cni.cncf.io
Webserver Caddy no inicia: adapt failed Caddyfile DUT mal formado Revisar kubectl -n web-agents logs deploy/webserver --previous
Persona shop en CreateContainerConfigError Secret persona-shop-db faltante en namespace persona-shop Ejecutar el Paso 13C para crear el secret
preflight: N pre-flight check(s) failed Prereqs del Modo DUT incompletos Leer el output de scripts/k8s-dut-up.sh preflight y corregir cada FAIL
Certificados de persona en estado False cert-manager no tiene el ClusterIssuer listo kubectl get clusterissuer persona-ca-issuer — si no existe, aplicar kubectl apply -k platform/
SNMP exporter retorna vacío o error IP del NGFW incorrecta o comunidad SNMP equivocada Verificar el secret dut-snmp-secrets y la accesibilidad de red al NGFW
CoreDNS no resuelve *.persona.internal El parche de CoreDNS no se aplicó bash platform/dns/patch-coredns.sh y kubectl -n kube-system rollout restart deploy/coredns

Importación air-gapped (opcional)

Si el servidor k3s no tiene acceso a internet, importa las imágenes desde una máquina que sí lo tenga:

# En la máquina con internet — descargar y guardar las imágenes
docker pull ghcr.io/nollagluiz/web-agent-agent:v3.7.0
docker pull ghcr.io/nollagluiz/web-agent-dashboard:v3.7.0
docker pull ghcr.io/nollagluiz/web-agent-k6agent:v3.7.0
docker pull ghcr.io/nollagluiz/web-agent-webserver:v3.7.0

docker save \
  ghcr.io/nollagluiz/web-agent-agent:v3.7.0 \
  ghcr.io/nollagluiz/web-agent-dashboard:v3.7.0 \
  ghcr.io/nollagluiz/web-agent-k6agent:v3.7.0 \
  ghcr.io/nollagluiz/web-agent-webserver:v3.7.0 \
  -o web-agent-images.tar

# Copiar al servidor k3s
scp web-agent-images.tar user@servidor-k3s:/tmp/

# En el servidor k3s — importar en containerd
sudo k3s ctr images import /tmp/web-agent-images.tar

# Verificar que las imágenes están disponibles
sudo k3s ctr images ls | grep nollagluiz

Limpieza

Escalar a cero (mantiene la instalación)

# Detener todos los agentes sin borrar nada
kubectl -n web-agents scale deployment/web-agent --replicas=0
kubectl -n web-agents scale deployment/k6-agent --replicas=0
kubectl -n web-agents scale deployment/dashboard --replicas=0

Eliminar el overlay DUT (volver a Modo Simple)

# Revertir el Modo DUT y restaurar los deployments base
scripts/k8s-dut-up.sh down

# Eliminar las subinterfaces VLAN del host (DUT)
sudo scripts/netsetup-dut.sh teardown

# Eliminar las subinterfaces VLAN de las personas
sudo scripts/netsetup-personas.sh teardown

Eliminar el namespace principal (borrar datos)

# Esto borra todos los pods, secrets, PVCs y datos de Postgres
kubectl delete namespace web-agents

Eliminar las personas (Modo DUT)

# Borrar los 20 namespaces de personas
kubectl delete -k personas/

# O uno por uno
kubectl delete namespace persona-shop persona-news persona-blog

Desinstalar k3s completamente

# Esto borra k3s, containerd, todos los pods y la configuración
sudo /usr/local/bin/k3s-uninstall.sh

Referencias

Recurso Ubicación
Manifests Kubernetes base k8s/
Overlay DUT k8s/dut/
Plataforma (PKI, DNS, observabilidad) platform/
Flota de personas personas/
Script de despliegue DUT scripts/k8s-dut-up.sh
Script de VLANs DUT scripts/netsetup-dut.sh
Script de VLANs personas scripts/netsetup-personas.sh
Scripts Nexus 9000 scripts/nexus/
README del overlay DUT k8s/dut/README.md
README principal README.md

Cloner de sitios públicos (single-node)

Objetivo

El cloner descarga sitios públicos reales vía VLAN 40 (acceso directo a internet, sin pasar por el NGFW) y sirve el contenido clonado localmente. Permite que la flota de agentes ejercite el NGFW con contenido real de sitios conocidos.

Almacenamiento en single-node

En single-node todos los pods (cloner + 10 slots cloned-persona + servidor NFS) aterrizan en el único nodo. El volumen compartido cloned-sites lo sirve el servidor NFS in-cluster (k8s/dut/35-nfs-server.yaml) — como cliente y servidor están co-localizados, el round-trip NFS es loopback y el overhead es despreciable. El hostPath /var/lib/agent-cluster/cloned-sites bajo el servidor NFS guarda la copia durable.

La misma arquitectura se extiende trivialmente a multi-node — vea UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.es.md para las particularidades (NFS sobre OOBI, cloner fijado en UCS-4).

Prerequisitos en el Nexus 9000

VLAN 40 en el trunk del servidor:

conf t
  vlan 40
    name cloner-isp-egress
  interface Ethernet1/<N>
    switchport trunk allowed vlan add 40

Verificación

kubectl get pod -n web-agents -l app.kubernetes.io/name=cloner -o wide
kubectl get pod -n web-agents -l app.kubernetes.io/name=nfs-server -o wide
kubectl get ds cni-dhcp-daemon -n web-agents
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

Véase también


Tuning del host — OBLIGATORIO para las stacks persona

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.

En single-node todos los workloads (personas, slots, agentes, dashboard, Cloner, NFS server) están en el único nodo — el script corre una vez:

# Aplica sysctls + módulos + CPU governor + cpuManagerPolicy: static
sudo scripts/host-tuning.sh apply --enable-cpu-pinning

# Verificar (reporte coloreado)
sudo scripts/host-tuning.sh status

# Deshacer
sudo scripts/host-tuning.sh remove --enable-cpu-pinning

--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.

En multi-node (UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.es.md) el script corre en CADA host UCS. Referencia completa en PERFORMANCE_TUNING_HOST.md.


Soporte

Para dudas o problemas con estas instrucciones, abre un issue en el repositorio o contacta al autor:

André Luiz Gallon — agallon@Cisco.com


© 2026 André Luiz Gallon — Distribuido bajo PolyForm Noncommercial 1.0.0 con Restricciones Adicionales de Uso (Apéndice A).