Instalar o Profiler
O RootPilot Profiler é o segundo artefato: opcional, privilegiado, e instalado por quem quer o eixo comportamental — o que mudou dentro do processo entre dois deploys, onde métrica e log mostram só o sintoma.
Ele é uma imagem diferente do Agent e exige privilégio que o Agent não pede. Antes de seguir, leia Segurança → RootPilot Profiler: é lá que estão a tabela de permissões, o que sai da sua infraestrutura e por que não existe um modo reduzido. Esta página é o “como”, e supõe que a decisão já foi tomada.
Antes de começar
Seção intitulada “Antes de começar”- Ligue o Profiler na organização. Em Configurações → RootPilot Profiler, no app. Sem isso o enrollment é recusado com 403 antes de qualquer coisa — o privilégio de kernel é concessão deliberada e registrada, não efeito colateral de um deploy.
- Escreva a lista de alvos. Ela é obrigatória e o Profiler não sobe sem ela. Ver abaixo.
- Tenha um bootstrap token do tenant, como no Agent.
A imagem
Seção intitulada “A imagem”# AWS (ECR)docker pull <conta-rootpilot>.dkr.ecr.<região>.amazonaws.com/rootpilot-profiler:<versão>
# Google Cloud (Artifact Registry)docker pull <região>-docker.pkg.dev/<projeto-rootpilot>/rootpilot/rootpilot-profiler:<versão>Multi-arch (amd64 + arm64) e assinada com cosign, como a do Agent — o procedimento de verificação é o mesmo descrito em A imagem.
A imagem embarca o coletor (ptop) e o agregador (witness). O processo que fala com o control-plane
roda como usuário não-root e sem capability nenhuma; quem carrega o privilégio é o binário do
coletor, por file capability.
A lista de alvos
Seção intitulada “A lista de alvos”Um JSON, montado no container, apontado por RUNNER_PROFILE_TARGETS_FILE:
{ "targets": [ { "workload": "checkout-api" }, { "namespace": "producao", "workload": "pricing-engine" } ]}Ela é sua e nunca passa por nós. Não existe default que signifique “tudo o que estiver no nó”: o
Profiler recusa subir com arquivo ausente, com lista vazia, ou com "workload": "*". O schema é
estrito — qualquer chave desconhecida, inclusive um campo de comentário bem-intencionado, derruba o
boot.
Arquivo e não variável de ambiente de propósito: no seu repositório de infraestrutura ele é diffável, entra numa revisão e alguém olha. É literalmente a lista de onde o container privilegiado pode encostar.
services: profiler: image: <conta-rootpilot>.dkr.ecr.<região>.amazonaws.com/rootpilot-profiler:<versão> restart: unless-stopped environment: RUNNER_TENANT: sua-org RUNNER_INSTANCE_ID: sua-org-profiler01 RUNNER_TUNNEL_URL: wss://tunnel.rootpilot.sh:8443 RUNNER_ENROLL_URL: https://app.rootpilot.sh RUNNER_IDENTITY_DIR: /identity RUNNER_PROFILE_TARGETS_FILE: /etc/rootpilot/profile-targets.json # As cinco, e nem uma a mais. A ausência de qualquer uma derruba o boot com o nome dela e o que # ela custa — ver a página de segurança. cap_add: [BPF, PERFMON, SYS_PTRACE, DAC_READ_SEARCH, SYS_ADMIN] # Visibilidade, não privilégio: sem isto o container só enxerga os próprios processos, e o alvo # simplesmente não existe para o coletor. pid: host security_opt: - apparmor=rootpilot-profiler # ver "AppArmor", abaixo volumes: - ./identity:/identity - ./profile-targets.json:/etc/rootpilot/profile-targets.json:ro # A biblioteca de eBPF procura os tracepoints aqui. Sem estes dois o coletor carrega e **não # anexa** — e a captura sai com eixos faltando, que é pior que sair vazia. - /sys/kernel/tracing:/sys/kernel/tracing - /sys/kernel/debug:/sys/kernel/debug mem_limit: 512mO bootstrap token entra como no Agent (RUNNER_BOOTSTRAP_TOKEN, de preferência via secret store, não
em texto no compose).
AppArmor
Seção intitulada “AppArmor”Se o seu host tem AppArmor ativo — Ubuntu e Debian têm, por default — o perfil docker-default
bloqueia o que o Profiler precisa fazer: ele só permite ptrace entre containers do mesmo
perfil, e o seu alvo normalmente não é um (um processo do host é unconfined; um container de outro
runtime tem outro perfil).
Não use apparmor=unconfined: isso desfaz o confinamento inteiro por causa de uma regra. Carregue um
perfil que é o docker-default mais uma linha:
#include <tunables/global>
profile rootpilot-profiler flags=(attach_disconnected,mediate_deleted) { #include <abstractions/base> # … o corpo do docker-default …
# A única diferença: ler /proc/<pid>/{exe,ns/*} de um alvo fora deste perfil. ptrace (read) peer=unconfined,}sudo apparmor_parser -r -W /etc/apparmor.d/rootpilot-profilerComo saber se é isso: uma captura volta com os coletores em permission denied sobre
/proc/<pid>/ns/* ou /proc/<pid>/exe, e o kernel registra a negação:
apparmor="DENIED" operation="ptrace" profile="docker-default" comm="ptop" peer="unconfined"Kubernetes
Seção intitulada “Kubernetes”O pod precisa de:
hostPID: true(oushareProcessNamespacese o alvo estiver no mesmo pod). Sem isso o coletor só enxerga a si mesmo.- As cinco capabilities em
securityContext.capabilities.add. /sys/kernel/tracinge/sys/kernel/debugmontados do host, comohostPath.- Um perfil AppArmor que permita
ptrace (read)contra o perfil do alvo. Em clusters com containerd, o default écri-containerd.apparmor.d, e o mesmo raciocínio da seção acima se aplica — o perfil precisa ser carregado em cada nó e referenciado nosecurityContext.appArmorProfile. - Um volume para
/identity, com a mesma vida do pod que o Agent precisa — e não compartilhado com outro artefato, pelo motivo da caixa acima. Num Deployment isso quer dizer um PVC por réplica, ou um StatefulSet.
Nenhuma permissão da API do Kubernetes é necessária hoje. O gatilho automático (perceber o pod subir
com a imagem nova) não existe — quando existir, ele pedirá get/list/watch em pods, só nos
namespaces da sua allowlist, e a página de segurança será atualizada antes.
DaemonSet: o roteamento existe, o cluster não foi exercitado
Seção intitulada “DaemonSet: o roteamento existe, o cluster não foi exercitado”Verificar que ele está medindo
Seção intitulada “Verificar que ele está medindo”Subir não é medir, e a diferença é a que mais custa: um coletor que não anexa produz captura com eixos faltando, e um eixo faltando comparado contra uma captura completa se lê como comportamento que parou.
Duas checagens, nessa ordem:
- O boot. Ele declara a classe e quantos alvos leu, e recusa subir nomeando a capability que faltar — não existe caminho em que ele suba verde e capture cego por falta de permissão.
- A primeira captura. Abra-a em Profiling, no app. A tela declara a instrumentação: se ela
disser
N de M probesem vez deinstrumentação completa, o bloco abaixo nomeia cada coletor que não anexou e o motivo — normalmente AppArmor ou os mounts de tracefs.
Uma captura com instrumentação incompleta não é uma captura mais fraca: é uma captura com eixos que não existem, e o produto prefere dizer isso a comparar mundos diferentes.
Pedir uma captura
Seção intitulada “Pedir uma captura”O gatilho é explícito: o Profiler não decide sozinho quando medir. Quem pede é o seu pipeline — logo depois de publicar uma versão — ou alguém do seu time durante uma investigação. São duas formas, e elas têm portas diferentes porque a pressa é diferente.
Pelo app, durante uma investigação
Seção intitulada “Pelo app, durante uma investigação”Em Profiling, no topo da página, há um formulário: processo, versão e ambiente. Qualquer membro da organização pode pedir — é o mesmo acesso que abre a página, e a decisão de owner/admin já foi tomada antes, ao ligar o Profiler. O desfecho aparece no registro logo abaixo, com quem pediu ao lado.
Janela, warmup e captura de réplica não estão no formulário de propósito: quem precisa deles está escrevendo pipeline, e o pipeline tem a chamada abaixo.
Pelo pipeline, depois de um deploy
Seção intitulada “Pelo pipeline, depois de um deploy”O token
Seção intitulada “O token”Em Configurações → Tokens de Captura do Profiler, no app. Ele começa com rpcap_, é mostrado
uma única vez na criação, e é distinto do token de MCP e do de ingress de incidentes: não lê nada,
só enfileira um pedido de captura.
Dê um token a cada pipeline — e um separado para uso humano, se o seu time for pedir captura durante investigação. O nome é o que aparece no registro da captura, e é ele que responde “quem pediu isto”.
A chamada
Seção intitulada “A chamada”curl -X POST https://app.rootpilot.sh/api/profiling/captures \ -H "Authorization: Bearer $ROOTPILOT_CAPTURE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "workload": "checkout-api", "version": "v2.14.0", "env": "producao", "duration_sec": 60 }'| campo | obrigatório | o que é |
|---|---|---|
workload |
sim | o nome do processo, exatamente como está na sua lista de alvos. Fora dela, o Profiler recusa. |
version |
sim | a versão que acabou de subir. É a chave do diff — a captura de v2.14.0 é comparada com a de v2.13.0. |
env |
não | producao, staging… Vazio é legítimo: nem toda frota tem o conceito. |
kind |
não (head) |
head é a captura da versão. replicate é a mesma versão medida de novo, e é o que sustenta o piso de ruído e a janela de confirmação — chame algumas vezes se quiser um diff mais confiante. |
duration_sec |
não (60) |
entre 5 e 300. |
warmup_sec |
não (5) |
entre 0 e 60 — o tempo descartado antes de começar a medir. |
duration_sec/durationSec e warmup_sec/warmupSec são aceitos nas duas grafias, e o token também
pode ir como ?token= — alguns passos declarativos de CI não deixam pôr header.
As respostas, e cada uma pede uma ação diferente:
- 202 —
{"request_id": "…", "status": "queued", "deduped": false}. O pedido entrou na fila e é drenado em segundos.deduped: truequer dizer que já havia um pedido igual em voo e você recebeu o id dele: repetir a chamada não empilha capturas. - 400 — o corpo não passou na validação, e a mensagem nomeia o campo.
- 401 — token ausente, inválido ou revogado.
- 403 — o Profiler não está habilitado na organização. É o opt-in, em Configurações.
- 429 — o teto é de 20 pedidos por hora por organização, e a resposta traz
retry_after_seconds. O teto é por organização e não por IP de propósito: quem paga a conta é o processo observado, não a rede de onde o pedido saiu.
O 202 diz que o pedido entrou, não que a captura aconteceu. Pode não haver Profiler vivo naquele
instante (o pedido espera), e o alvo pode não estar rodando em nó nenhum. O desfecho — pushed,
nothing_to_push ou failed, com o motivo ao lado — aparece em Profiling, no app.
O que ainda não foi verificado
Seção intitulada “O que ainda não foi verificado”Dizemos isto aqui porque a alternativa é você descobrir sozinho:
- Kubernetes, em nenhum cluster.
- JVM: o eixo de símbolo para a JVM está implementado e testado unitariamente, e nunca rodou contra uma JVM viva.
Docker em amd64 e arm64 é o que está exercitado.