Skip to content

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 --help para ver todas as flags disponíveis, incluindo --dry-run para 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-IP pode ficar <pending> — normal; acessaremos o dashboard via kubectl 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


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