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 --helppara ver todos los parámetros disponibles, incluyendo--dry-runpara 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@servidorpara 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
/loginy 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-credentialsestá 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
desiredAgentCountconfigurado 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¶
docs/CLONER.md— referencia completa de arquitecturadocs/CLONER_OPERATIONS.md— playbook operativo
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).