TLSStress.Art — Deployment Dual-Node em 2 Servidores UCS¶
Modo de deployment: dual-node — UCS-1 roda a frota de agentes (browser-engine + synthetic-load) e UCS-2 roda todo o restante (personas, serviços, observabilidade). Para um setup de servidor único veja
UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.pt-BR.md; para a topologia distribuída de quatro servidores vejaUBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.pt-BR.md.Objetivo: Subir um TLSStress.Art distribuído em exatamente dois servidores Cisco UCS — um dedicado à geração de carga, outro dedicado a webservers + serviços + observabilidade — para oferecer um ponto de escala intermediário entre single-node e multi-node de quatro servidores.
Ú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.
Para quem é: Engenheiros de rede e sistemas operando um test bed de NGFW que dispõem de dois chassis UCS e querem isolamento de hardware entre os geradores de carga e os webservers sob medição.
Tempo estimado: 60–90 minutos para o setup inicial do cluster; 15 minutos para teardown + redeploy.
Instalação automatizada (recomendada)¶
Execute o script de instalação uma vez por servidor nesta ordem exata (UCS-2 primeiro porque hospeda o control plane do k3s):
# Clone o repositório em cada servidor
git clone https://github.com/nollagluiz/AI_forSE.git && cd AI_forSE
# UCS-2 — k3s server + personas + serviços + observabilidade
sudo ./scripts/k8s-install.sh --mode=dual-server --data-iface=eth1
# ↑ Imprime JOIN_TOKEN e SERVER_IP no final — guarde para usar no UCS-1.
# UCS-1 — agentes browser-engine + synthetic-load
sudo ./scripts/k8s-install.sh --mode=dual-agent \
--server-ip=<UCS-2-OOBI-IP> --token=<JOIN_TOKEN> --data-iface=eth1
# De volta no UCS-2 — aplica todos os manifests Kubernetes
sudo ./scripts/k8s-install.sh --mode=dual-apply
Dry run: acrescente
--dry-runem qualquer comando para visualizar sem fazer alterações.
O restante deste guia detalha cada passo e é útil para customizar o setup ou diagnosticar problemas da instalação automatizada.
Sumário¶
- Por que dual-node
- Visão geral da arquitetura
- Rede OOBI — obrigatória nos dois UCS
- Pré-requisitos por servidor
- Passo 1 — Preparação do host (ambos UCS)
- Passo 2 — Bootstrap do cluster k3s
- Passo 3 — Setup de VLAN por nó
- Passo 4 — Aplicar manifests
- Passo 5 — Verificar o deployment
- Observabilidade — Grafana e Prometheus em dual-node
- Troubleshooting
- Referência — arquivo de overlay
1. Por que dual-node¶
Single-node é o layout mais simples, mas todos os workloads competem pela mesma CPU, memória e filas de NIC — sob carga não dá para distinguir se o gargalo está no NGFW ou no próprio test bed. Multi-node resolve isso completamente, mas exige quatro chassis UCS.
Dual-node é o meio-termo:
- Isolamento de hardware: geradores de carga (browser-engine + synthetic-load) e webservers (personas + cloned-personas) rodam em chassis UCS físicos diferentes, então o único caminho de contenção entre eles é o NGFW sob teste — exatamente a variável que se quer medir.
- Dois papéis em vez de quatro: metade do espaço em rack, metade do cabeamento, metade da carga operacional do multi-node.
- Mesma observabilidade: dashboards Grafana e alertas Prometheus idênticos aos do single-node e multi-node — o
node_exporterroda em ambos os UCS via DaemonSet, e o alerta de gargalo do test-bed se auto-adapta à topologia de dois nós.
Escolha dual-node quando você tem dois chassis e quer medições limpas do NGFW sem montar um cluster de quatro servidores.
2. Visão geral da arquitetura¶
Topologia física¶
┌──────────────────────────────────────────────────┐
│ Rede OOBI — eth0 nos DOIS UCS │
│ 10.0.0.0/24 (exemplo) │
│ k3s API :6443 · kubelet · scrape Prometheus │
└────────┬──────────────────────┬───────────────────┘
│ │
┌────────┴────────┐ ┌────────┴────────────────────┐
│ UCS-1 │ │ UCS-2 │
│ role=agents │ │ role=ngfw-dut │
│ │ │ │
│ browser engine │ │ 20 pods Caddy de personas │
│ synthetic-load engine │ │ 10 slots cloned-persona │
│ │ │ Dashboard · Postgres │
│ │ │ PgBouncer · Cloner · NFS │
│ │ │ Grafana · Prometheus │
│ │ │ SNMP exporter │
│ eth0 (OOBI) │ │ eth0 (OOBI) │
│ eth1 (trunk) │ │ eth1 (trunk) │
└────────┬─────────┘ └────────────┬─────────────────┘
│ │
VLAN 20 (172.16/16) VLAN 99 mgmt (192.168.90/24)
VLAN 30 (172.17/16) VLAN 40 ISP (DHCP via macvlan)
VLANs 101–120 (10.1.x/27)
VLANs 200–209 (10.2.x/27)
│ │
└─────────────┬─────────────┘
│
┌────────────┴────────────┐
│ Cisco Nexus 9000 │
│ trunk de VLAN · ECMP │
│ DSCP AF41 · MTU 9216 │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ NGFW (Device Under Test)│
│ TLS leg-1: agentes→NGFW│
│ TLS leg-2: NGFW→Caddy │
└─────────────────────────┘
Distribuição de workloads¶
| Workload | UCS-1 (role=agents) |
UCS-2 (role=ngfw-dut) |
|---|---|---|
Agentes browser engine (web-agent) |
✓ | — |
Agentes synthetic-load engine (k6-agent) |
✓ | — |
| Personas sintéticas (20× Caddy) | — | ✓ |
| Slots de cloned-persona (10× Caddy) | — | ✓ |
| Dashboard, Postgres, PgBouncer | — | ✓ |
| Cloner | — | ✓ |
| Servidor NFS (cloned-sites) | — | ✓ |
| SNMP exporter | — | ✓ |
| Prometheus + Grafana | — | ✓ |
node_exporter (métricas por host) |
✓ DaemonSet | ✓ DaemonSet |
node-tuning (sysctl + BBR + CPU governor) |
✓ DaemonSet (dut-data-plane=true) |
✓ DaemonSet (dut-data-plane=true) |
cni-dhcp (DHCP ISP do cloner) |
— | ✓ DaemonSet |
Por que role=ngfw-dut no UCS-2 (em vez de role=infra)¶
As personas sintéticas (personas/_generated/*/deployment.yaml), as cloned-personas (k8s/clone-personas/20-slots.yaml) e o SNMP exporter (k8s/dut/60-snmp-exporter.yaml) têm nodeSelector: role=ngfw-dut fixado. Reaproveitar essa label no UCS-2 evita mexer em vinte e tantos manifests. O overlay dual-node então faz o patch dos workloads de serviços adicionais (Dashboard, Postgres, PgBouncer, Cloner) — que o overlay multi-node envia para role=infra — para o mesmo nó role=ngfw-dut.
3. Rede OOBI — obrigatória nos dois UCS¶
A rede OOBI (out-of-band infrastructure) é o segmento dedicado de control plane carregado por eth0 em cada UCS, independentemente do modo de deployment.
O que roda em OOBI¶
- API server do k3s (
:6443) — UCS-1 alcança o UCS-2 para entrar no cluster - kubelet (
:10250) — UCS-2 faz scrape do UCS-1 para status de pods - flannel (CNI) — tráfego pod-a-pod entre nós usa VXLAN sobre eth0
- Scrape Prometheus —
node_exporter,cAdvisor,kube-state-metricssão todos alcançados via eth0 - Renovações cert-manager — webhook + tráfego de challenge ACME
- Dashboard / kubectl — acesso do operador
O que NÃO roda em OOBI¶
- Tráfego de teste do NGFW — esse vai pelo trunk de VLANs em
eth1(data plane) - Polling SNMP — VLAN 99 (
192.168.90.0/24) emeth1.99 - Egress de internet do cloner — VLAN 40 em
eth1.40
Requisitos por UCS¶
| UCS-1 (agentes) | UCS-2 (serviços) | |
|---|---|---|
| IP em eth0 | estático, subnet OOBI | estático, subnet OOBI |
| Alcançável a partir do eth0 do par | ✓ (kubelet ↔ control plane) | ✓ |
| MTU | 1500 (default) | 1500 (default) |
| Rota default via OOBI | ✓ — egress de internet para pull de imagens e instalação do k3s | ✓ |
Se eth0 faltar em qualquer UCS, o flannel do k3s não consegue estabelecer o control plane, o UCS-1 não consegue entrar no cluster e o Prometheus no UCS-2 não consegue fazer scrape de métricas de host do UCS-1. O pre-flight check em k8s-install.sh rejeita tanto dual-server quanto dual-agent se eth0 estiver ausente.
4. Pré-requisitos por servidor¶
Hardware¶
| UCS-1 | UCS-2 | |
|---|---|---|
| CPU | ≥ 16 cores físicos | ≥ 32 cores físicos |
| RAM | ≥ 64 GB | ≥ 128 GB |
| NICs | eth0 (OOBI) + eth1 (trunk para Nexus) | eth0 (OOBI) + eth1 (trunk para Nexus) |
| Disco | ≥ 200 GB livres em / |
≥ 500 GB livres em / (NFS + Postgres + retenção do Prometheus) |
UCS-2 é o nó mais pesado — roda personas, todos os serviços e o stack de observabilidade. Planeje 2× os recursos do UCS-1.
Sistema operacional¶
- Ubuntu 22.04 LTS ou 24.04 LTS, amd64
- acesso root (ou sudo)
- acesso à internet pela rota default OOBI durante a instalação (k3s + Helm + imagens de container)
Rede — Nexus 9000¶
VLANs que precisam estar configuradas no trunk para ambos os UCS:
| VLAN | UCS-1 | UCS-2 | Subnet | Uso |
|---|---|---|---|---|
| 20 | ✓ | — | 172.16.0.0/16 | Agentes browser engine |
| 30 | ✓ | — | 172.17.0.0/16 | Agentes synthetic-load engine |
| 40 | — | ✓ | DHCP do ISP | Egress de internet do cloner (macvlan) |
| 99 | — | ✓ | 192.168.90.0/24 | Polling SNMP — Nexus (.2), NGFW (.3) |
| 101–120 | — | ✓ | 10.1.{1..20}.0/27 | Webservers de personas sintéticas |
| 200–209 | — | ✓ | 10.2.{1..10}.0/27 | Slots de cloned-persona |
A rigor: VLAN 20 e VLAN 30 só podem chegar no UCS-1; VLANs 40, 99, 101–120 e 200–209 só podem chegar no UCS-2. O trunk Nexus pode carregar todas e deixar cada lado ignorar as que não consome — o trunk é idêntico para as duas portas.
Passo 1 — Preparação do host (ambos UCS)¶
Preparação idêntica em UCS-1 e UCS-2:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
curl git jq openssl ca-certificates gnupg lsb-release \
vlan iproute2 ethtool net-tools
# Verifica as duas NICs
ip link show eth0
ip link show eth1
Em seguida rode o script de tuning de host do projeto em ambos UCS — sysctls para QUIC + TCP de alto fan-out, BBR + FQ, CPU governor, conntrack, portas:
sudo scripts/host-tuning.sh apply
sudo scripts/host-tuning.sh status
A etapa apply é idempotente e persiste via drop-ins do systemd-sysctl. A cada rodada de medição o alerta TestBedSysctlMissing do dashboard dispara se algum dos UCS regredir aos defaults do kernel.
Passo 2 — Bootstrap do cluster k3s¶
UCS-2 primeiro (é o k3s server)¶
sudo ./scripts/k8s-install.sh --mode=dual-server --data-iface=eth1
O script:
- Faz pre-flight de eth0 + eth1 + interface ISP
- Cria subinterfaces de VLAN 40, 99, 101–120, 200–209 em
eth1 - Sobe a subinterface ISP
eth1.40e a persiste via netplan - Instala o k3s server, vincula a API a
eth0, desabilita Traefik e ServiceLB - Instala Helm, cert-manager, Multus
- Aplica labels
role=ngfw-dut+dut-data-plane=trueno nó - Imprime o JOIN_TOKEN e o SERVER_IP
Salve as credenciais impressas — UCS-1 vai precisar delas.
UCS-1 (agentes)¶
sudo ./scripts/k8s-install.sh --mode=dual-agent \
--server-ip=<UCS-2-OOBI-IP> --token=<JOIN_TOKEN> --data-iface=eth1
O script:
- Faz pre-flight de eth0 + eth1 (ambos obrigatórios)
- Cria subinterfaces de VLAN 20, 30 em
eth1 - Entra no k3s como agent via eth0 → UCS-2:6443
- Aplica labels
role=agents+dut-data-plane=trueem si mesmo
Verifique a partir do UCS-2:
kubectl get nodes -o wide
# Esperado:
# NAME STATUS ROLES AGE VERSION LABELS
# ucs-1 Ready agent 1m v1.31.x role=agents,dut-data-plane=true,...
# ucs-2 Ready master 3m v1.31.x role=ngfw-dut,dut-data-plane=true,...
Passo 3 — Setup de VLAN por nó¶
O script de instalação faz isso automaticamente. Para verificar:
UCS-1:
ip -br link | grep eth1\\.
# eth1.20 UP (172.16.0.1/16)
# eth1.30 UP (172.17.0.1/16)
UCS-2:
ip -br link | grep eth1\\.
# eth1.40 UP (sem IP — DHCP via macvlan do pod cloner)
# eth1.99 UP (192.168.90.1/24)
# eth1.101 UP (10.1.1.1/27)
# … até eth1.120 (10.1.20.1/27)
# eth1.200 UP (10.2.1.1/27)
# … até eth1.209 (10.2.10.1/27)
Persistência das VLANs entre reboots é feita pelo netplan (o script grava /etc/netplan/99-*.yaml).
Passo 4 — Aplicar manifests¶
A partir do UCS-2:
# Cria o configmap da CA do NGFW (uma vez)
kubectl create configmap ngfw-ca -n web-agents --from-file=ngfw-ca.crt=<path-do-cert>
# Cria os secrets de aplicação a partir do .env
kubectl create secret generic web-agent-secrets -n web-agents --from-env-file=.env
# Aplica tudo
sudo ./scripts/k8s-install.sh --mode=dual-apply
Isso aplica:
kubectl apply -k overlays/dual-node/— manifests k8s base com browser-engine/synthetic-load fixados emrole=agentse Dashboard/Postgres/PgBouncer/Cloner fixados emrole=ngfw-dutkubectl apply -k k8s/dut/— overlay DUT (NADs, probes SNMP, servidor NFS, DaemonSet node-tuning, ServiceMonitor cAdvisor, regras Prometheus de infra)- Faz patch do
nodeSelectordo DaemonSetnode-tuningparadut-data-plane=true(roda nos dois UCS) - Faz patch dos deployments browser-engine + synthetic-load via
40-playwright-patch.yaml/50-k6-patch.yaml— anotação Multus net1 macvlan, trust da CA do NGFW, REJECT_INVALID_CERTS=true - Re-fixa browser-engine + synthetic-load em
role=agents(os arquivos de patch usamrole=ngfw-dutpor padrão para compatibilidade com single-node — dual-node sobrescreve) - Espera todos os pods ficarem Ready
Passo 5 — Verificar o deployment¶
# Pods no UCS-1 (agentes)
kubectl get pods -n web-agents -o wide --field-selector spec.nodeName=ucs-1
# Esperado: somente web-agent-* e k6-agent-*
# Pods no UCS-2 (serviços + personas)
kubectl get pods -n web-agents -o wide --field-selector spec.nodeName=ucs-2
# Esperado: dashboard, postgres, pgbouncer, cloner, nfs-server,
# persona-shop-*, persona-news-*, …, clone-persona-1-*, …
# Personas sintéticas + clonadas em seus próprios namespaces
kubectl get pods --all-namespaces -o wide | grep -E "persona-|clone-persona-"
# Multus net1 anexado nos agentes
kubectl exec -n web-agents deploy/web-agent -- ip -br addr | grep net1
# Esperado: um endereço 172.16.x.x
# Confere que o servidor NFS está alcançável a partir do cloner
kubectl exec -n web-agents deploy/cloner -- mount | grep nfs
# Smoke test fim a fim — agentes alcançam uma persona via NGFW
kubectl exec -n web-agents deploy/web-agent -- \
curl -sf https://shop.persona.local/ | head -1
6. Observabilidade — Grafana e Prometheus em dual-node¶
A observabilidade roda integralmente no UCS-2 (role=ngfw-dut):
- Prometheus — faz scrape dos dois UCS via OOBI (eth0) na porta
:9100paranode_exporter, kubelet:10250para cAdvisor e:8080parakube-state-metrics - Grafana — dashboard
Test-Bed Infrastructure Healthse auto-adapta à topologia de dois nós via filtros por host e regras de recordingcount(node_exporter) - Alertas — o composto
TestBedInfrastructureBottleneck, alertas a nível de host (HostUDPBufferOverflow,HostConntrackNearFull, etc.) e a nível de pod (PodCPUThrottled,OOMKilled) funcionam todos out-of-the-box. VejaMONITORING_TEST_VALIDITY.pt-BR.mdpara o catálogo completo de alertas
O DaemonSet node_exporter não tem nodeSelector e usa tolerations: operator: Exists, então cobre os dois UCS sem configuração adicional. O alerta NodeExporterCoverageIncomplete dispara automaticamente se algum dos nós parar de reportar métricas de host.
Para abrir o Grafana a partir da workstation do operador (assumindo que você tenha acesso kubectl ao UCS-2):
kubectl port-forward -n web-agents svc/grafana 3000:3000
# então acesse http://localhost:3000
7. Troubleshooting¶
UCS-1 não consegue entrar no cluster¶
Sintomas: instalação dual-agent trava em Joining k3s cluster as agent.
# No UCS-1 — checa alcance OOBI até o UCS-2
ping <UCS-2-OOBI-IP>
nc -vz <UCS-2-OOBI-IP> 6443
# No UCS-1 — checa status do serviço k3s-agent e logs
sudo systemctl status k3s-agent
sudo journalctl -u k3s-agent -n 100
Causas mais comuns: subnet OOBI inconsistente, firewall bloqueando 6443, ou JOIN_TOKEN errado.
Pods browser engine ficam em Pending¶
kubectl describe pod -n web-agents -l app=web-agent | grep -A4 Events
Se aparecer node(s) didn't match Pod's node affinity/selector, a etapa de re-fixar os agentes não rodou. Aplique manualmente:
kubectl patch deployment web-agent -n web-agents --type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"role":"agents"}}}}}'
kubectl patch deployment k6-agent -n web-agents --type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"role":"agents"}}}}}'
Personas ficam em Pending¶
kubectl describe pod -n persona-shop persona-shop-* | grep -A4 Events
Se aparecer node(s) didn't match de novo, o UCS-2 não está com a label role=ngfw-dut. Aplique a label novamente:
kubectl label node ucs-2 role=ngfw-dut dut-data-plane=true --overwrite
Cloner não alcança a internet¶
O cloner usa macvlan em eth1.40 com DHCP. Verifique no UCS-2:
ip link show eth1.40
sudo dhclient -v eth1.40 # apenas como diagnóstico manual — o pod tem DHCP próprio
Se eth1.40 estiver faltando, rode setup_isp_iface novamente (faz parte de dual-server).
node_exporter reporta apenas um nó¶
kubectl get pods -n web-agents -l app.kubernetes.io/name=node-exporter -o wide
Tanto ucs-1 quanto ucs-2 devem aparecer. Se não, verifique:
kubectl describe daemonset node-exporter -n web-agents | grep -A3 Tolerations
As tolerations devem ser operator: Exists (casa com qualquer taint). Se um nó tiver um taint customizado bloqueando, adicione uma toleration ou remova o taint.
8. Referência — arquivo de overlay¶
O overlay dual-node fica em overlays/dual-node/kustomization.yaml. Ele herda de k8s/ e adiciona seis patches strategic-merge:
patches:
# browser-engine + K6 → UCS-1
- target: { kind: Deployment, name: web-agent } # role=agents
- target: { kind: Deployment, name: k6-agent } # role=agents
# Serviços → UCS-2 (reaproveita role=ngfw-dut)
- target: { kind: Deployment, name: dashboard } # role=ngfw-dut
- target: { kind: StatefulSet, name: postgres } # role=ngfw-dut
- target: { kind: Deployment, name: pgbouncer } # role=ngfw-dut
- target: { kind: Deployment, name: cloner } # role=ngfw-dut
Para aplicar manualmente (sem o script de instalação):
kubectl apply -k overlays/dual-node/
kubectl apply -k k8s/dut/
kubectl patch daemonset node-tuning -n web-agents \
--type=strategic-merge-patch \
-p '{"spec":{"template":{"spec":{"nodeSelector":{"dut-data-plane":"true"}}}}}'
Veja também¶
UBUNTU_K3S_SINGLENODE_QUICKSTART_DEPLOY.pt-BR.md— alternativa de servidor únicoUBUNTU_K3S_MULTINODE_QUICKSTART_DEPLOY.pt-BR.md— alternativa de quatro servidoresMONITORING_TEST_VALIDITY.pt-BR.md— alertas de observabilidade + validade de testePERFORMANCE_TUNING_HOST.md— tuning de kernel do hostSYSTEM_OVERVIEW.md— referência de arquitetura para os três modos de deployment