Public Website Cloner — Guia de Operações¶
Stack:
cloner· Namespaceweb-agents· Porta 8081
Versão: v3.6.0+Scope status (post-Scope-Freeze 2026-05-10) — See ARCHITECTURE.md for the canonical 37 MÓDULOs + 7 Test Kinds + DOM/CPOS/PIE-PA safety architecture. ADRs 0014, 0019-0025 cover post-Freeze additions. Contato: agallon@cisco.com
Objetivo¶
O Public Website Cloner é um componente opcional do TLSStress.Art que permite baixar sites públicos da internet e servi-los localmente dentro do lab, sem dependência de conectividade externa durante os testes.
Isso permite que a frota de agentes browser engine e synthetic-load engine teste conteúdo real de sites conhecidos (ex.: portais bancários, e-commerce, streaming) diretamente através do NGFW em modo DUT, sem tráfego de produção saindo pela rede de testes.
Fluxo típico de uso:
1. Administrador seleciona um site público (ex.: https://example.com) e uma persona (ex.: shop)
2. O cloner baixa o site completo via interface ISP (VLAN 40 — acesso direto à internet)
3. O conteúdo clonado fica disponível em http://clone-serve:8081/shop/
4. O Dashboard cria um target http://clone-serve:8081/shop/ na frota de teste
5. browser-engine/synthetic-load acessa o conteúdo clonado através do NGFW — exercitando inspeção TLS, regras e políticas sobre conteúdo real
Como funciona¶
Arquitetura e fluxo de dados¶
┌──────────────────────────────────────────────────────────────────────┐
│ Cloner pod (1 réplica, node role=infra / UCS-4) │
│ │
│ eth0 ─── OOBI (K8s default) ─── Dashboard API ─── PostgreSQL │
│ net1 ─── VLAN 40 (ISP) ─────── Nexus 9000 ─── Upstream router │
│ │ │
│ INTERNET │
│ ┌───────────────────────────────────────────┐ │ │
│ │ browser engine headful + stealth │◄───────┘ │
│ │ headless: false (contorna anti-bot) │ │
│ │ user-agent realista, viewport real │ ← baixa via VLAN 40 │
│ │ navigator.webdriver = undefined │ │
│ └─────────────────────┬─────────────────────┘ │
│ │ escreve │
│ ▼ │
│ /mnt/cloned/{persona}/ │
│ (PVC NFS RWX — cloned-sites, OOBI only) │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ clone-serve HTTP Server (:8081) │ │
│ │ GET /healthz → liveness probe │ │
│ │ GET /metrics → Prometheus │ │
│ │ GET /{persona}/… → assets clonados │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ Internet Health Monitor (10 s) │ │
│ │ ping 8.8.8.8 → internet OK? │ │
│ │ ping 1.1.1.1 → internet OK? │ │
│ │ ping <gw DHCP> → gateway VLAN 40 OK? │ │
│ └───────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
Ciclo de vida de um job¶
Admin cria job (POST /api/clone/jobs)
│
▼ clone_jobs.status = 'pending'
│
▼ cloner poll (GET /api/clone/agents/{id}/job — SELECT … SKIP LOCKED)
clone_jobs.status = 'running'
│
▼ browser engine baixa o site via VLAN 40
escreve /mnt/cloned/{persona}/index.html, *.css, *.js, imagens…
│
▼ PATCH /api/clone/jobs/{id}
clone_jobs.status = 'completed' | 'failed'
│
▼
Assets servidos em http://clone-serve.web-agents.svc.cluster.local:8081/{persona}/
Rede — VLAN 40 (ISP)¶
Mapa de VLANs do lab¶
| VLAN | Nome | Interface host | Subnet | Finalidade |
|---|---|---|---|---|
| 20 | dut-pw | eth1.20 | 172.16.0.0/16 | Agentes browser engine |
| 30 | dut-k6 | eth1.30 | 172.17.0.0/16 | Agentes synthetic-load engine |
| 40 | cloner-isp | eth1.40 | DHCP (ISP) | Cloner — egresso internet |
| 99 | dut-mgmt | eth1.99 | 10.254.254.0/24 | SNMP / gerência NGFW |
| 101–120 | persona-N | eth1.10N | 10.1.N.0/27 | Webservers de persona individuais (Synthetic) |
| 200–209 | clone-persona-N | eth1.20N | 10.2.N.0/27 | Webservers de persona clonadas (Cloned) |
VLAN 40 — características: - Sem IP fixo no host — o pod recebe IP via DHCP do roteador upstream - O Nexus 9000 deve permitir VLAN 40 no trunk do UCS-4 (role=infra) - A VLAN 40 deve ser roteada até o link ISP / upstream router do lab - Não passa pelo NGFW — acesso direto à internet sem inspeção TLS
Roteamento dentro do pod¶
O initContainer (routing-init, Alpine com NET_ADMIN) configura policy routing:
Routing table 100 (ISP): default via <gateway VLAN 40> dev net1
iptables mangle OUTPUT:
-d 10.0.0.0/8 → RETURN (K8s/OOBI: usa eth0)
-d 172.16.0.0/12 → RETURN (K8s/OOBI: usa eth0)
-d 192.168.0.0/16 → RETURN (K8s/OOBI: usa eth0)
-j MARK --set-mark 100 (todo o resto → usa net1/VLAN 40)
ip rule: fwmark 100 → table 100
Todo o tráfego público (TCP 80/443, UDP 443/QUIC, UDP 53/DNS, ICMP) sai via VLAN 40 automaticamente.
Pré-requisito Nexus 9000¶
! No Nexus 9000 — permitir VLAN 40 na porta trunk de UCS-4
interface Ethernet1/<N>
switchport trunk allowed vlan add 40
vlan 40
name cloner-isp-egress
HTTPS e certificados¶
Acesso a sites HTTPS públicos¶
O cloner acessa sites diretamente via VLAN 40, sem passar pelo NGFW. Portanto:
- Os sites servem seus próprios certificados TLS (assinados por CAs públicas)
- O Chromium valida os certificados usando o bundle Mozilla/NSS do Debian (
ca-certificates) - Nenhuma configuração adicional é necessária para sites HTTPS públicos (Let's Encrypt, DigiCert, Sectigo, etc.)
- A CA do NGFW não é injetada no cloner — não é necessária (o cloner bypassa o NGFW)
Fluxo TLS no cloner vs. no restante do lab¶
Agentes browser-engine/synthetic-load (TLS leg 1) → NGFW (TLS leg 2) → Caddy persona
[NGFW inspeciona e re-assina com CA própria]
Cloner → VLAN 40 → Internet → site real
[TLS direto; CA pública; sem inspeção; ca-certificates Debian]
Certificados privados ou auto-assinados¶
Se o site alvo usar CA privada:
# 1. Exportar a CA em formato PEM
# 2. Criar ConfigMap com a CA
kubectl create configmap cloner-extra-cas \
--from-file=extra-ca.crt=/caminho/para/ca.pem \
-n web-agents
# 3. Montar no pod e configurar NODE_EXTRA_CA_CERTS em 81-cloner-deployment.yaml
DNS forçado¶
O pod usa dnsPolicy: None. Os resolvers são fixos independentemente do DHCP:
| Prioridade | Servidor | Finalidade |
|---|---|---|
| 1 | 8.8.8.8 |
Google Public DNS — nomes de internet |
| 2 | 208.67.222.222 |
OpenDNS — fallback internet |
| 3 | 10.96.0.10 |
k3s CoreDNS — dashboard, pgbouncer, etc. |
Queries para 8.8.8.8/208.67.222.222 são endereços públicos → fwmarked → saem via VLAN 40.
CoreDNS em cluster diferente do k3s padrão:
kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}' # Atualizar k8s/81-cloner-deployment.yaml → dnsConfig.nameservers[2]
Deploy¶
Kubernetes — passo a passo¶
# 1. VLAN 40 no Nexus 9000 (ver seção acima)
# 2. Criar subinterface eth1.40 no UCS-4 (automático via k8s-install.sh)
sudo ip link add link eth1 name eth1.40 type vlan id 40 2>/dev/null || true
sudo ip link set eth1.40 up
# 3. Criar secrets (inclui cloner-secrets automaticamente)
sudo ./scripts/secrets-init.sh --hostname agents.mylab.local
# 4. Aplicar manifests
kubectl apply -f k8s/
# 5. Verificar
kubectl get pod -n web-agents -l app.kubernetes.io/name=cloner -o wide
kubectl logs -n web-agents -l app.kubernetes.io/name=cloner -f
# Aguardar: "[health] ping 8.8.8.8: ok X.Xms"
Docker Compose (desenvolvimento)¶
# Control stack deve estar em execução
export CONTROLLER_TOKEN=<mesmo que AGENT_API_TOKEN>
docker compose -f docker-compose.cloner.yml up -d
# Verificar DNS forçado
docker exec -it <cloner-container> cat /etc/resolv.conf
# Deve mostrar: nameserver 8.8.8.8 / 208.67.222.222
Gerenciar jobs¶
Criar job (curl)¶
curl -s -X POST https://agents.mylab.local/api/clone/jobs \
-H "Authorization: Basic $(echo -n 'admin:<PASS>' | base64)" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","personaName":"shop"}' | python3 -m json.tool
Listar / detalhar / cancelar¶
BASE=https://agents.mylab.local
H="Authorization: Basic $(echo -n 'admin:<PASS>' | base64)"
curl -s "$BASE/api/clone/jobs" -H "$H" | python3 -m json.tool
curl -s "$BASE/api/clone/jobs/<job-id>" -H "$H"
curl -s -X DELETE "$BASE/api/clone/jobs/<job-id>" -H "$H"
Acessar conteúdo clonado¶
kubectl port-forward -n web-agents svc/clone-serve 8081:8081
# Abrir: http://localhost:8081/shop/
Monitoramento¶
Grafana — linha "Cloner — Internet Health & Jobs"¶
| Painel | Métrica | Verde = |
|---|---|---|
| Acesso Internet (ISP) | cloner_internet_any_up |
Qualquer ping respondeu |
| Ping 8.8.8.8 | cloner_internet_up{target="8.8.8.8"} |
Acessível |
| Ping 1.1.1.1 | cloner_internet_up{target="1.1.1.1"} |
Acessível |
| Gateway ISP | cloner_gateway_up{gateway="<IP>"} |
Gateway DHCP responde ICMP |
| Ping RTT | cloner_ping_rtt_ms |
Latência < 50 ms |
Métricas diretas¶
kubectl port-forward -n web-agents svc/clone-serve 8081:8081
curl http://localhost:8081/metrics | grep cloner_
Saída esperada:
cloner_internet_up{target="8.8.8.8"} 1
cloner_internet_up{target="1.1.1.1"} 1
cloner_internet_any_up 1
cloner_gateway_up{gateway="10.40.0.1"} 1
cloner_ping_rtt_ms{target="8.8.8.8"} 4.321
cloner_ping_rtt_ms{target="1.1.1.1"} 3.876
cloner_ping_rtt_ms{target="gateway(10.40.0.1)"} 0.412
Alertas Prometheus¶
| Alerta | Condição | Severidade |
|---|---|---|
ClonerInternetDown |
Ambos 8.8.8.8 e 1.1.1.1 falham por 2 min | critical |
ClonerInternetPartialLoss |
Apenas 1 dos 2 OK por 5 min | warning |
ClonerJobStuck |
Job em running > 30 min |
warning |
ClonerNoAgent |
Métrica ausente por 5 min | warning |
Troubleshooting¶
Pod não inicia¶
kubectl describe pod -n web-agents -l app.kubernetes.io/name=cloner
kubectl logs -n web-agents -l app.kubernetes.io/name=cloner --previous 2>/dev/null
| Sintoma | Causa | Solução |
|---|---|---|
ImagePullBackOff |
Imagem não publicada | Build local ou aguardar CI push |
PVC cloned-sites Pending (web-agents ou clone-persona-N) |
NFS server não pronto OU volumeName inválido |
kubectl -n web-agents get pod -l app.kubernetes.io/name=nfs-server. Se Ready: kubectl describe pv cloned-sites-writer. PVs estão em k8s/dut/36-cloned-sites-pvs.yaml. |
Slot MountVolume.SetUp failed com mount.nfs: Connection refused |
NFS server caiu | kubectl -n web-agents rollout restart deploy/nfs-server. Conteúdo persiste no hostPath do nó. |
| Slot pod sem dados após bind | Cloner gravou em outro nó (multi-node bug pré-PR-H) | Confirme cloner em role=infra e NFS server no MESMO nó. kubectl -n web-agents get pod -o wide. |
CreateContainerError |
Secret cloner-secrets ausente |
./scripts/secrets-init.sh |
Init:Error |
routing-init falhou | Ver seção abaixo |
Cloner sem IP em net1 |
CNI DHCP daemon ausente do nó | kubectl get ds cni-dhcp-daemon -n web-agents deve ter DESIRED=READY=número de nós. |
VLAN 40 (net1) sem IP¶
# Dentro do pod
kubectl exec -n web-agents deploy/cloner -- ip addr show net1
# NAD correto?
kubectl get nad cloner-isp -n web-agents -o jsonpath='{.spec.config}' | python3 -m json.tool
# Deve mostrar: "master": "eth1.40"
# eth1.40 existe no node?
kubectl debug node/<nome-do-node> -it --image=alpine -- ip link show eth1.40
# Criar manualmente se ausente:
sudo ip link add link eth1 name eth1.40 type vlan id 40
sudo ip link set eth1.40 up
kubectl rollout restart deployment/cloner -n web-agents
Nexus 9000: verificar VLAN 40 no trunk de UCS-4:
show interfaces trunk | grep Eth1/<N>
! Se VLAN 40 não aparecer:
conf t
int Eth1/<N>
switchport trunk allowed vlan add 40
routing-init falhou¶
kubectl logs \
$(kubectl get pod -n web-agents -l app.kubernetes.io/name=cloner -o name) \
-c routing-init
Sucesso esperado:
[routing-init] ISP iface=net1 ip=10.40.0.X gw=10.40.0.1
[routing-init] gateway=10.40.0.1 written to /var/isp-config/gateway.txt
[routing-init] routing configured — all public TCP/UDP/ICMP exits via net1
WARNING: net1 has no IP after 60 s → DHCP da VLAN 40 não respondeu (ver seção acima).
Sem acesso à internet (ping vermelho)¶
kubectl exec -n web-agents deploy/cloner -- ip route show table 100
# Deve: default via 10.40.0.1 dev net1
kubectl exec -n web-agents deploy/cloner -- ip rule show
# Deve: 10: from all fwmark 0x64 lookup 100
kubectl exec -n web-agents deploy/cloner -- ping -c 3 -I net1 8.8.8.8
DNS não resolve¶
kubectl exec -n web-agents deploy/cloner -- cat /etc/resolv.conf
# Deve: nameserver 8.8.8.8 / 208.67.222.222 / 10.96.0.10
kubectl exec -n web-agents deploy/cloner -- nslookup example.com 8.8.8.8
kubectl exec -n web-agents deploy/cloner -- \
nslookup dashboard.web-agents.svc.cluster.local 10.96.0.10
Erro de certificado HTTPS¶
kubectl exec -n web-agents deploy/cloner -- \
curl -v https://example.com 2>&1 | grep -E "SSL|cert|error"
- CA pública → deve funcionar sem configuração adicional (bundle Debian inclui Mozilla roots)
- CA privada → injetar via ConfigMap (ver seção "Certificados privados")
SSL_ERROR_RX_RECORD_TOO_LONG→ tráfego interceptado incorretamente; verificar que o cloner não passa pelo NGFW
Job travado em running¶
kubectl logs -n web-agents deploy/cloner -f
# Cancelar via API
curl -s -X DELETE https://agents.mylab.local/api/clone/jobs/<job-id> \
-H "Authorization: Basic $(echo -n 'admin:<PASS>' | base64)"
# Ou diretamente no banco
kubectl exec -n web-agents deploy/dashboard -- psql "$DATABASE_URL" -c \
"UPDATE clone_jobs SET status='cancelled', completed_at=now()
WHERE id='<job-id>' AND status='running';"
Causas comuns:
| Causa | Sinal |
|-------|-------|
| Anti-bot bloqueou | Log: net::ERR_BLOCKED ou timeout 60 s |
| Volume NFS cheio | kubectl exec deploy/cloner -- df -h /mnt/cloned (capacidade=hostPath do nó do NFS server) |
| NFS server fora do ar | Slot pods retornam 503/timeout; kubectl -n web-agents get pod -l app.kubernetes.io/name=nfs-server |
| VLAN 40 perdeu rota | Gateway ping vermelho no Grafana |
Gateway ISP vermelho¶
kubectl exec -n web-agents deploy/cloner -- cat /var/isp-config/gateway.txt
GW=$(kubectl exec -n web-agents deploy/cloner -- cat /var/isp-config/gateway.txt)
kubectl exec -n web-agents deploy/cloner -- ping -c 3 $GW
kubectl exec -n web-agents deploy/cloner -- ip neigh show dev net1
Multi-node (4 UCS servers)¶
O cloner é fixado no UCS-4 (role=infra) pelo overlay patch-cloner-nodesel.yaml.
# Verificar node
kubectl get pod -n web-agents -l app.kubernetes.io/name=cloner -o wide
# NODE deve ser UCS-4
# VLAN 40 em UCS-4
kubectl debug node/<ucs4-hostname> -it --image=alpine -- ip link show eth1.40
Notas¶
- Uma réplica — jobs executam sequencialmente. Para paralelismo, aumentar
replicas. - Sobrescrita — clonar a mesma persona substitui o clone anterior.
- Storage NFS RWX — em multi-node, o NFS server roda no UCS-4 (mesmo nó que o cloner). O hostPath subjacente fica em
/var/lib/agent-cluster/cloned-sitesno UCS-4. Slots em UCS-1 leem via NFS sobre OOBI (eth0). A capacidade depende do disco do UCS-4. Monitorar:kubectl -n web-agents exec deploy/cloner -- df -h /mnt/cloned. - OOBI vs data plane — todo I/O de storage atravessa OOBI. As VLANs 200-209 (slots) e VLAN 40 (cloner) carregam APENAS tráfego de teste agente↔NGFW↔persona, nunca NFS.
- SPAs — sites que exigem interação do usuário para renderizar conteúdo podem ter clone incompleto.
- robots.txt — ignorado (uso interno de lab).