TLSStress.Art — Deploy Single-Node em Ubuntu com k3s¶
Modo de deploy: nó único (single-node) — todos os componentes (agentes, webservers, dashboard, observabilidade) rodam em um único servidor Ubuntu. É a forma mais rápida de começar. Para um deploy multi-servidor onde cada camada tem uma máquina dedicada, consulte
UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.pt-BR.md.Última verificação contra o código de produção: v3.7.0 (2026-05-12) — Ver ARCHITECTURE.md para os 37 MÓDULOs canônicos + 7 Test Kinds + arquitetura de safety DOM/CPOS/PIE-PA + a postura ZTP-prem 12/12 camadas contra operador insider (25 reivindicações de patente, partição Tier A/B, Confidential Computing, audit log selado em hash-chain, webhook de admission K8s, TPM 2.0 measured-boot, monitor DLP de egress, detector comportamental de anomalias). ADRs 0014, 0019-0025 cobrem adições pós-Freeze.
Objetivo: instalar a stack completa do TLSStress.Art em um único servidor Ubuntu usando k3s — incluindo Dashboard, Postgres, frota browser engine (até 300 agentes), frota synthetic-load engine (até 1.000 agentes), 30 webservers persona com TLS (20 Sintéticos + 10 slots Clonados), Grafana e Prometheus — e, opcionalmente, colocar um NGFW físico no meio do tráfego para medir performance de inspeção TLS (modo DUT).
Para quem é este guia: engenheiros de rede e de sistemas que sabem abrir um terminal Linux e colar comandos. Não é necessária experiência prévia com Kubernetes.
Tempo estimado: Modo Simples: 30–45 min | Modo DUT completo: 90–120 min.
Autoria: André Luiz Gallon — agallon@Cisco.com
Instalação automatizada (recomendada)¶
Se quiser começar rapidamente, use o script de instalação automatizada. Ele instala k3s, Helm, cert-manager, Multus, configura as interfaces VLAN e aplica todos os manifests Kubernetes na ordem correta — sem precisar de conhecimento prévio em Kubernetes:
# Clone o repositório
git clone https://github.com/nollagluiz/AI_forSE.git
cd AI_forSE
# Single-node: tudo em um único servidor
sudo ./scripts/k8s-install.sh --mode=single --data-iface=eth1
O script irá:
1. Validar pré-requisitos (SO, RAM, disco, interfaces de rede)
2. Instalar k3s, Helm, cert-manager e Multus CNI
3. Configurar subinterfaces VLAN em eth1 (VLANs 20, 30, 99, 101–120)
4. Aplicar todos os manifests Kubernetes na ordem correta
5. Aguardar os pods ficarem prontos e exibir a URL do Dashboard
Opções: execute
sudo ./scripts/k8s-install.sh --helppara ver todas as flags disponíveis, incluindo--dry-runpara visualizar o que seria executado sem fazer nenhuma alteração.
O restante deste guia explica cada etapa em detalhes, útil para personalizar a instalação ou depurar problemas.
Escolha o modo de instalação¶
| Modo Simples | Modo DUT | |
|---|---|---|
| Para que serve | Lab básico, teste de alcançabilidade e throughput contra sites reais da internet | Banco de testes de NGFW: mede custo real de inspeção TLS com HTTP/2 e HTTP/3 |
| Frota browser engine | ✅ | ✅ |
| Frota synthetic-load engine | ✅ | ✅ |
| 30 webservers persona (20 Sintéticos + 10 slots Clonados) | ❌ | ✅ |
| NGFW (DUT) no caminho | ❌ | ✅ |
| Grafana + Prometheus | Opcional | ✅ |
| cert-manager + PKI | ✅ (requerido) | ✅ |
| Multus CNI + macvlan | ❌ | ✅ |
| NICs necessárias | 1 | 2 (eth0 gestão + eth1 trunk VLAN) |
| Script de orquestração | Manual | scripts/k8s-dut-up.sh up |
Pré-requisitos¶
| Item | Modo Simples | Modo DUT |
|---|---|---|
| Sistema operacional | Ubuntu 22.04 LTS ou 24.04 LTS | mesmo |
| Arquitetura | x86_64 ou arm64 | x86_64 (UCS/baremetal recomendado) |
| RAM | 16 GB (até ~50 agentes) | 64 GB+ |
| vCPU | 4 | 16+ |
| Disco | 60 GB livres | 200 GB+ (personas + logs + métricas) |
| NICs | 1 (eth0) | 2 (eth0=gestão k8s, eth1=trunk 802.1q) |
| Switch | — | Nexus 9000 com VLAN trunk (VLANs 20, 30, 99, 101–120) |
| NGFW | — | Cisco FTD/IOS-XE, FortiGate, Palo Alto, Check Point, Huawei, etc. |
| Acesso ao host | usuário com sudo |
mesmo |
| Internet (bootstrap) | Sim (k3s, imagens GHCR) | Sim (ou offline — veja Passo 4C) |
Parte A — Preparação do host (ambos os modos)¶
Passo 0 — Pacotes base¶
sudo apt update && sudo apt install -y \
curl ca-certificates iptables openssl jq git python3 iproute2 vlan
Passo 1 — Instalar k3s¶
Modo Simples:
curl -sfL https://get.k3s.io | \
INSTALL_K3S_EXEC="--disable=traefik --write-kubeconfig-mode=644" sh -
Modo DUT (com Multus CNI para macvlan):
curl -sfL https://get.k3s.io | \
INSTALL_K3S_EXEC="--disable=traefik --write-kubeconfig-mode=644 \
--flannel-iface=eth0" sh -
# Multus CNI — necessário para net1 (macvlan VLAN data plane)
kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset-thick.yml
kubectl -n kube-system wait --for=condition=ready \
pod -l app=multus --timeout=120s
Verificar que o nó subiu:
sudo systemctl status k3s --no-pager | head -5
sudo k3s kubectl get nodes
Esperado: STATUS=Ready, ROLES=control-plane,master.
Configurar kubectl sem sudo:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
chmod 600 ~/.kube/config
echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
source ~/.bashrc
kubectl get nodes
Passo 2 — Instalar o Ingress Nginx¶
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml
kubectl -n ingress-nginx wait --for=condition=available \
deployment/ingress-nginx-controller --timeout=180s
Em um nó single-node o
EXTERNAL-IPpode ficar<pending>— normal; acessaremos o dashboard viakubectl port-forward.
Passo 3 — Instalar o metrics-server¶
O HPA precisa de métricas de CPU/memória para escalar automaticamente.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# k3s usa certificado self-signed no kubelet; o metrics-server precisa dessa flag:
kubectl -n kube-system patch deployment metrics-server --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl -n kube-system wait --for=condition=available \
deployment/metrics-server --timeout=180s
# Validar — deve aparecer CPU/MEM em segundos:
kubectl top nodes
Passo 4 — Instalar cert-manager¶
Necessário em ambos os modos (gerencia TLS interno do cluster e, no modo DUT, emite certificados para todas as 30 personas (20 Sintéticas + 10 slots Clonados) via PKI interna).
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.0/cert-manager.yaml
kubectl -n cert-manager wait --for=condition=available \
deployment --all --timeout=180s
# Confirmar os 3 pods do cert-manager em Running:
kubectl -n cert-manager get pods
Passo 5 — Clonar o projeto¶
git clone https://github.com/nollagluiz/AI_forSE.git
cd AI_forSE
# Usar sempre a branch main
Parte B — Modo Simples (agentes vs. sites da internet)¶
Neste modo os agentes browser engine e synthetic-load engine navegam sites reais da internet. Não há personas, NGFW nem macvlan. Ideal para validar a stack rapidamente antes de montar o hardware DUT.
Passo 6B — Criar segredos¶
# Namespace
kubectl create namespace web-agents
# Postgres
DB_PASS=$(openssl rand -hex 24)
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 compartilhado entre dashboard e agentes browser-engine
TOKEN=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic web-agent-secrets \
--from-literal=CONTROLLER_TOKEN="$TOKEN"
# Token para agentes synthetic-load
K6_SECRET=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic k6-agent-secrets \
--from-literal=DASHBOARD_SECRET="$K6_SECRET"
# Dashboard
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"
echo "================================================"
echo " Login admin do dashboard:"
echo " Usuário: admin"
echo " Senha: $ADMIN_PASS"
echo "================================================"
Salve a senha admin — você vai precisar para abrir a UI.
Passo 7B — Aplicar os manifests base¶
Os manifests já apontam para as imagens corretas em ghcr.io/nollagluiz/.
Não é necessário fazer build local — o k3s vai puxar automaticamente do GHCR.
# Aplica todos os manifests em k8s/ (exceto os que ficam no DUT overlay)
# Os Secrets embutidos nos arquivos são ignorados — já criamos os reais no Passo 6B
for f in k8s/00-namespace.yaml \
k8s/05-resource-quota.yaml \
k8s/10-agent-config.yaml \
k8s/11-k6-agent-config.yaml \
k8s/20-agent-deployment.yaml \
k8s/21-k6-agent-deployment.yaml \
k8s/40-postgres.yaml \
k8s/42-pgbouncer.yaml \
k8s/50-dashboard.yaml \
k8s/52-dashboard-persona-rbac.yaml \
k8s/55-dashboard-pdb.yaml; do
kubectl apply -f "$f" --server-side 2>/dev/null || kubectl apply -f "$f"
done
Passo 8B — Esperar os pods subirem¶
kubectl -n web-agents wait --for=condition=ready \
pod -l app.kubernetes.io/name=postgres --timeout=180s
kubectl -n web-agents wait --for=condition=available \
deployment/dashboard --timeout=240s
kubectl -n web-agents get pods
Esperado: postgres-0, dashboard-* e web-agent-* todos Running.
Passo 9B — Aplicar migrações do banco¶
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 -i "$PG_POD" -- \
psql -U agent_dashboard -d agent_dashboard \
-c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;'
for f in dashboard/src/db/migrations/*.sql; do
echo "==> aplicando $f"
kubectl -n web-agents exec -i "$PG_POD" -- \
psql -U agent_dashboard -d agent_dashboard -v ON_ERROR_STOP=1 < "$f"
done
Passo 10B — Abrir o dashboard¶
Em outro terminal:
kubectl -n web-agents port-forward svc/dashboard 3000:3000
- Desktop local: http://localhost:3000/
- Servidor remoto:
ssh -L 3000:localhost:3000 user@servidor.ubuntu→ http://localhost:3000/
Login: admin + a senha do Passo 6B.
Passo 11B — Cadastrar sites e ligar a frota¶
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"
# Adicionar sites alvo (exemplos):
for url in https://www.g1.globo.com https://www.uol.com.br https://www.nasa.gov; do
curl -s -u "$ADMIN" -H 'content-type: application/json' \
-d "{\"url\":\"$url\",\"weight\":1}" \
http://localhost:3000/api/admin/targets; echo
done
# Habilitar frota browser-engine com 5 agentes:
curl -s -u "$ADMIN" -X PUT -H 'content-type: application/json' \
-d '{"enabled":true,"cycleIntervalMs":30000,"desiredAgentCount":5}' \
http://localhost:3000/api/admin/config
Passo 12B — Escalar (até 300 browser engine, até 1.000 synthetic-load engine)¶
# Frota browser-engine — escala manual:
kubectl -n web-agents scale deployment/web-agent --replicas=50
# Frota synthetic-load — escala manual:
kubectl -n web-agents scale deployment/k6-agent --replicas=100
# Ou deixar o HPA decidir conforme CPU (já configurado nos manifests):
kubectl -n web-agents get hpa -w
Cada agente browser engine reserva 512 MB de RAM. Dimensione conforme o hardware. Cada agente synthetic-load engine reserva apenas 128 MB (Go runtime, sem browser).
Parte C — Modo DUT (banco de testes NGFW completo)¶
Este modo coloca um NGFW físico entre os agentes e os 30 webservers persona (20 Sintéticos + 10 slots Clonados). Todo tráfego HTTPS passa pelo firewall, que decripta, inspeciona e reencripta cada conexão. A stack mede o custo real dessa inspeção.
Arquitetura resumida:
Agentes (browser-engine VLAN 20 / synthetic-load VLAN 30) │ TLS leg 1: agentes → NGFW (NGFW apresenta seu cert) ▼ NGFW (Device Under Test) │ TLS leg 2: NGFW → Caddy persona (cert emitido pelo cert-manager) ▼ 20 Personas Sintéticas (VLANs 101–120, 10.1.x.0/27) 10 slots de Personas Clonadas (VLANs 200–209, 10.2.{1..10}.0/27)
Passo 5C — Dependências extras para DUT¶
O Multus já foi instalado no Passo 1. Instale agora Helm e o stack de observabilidade (Prometheus + Grafana):
# Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Prometheus + Grafana (kube-prometheus-stack)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace monitoring
helm install kube-prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--set grafana.adminPassword=prom-operator \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false \
--timeout 10m
kubectl -n monitoring wait --for=condition=available deployment --all --timeout=300s
Passo 6C — Configurar a CA do NGFW¶
Os agentes precisam confiar nos certificados apresentados pelo NGFW durante a inspeção TLS. Exporte o bundle CA do seu NGFW e coloque-o no ConfigMap:
# Exporte a CA do NGFW em PEM — o processo varia por fabricante:
# Cisco FTD: Devices → Certificates → Internal CA → Export Chain
# FortiGate: System → Certificates → Export
# Palo Alto: Device → Certificate Management → Certificates → Export
# Check Point: SmartConsole → Gateways → Edit → HTTPS Inspection → Export CA
# Cole o conteúdo PEM (incluindo -----BEGIN CERTIFICATE-----)
# em k8s/dut/10-ngfw-ca.yaml, campo data.ca.crt
${EDITOR:-nano} k8s/dut/10-ngfw-ca.yaml
Passo 7C — Criar todos os segredos¶
# Namespace (pode já existir se fez o Modo Simples antes)
kubectl create namespace web-agents --dry-run=client -o yaml | kubectl apply -f -
# Postgres
DB_PASS=$(openssl rand -hex 24)
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 \
--dry-run=client -o yaml | kubectl apply -f -
# Token browser engine
TOKEN=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic web-agent-secrets \
--from-literal=CONTROLLER_TOKEN="$TOKEN" \
--dry-run=client -o yaml | kubectl apply -f -
# Token synthetic-load engine
K6_SECRET=$(openssl rand -hex 32)
kubectl -n web-agents create secret generic k6-agent-secrets \
--from-literal=DASHBOARD_SECRET="$K6_SECRET" \
--dry-run=client -o yaml | kubectl apply -f -
# Dashboard
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" \
--dry-run=client -o yaml | kubectl apply -f -
# SNMP — comunidade do seu NGFW (padrão geralmente "public" em lab)
SNMP_COMMUNITY="public" # Altere conforme seu NGFW
kubectl -n web-agents create secret generic dut-snmp-secrets \
--from-literal=SNMP_COMMUNITY="$SNMP_COMMUNITY" \
--dry-run=client -o yaml | kubectl apply -f -
echo "================================================"
echo " Login admin do dashboard:"
echo " Usuário: admin"
echo " Senha: $ADMIN_PASS"
echo "================================================"
Passo 8C — Rotular o nó DUT¶
O nodeSelector role: ngfw-dut garante que personas e webserver rodem no nó
com a NIC trunk:
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl label node "$NODE" role=ngfw-dut
kubectl get node "$NODE" --show-labels | grep ngfw
Passo 9C — Subir o stack completo com k8s-dut-up.sh¶
O script orquestra as 4 fases de forma idempotente:
# Verificação de pré-requisitos (recomendado antes de subir):
bash scripts/k8s-dut-up.sh preflight
# Subir o stack completo (pode levar 10–20 min na primeira vez):
bash scripts/k8s-dut-up.sh up
O que o script faz:
| Fase | O que aplica |
|---|---|
| Phase 1 | kubectl apply -f k8s/ — namespace, Postgres, Dashboard, agentes browser engine e synthetic-load engine, PgBouncer, Prometheus rules |
| Phase 2 | kubectl apply -k k8s/dut/ — CA do NGFW, NetworkAttachmentDefinitions macvlan, webserver Caddy DUT, SNMP exporter, NetworkPolicy DUT, PKI TLS local, Caddyfile DUT, node tuning DaemonSet, Stakater Reloader; patches estratégicos para browser engine e synthetic-load engine |
| Phase 3 | kubectl apply -k platform/ — ClusterIssuer PKI personas, cert-manager emite certs para todas as 30 personas (20 Sintéticas + 10 slots Clonados); CoreDNS patch zona persona.internal; dashboards Grafana; Aguarda Secret persona-ca-bundle |
| Phase 4 | kubectl apply -k personas/ — 20 namespaces, deployments Caddy, NetworkAttachmentDefinitions, TLS Certificates; scripts/netsetup-personas.sh setup — cria VLAN subinterfaces + roteamento no host |
Passo 10C — Verificar a frota de personas¶
# Verificar pods das personas (todos devem estar Running após ~5 min):
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster \
--field-selector=status.phase!=Running 2>/dev/null | head -20
# Verificar certificados TLS emitidos:
kubectl get certificate -A | grep -v "True" | head -20
# Testar resolução DNS das personas:
kubectl run -it --rm dns-test --image=busybox --restart=Never \
-- nslookup shop.persona.internal
# Esperado: Address: 10.1.1.2
# Listar interfaces macvlan criadas no host:
ip link show | grep -E "vlan|macvlan" | head -20
Passo 11C — Criar o Secret do Saleor (persona shop)¶
A persona shop executa o e-commerce Saleor, que precisa de um banco Postgres
dedicado. Crie o Secret após a Phase 4 (o namespace persona-shop só
existe depois):
# Crie um banco Postgres para o Saleor (pode usar o mesmo postgres do cluster
# ou um externo). Exemplo usando o Postgres do cluster:
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 -i "$PG_POD" -- \
psql -U agent_dashboard -d agent_dashboard <<SQL
CREATE DATABASE saleor;
CREATE USER saleor WITH PASSWORD 'saleor_dev_pass';
GRANT ALL PRIVILEGES ON DATABASE saleor TO saleor;
SQL
kubectl -n persona-shop create secret generic persona-shop-db \
--from-literal=url="postgres://saleor:saleor_dev_pass@postgres.web-agents.svc.cluster.local:5432/saleor"
# Reiniciar o pod da persona shop para pegar o Secret:
kubectl -n persona-shop rollout restart deployment/caddy
Passo 12C — Acessar o Grafana e o Dashboard¶
# Grafana (métricas de throughput TLS, latência por persona, CPU do NGFW via SNMP):
kubectl -n monitoring port-forward svc/kube-prometheus-grafana 3001:80
# Dashboard Web Agent:
kubectl -n web-agents port-forward svc/dashboard 3000:3000
- Grafana: http://localhost:3001 — login
admin/prom-operator - Dashboard "Persona Fleet Overview" — throughput por persona
- Dashboard "DUT NGFW" — CPU/memória do NGFW via SNMP
- Dashboard Web Agent: http://localhost:3000 — login
admin/ senha do Passo 7C
Passo 13C — Configurar o NGFW (Nexus 9000)¶
Configure o switch para criar o trunk com os VLANs necessários:
# Aplicar configuração de QoS, MTU 9216 e VLANs no Nexus 9000:
# (requer SSH/NX-OS CLI ou Ansible)
# Scripts prontos em scripts/nexus/:
# scripts/nexus/01-apply-tuning.nxos — aplica QoS, ECMP, EEE off
# scripts/nexus/02-verify.nxos — verifica configuração
# scripts/nexus/03-rollback.nxos — reverte se necessário
# Exemplo via SSH:
ssh admin@nexus-9000 'copy running-config startup-config'
Consulte o guia completo em docs/DUT_TESTBED.md.
Passo 14C — Ligar a frota e iniciar os testes¶
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"
# Habilitar frota browser-engine apontando para personas internas:
curl -s -u "$ADMIN" -X PUT -H 'content-type: application/json' \
-d '{"enabled":true,"cycleIntervalMs":15000,"desiredAgentCount":50}' \
http://localhost:3000/api/admin/config
# Iniciar teste synthetic-load engine de carga (ex: 200 agentes por 10 min):
kubectl -n web-agents scale deployment/k6-agent --replicas=200
Passo 15C — Validação completa do modo DUT¶
# Pods de todas as personas em Running:
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster | grep -v Running
# Agentes browser-engine e synthetic-load engine conectando (deve mostrar logs de ciclos):
kubectl -n web-agents logs -l app.kubernetes.io/name=web-agent \
--tail=5 --prefix 2>/dev/null | grep -i "cycle\|success\|http"
kubectl -n web-agents logs -l app.kubernetes.io/name=k6-agent \
--tail=5 --prefix 2>/dev/null | grep -i "run\|result\|http"
# Throughput no banco (últimos 5 min):
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 -i "$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';"
# Métricas SNMP do NGFW (via Grafana dashboard "DUT NGFW"):
kubectl -n web-agents port-forward svc/snmp-exporter 9116:9116 &
curl -s "http://localhost:9116/snmp?target=<IP_DO_NGFW>&module=cisco_ftd" | \
grep -E "cpu|memory|conn" | head -10
Imagens de container¶
Todas as imagens são públicas em ghcr.io/nollagluiz/ e suportam amd64
e arm64. Nenhum build local é necessário.
# Puxar manualmente (opcional — o k3s faz isso automaticamente):
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
Ambiente air-gapped (sem internet no cluster):
# Na máquina com internet:
for img in \
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; do
docker pull "$img"
docker save "$img" -o "$(basename ${img%:*}).tar"
done
# Copiar os .tar para o servidor e importar no containerd do k3s:
sudo k3s ctr images import web-agent-agent.tar
sudo k3s ctr images import web-agent-dashboard.tar
sudo k3s ctr images import web-agent-k6agent.tar
sudo k3s ctr images import web-agent-webserver.tar
Comandos do dia a dia¶
# ── Status geral ──────────────────────────────────────────────────────────
kubectl -n web-agents get pods
kubectl get pods -A -l app.kubernetes.io/part-of=web-agent-cluster
# ── Agentes browser-engine ────────────────────────────────────────────────────
kubectl -n web-agents scale deployment/web-agent --replicas=N
kubectl -n web-agents rollout restart deployment/web-agent
kubectl -n web-agents logs -l app.kubernetes.io/name=web-agent -f --max-log-requests 50
# ── Agentes synthetic-load ────────────────────────────────────────────────────────────
kubectl -n web-agents scale deployment/k6-agent --replicas=N
kubectl -n web-agents logs -l app.kubernetes.io/name=k6-agent -f --max-log-requests 50
# ── Dashboard ─────────────────────────────────────────────────────────────
kubectl -n web-agents port-forward svc/dashboard 3000:3000
kubectl -n web-agents rollout restart deployment/dashboard
kubectl -n web-agents logs deploy/dashboard -f
# ── Grafana (modo DUT) ────────────────────────────────────────────────────
kubectl -n monitoring port-forward svc/kube-prometheus-grafana 3001:80
# ── Personas ──────────────────────────────────────────────────────────────
# Ver status de todos os pods de personas:
kubectl get pods -A -l persona --no-headers | sort -k1
# Reiniciar uma persona específica:
kubectl -n persona-shop rollout restart deployment/caddy
# Ver cert TLS de uma persona:
kubectl -n persona-shop get certificate persona-shop-tls
# ── HPA (autoscaler) ──────────────────────────────────────────────────────
kubectl -n web-agents get hpa -w
# ── Postgres ──────────────────────────────────────────────────────────────
PG=$(kubectl -n web-agents get pod -l app.kubernetes.io/name=postgres \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n web-agents exec -it "$PG" -- psql -U agent_dashboard -d agent_dashboard
# ── Uso de recursos ───────────────────────────────────────────────────────
kubectl top nodes
kubectl -n web-agents top pods
Problemas comuns¶
| Sintoma | Causa provável | Solução |
|---|---|---|
Pods em ImagePullBackOff |
GHCR inacessível ou imagem não existe | Verificar tag da imagem; usar import manual (ver seção Imagens) |
kubectl top nodes falha |
metrics-server sem --kubelet-insecure-tls |
Repetir o kubectl patch do Passo 3 e aguardar 60 s |
Pods em Pending por minutos |
Sem CPU/RAM no nó | Reduzir --replicas ou requests em k8s/20-agent-deployment.yaml |
| Dashboard HTTP 500 nas APIs | Migrações não aplicadas | Repetir Passo 9B |
| Agentes aparecem mas sem ciclos | Frota não habilitada ou sem targets | Repetir Passo 11B/14C |
| Login admin recusado | Senha errada | kubectl -n web-agents get secret dashboard-secrets -o jsonpath='{.data.ADMIN_BASIC_AUTH}' \| base64 -d |
nslookup shop.persona.internal falha |
CoreDNS não foi patchado | bash platform/dns/patch-coredns.sh |
Personas em CrashLoopBackOff |
cert TLS não emitido ainda | kubectl get certificate -A; aguardar cert-manager emitir (~60 s) |
Pod persona-shop em CreateContainerConfigError |
Secret persona-shop-db ausente |
Criar o Secret (Passo 11C) |
DaemonSet node-tuning rejeitado pelo PSA |
Label PSA ausente no namespace | Aplicar kubectl apply -k k8s/dut/ (inclui 00-namespace-psa.yaml) |
| Interfaces macvlan não criadas | scripts/netsetup-personas.sh não rodou |
bash scripts/netsetup-personas.sh setup |
| Tráfego não passa pelo NGFW | Roteamento VLAN errado no Nexus | Verificar trunk 802.1q e rotas — ver scripts/nexus/02-verify.nxos |
| Métricas SNMP ausentes no Grafana | Comunidade SNMP errada | Atualizar dut-snmp-secrets e reiniciar snmp-exporter |
| Pods Caddy com cert expirado após renovação | Stakater Reloader não instalado ou annotation ausente | Verificar kubectl -n web-agents get deploy/reloader; re-aplicar k8s/dut/ |
Limpeza¶
# Pausar agentes (mantém tudo instalado, zero consumo):
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
# Remover personas (modo DUT):
kubectl delete -k personas/
# Remover plataforma (PKI, observabilidade):
kubectl delete -k platform/ 2>/dev/null || true
# Remover DUT overlay:
kubectl delete -k k8s/dut/ 2>/dev/null || true
# Remover stack base:
kubectl delete namespace web-agents
# Remover interfaces macvlan do host:
bash scripts/netsetup-personas.sh teardown
# Desinstalar k3s completamente:
sudo /usr/local/bin/k3s-uninstall.sh
Referências dentro do repositório¶
| Arquivo | Conteúdo |
|---|---|
docs/DUT_TESTBED.md |
Guia completo de montagem física do NGFW test-bed |
docs/ARCHITECTURE.md |
Diagrama de componentes e topologia |
docs/K6_FLEET.md |
Operações da frota synthetic-load engine |
docs/para-que-serve.md |
O que o projeto faz (PT) |
k8s/ |
Manifests base (namespace, agentes, dashboard, Postgres) |
k8s/dut/ |
Overlay DUT (macvlan, PKI, SNMP, tuning) |
platform/ |
PKI personas, CoreDNS, dashboards Grafana |
personas/_generated/ |
20 Personas Sintéticas geradas (Caddy + cert + NetworkAttachmentDefinition) |
k8s/clone-personas/ |
10 slots de Personas Clonadas (Caddy stub + NetworkAttachmentDefinition + Certificate; conteúdo fornecido em runtime pelo Cloner) |
scripts/k8s-dut-up.sh |
Script de orquestração das 4 fases |
scripts/netsetup-personas.sh |
Cria/remove VLAN subinterfaces no host |
scripts/nexus/ |
Scripts NX-OS para configurar o Nexus 9000 |
dashboard/src/db/migrations/ |
Migrações SQL do banco |
Cloner de sites públicos (single-node)¶
Objetivo¶
O cloner baixa sites públicos reais via VLAN 40 (acesso direto à internet, sem passar pelo NGFW) e serve o conteúdo clonado localmente. Permite que a frota de agentes teste conteúdo real através do NGFW.
Storage em single-node¶
Em single-node todos os pods (cloner + 10 slots cloned-persona + NFS server) caem no único nó. O volume compartilhado cloned-sites é servido pelo NFS server in-cluster (k8s/dut/35-nfs-server.yaml) — como cliente e servidor estão co-localizados, o round-trip NFS é loopback e o overhead é desprezível. O hostPath /var/lib/agent-cluster/cloned-sites debaixo do NFS server guarda a cópia durável.
A mesma arquitetura se estende trivialmente para multi-node — veja UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.pt-BR.md para as particularidades (NFS sobre OOBI, cloner fixado em UCS-4).
Pré-requisitos no Nexus 9000¶
VLAN 40 no trunk do servidor:
conf t
vlan 40
name cloner-isp-egress
interface Ethernet1/<N>
switchport trunk allowed vlan add 40
Verificação¶
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
Ver também¶
docs/CLONER.md— referência completa de arquiteturadocs/CLONER_OPERATIONS.md— playbook operacional
Tuning do host — OBRIGATÓRIO para as stacks persona¶
As stacks de Synthetic Personas e Cloned Personas exigem este tuning pra entregar o throughput de teste alvo. Sem ele, os buffers UDP do kernel limitam QUIC a ~30 Mbps por réplica, e o cwnd do TCP é resetado a cada janela idle de HTTP/2 (~1 ms RTT extra por cycle).
O DaemonSet node-tuning aplica os mesmos sysctls em runtime, mas não sobrevive a reboot até o pod voltar a subir. O script scripts/host-tuning.sh grava os valores em /etc/sysctl.d/, instala um systemd unit pro CPU governor, e (opcionalmente) liga cpuManagerPolicy: static pra alocação exclusiva de cores.
Em single-node todos os workloads (personas, slots, agentes, dashboard, Cloner, NFS server) ficam no único nó — script roda uma vez:
# Aplica sysctls + módulos + CPU governor + cpuManagerPolicy: static
sudo scripts/host-tuning.sh apply --enable-cpu-pinning
# Verificar (relatório colorido)
sudo scripts/host-tuning.sh status
# Desfazer
sudo scripts/host-tuning.sh remove --enable-cpu-pinning
--enable-cpu-pinning ativa cpuManagerPolicy: static no kubelet (k3s e vanilla auto-detectados) e reinicia o kubelet — planejar janela. Sem a flag o script ainda aplica sysctls, módulos, governor e THP.
No multi-node (UBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.pt-BR.md) o script roda em CADA host UCS. Referência completa em PERFORMANCE_TUNING_HOST.md.
Suporte¶
Em caso de dúvida ou problema, abra uma issue no repositório:
André Luiz Gallon — agallon@Cisco.com
© 2026 André Luiz Gallon — Distribuído sob PolyForm Noncommercial 1.0.0 com Restrições Adicionais de Uso (Apêndice A).