K8s Debug Bridge
Cria um túnel de tráfego ativo de um Serviço Kubernetes/OpenShift para uma máquina local
para depuração usando apenas kubectl/oc exec padrão, o /dev/tcp embutido do bash e o
nc dentro do pod bridge — sem necessidade de CLI ou agente extra para instalar.
Mecanismo: um "pod bridge" temporário é criado, o spec.selector do Serviço alvo é
temporariamente alterado para o label deste pod, e um túnel reverso conecta o pod e a
porta local. Funciona em qualquer distribuição Kubernetes (vanilla K8s, OpenShift,
EKS/GKE/AKS) — usa oc se disponível, senão kubectl (veja scripts/lib-kube-cli.sh).
O README.md aborda a lógica e as desvantagens em relação a ferramentas como
Telepresence e mirrord.
Isto não está vinculado a uma única linguagem/framework: a ponte apenas transporta
tráfego. VS Code + Air + Delve (Go) é apenas um exemplo de integração — se você
trabalha em outra linguagem, conecte seu próprio watcher/depurador (Node: nodemon+
inspector, Python: watchdog+debugpy, etc.) através do mesmo túnel.
Se o lado local do usuário ainda não estiver configurado — sem recarregador, sem
depurador anexado, ou se eles quiserem adaptá-lo ao seu próprio projeto — leia
docs/local-debug-setup.md nesse ponto e siga-o. Não reproduza seu conteúdo de
memória e não o leia quando o aplicativo local já estiver em execução e escutando.
Esta skill cria um recurso real em seu cluster e modifica um recurso real:
ela cria um Pod e altera temporariamente o spec.selector de um Serviço. Estas não são
simulações — este é um estado real do cluster. Deve haver um plano de rollback em cada
etapa — nunca altere o seletor e esqueça dele.
Pré-requisitos — verifique antes de perguntar ao usuário
Não pergunte ao usuário o namespace/nome do serviço e passe cegamente para
bridge-up.sh — um nome digitado incorretamente falhará no meio do script. Execute
primeiro scripts/check-env.sh [namespace] (somente leitura, não altera nada):
- Executar sem argumentos: lista o status de login e namespaces acessíveis. Se não
estiver logado, pare aqui e diga ao usuário para executar
oc login/kubectl config use-context. - Executar com um namespace: verifica se o namespace existe e lista os Serviços dentro dele (nome + porta + porta alvo, incluindo aqueles já com bridge).
Mostre esta saída ao usuário e deixe-o escolher o Serviço alvo da lista — não o faça adivinhar. Em seguida:
- A imagem do pod bridge — sempre pergunte, nunca presuma. Não escolha um padrão e
execute com ele: pergunte diretamente ao usuário "qual imagem o pod bridge deve usar?" e
aguarde uma resposta. A imagem precisa ser aprovada e acessível por aquele cluster
específico, e apenas eles sabem qual é — muitos clusters puxam apenas de um
registro interno aprovado, portanto uma imagem adivinhada pode falhar ao puxar com o
Serviço já redirecionado.
nicolaka/netshooté um exemplo a oferecer, não um substituto para usar silenciosamente. O que quer que escolham precisa ter onc: o lado do pod usancem vez de socat porque imagens reduzidas frequentemente removemsocatenquanto mantêm oncdo Nmap.tunnel.shverifica a presença dencantes de abrir o túnel e para com um erro claro se a imagem não o tiver. - Manifesto do pod: o pod bridge é renderizado a partir de
assets/bridge-pod.yaml. Se o cluster exigir imagePullSecrets, um securityContext, nodeSelector/tolerations ou limites de recursos diferentes, edite esse arquivo — não contorne isso nos scripts. Mantenha o labeldebug: "__BRIDGE_NAME__"e o nome do contêinerdebug-bridge; os outros scripts se referem ao pod através deles. - A porta: use o
targetPortnumérico do Serviço da saída decheck-env.sh, NÃO uma porta livre arbitrária. Se o Serviço usar um targetPort nomeado (ex.:http), resolva-o para sua porta de contêiner numérica. O kube-proxy encaminha o tráfego para otargetPortdo pod assim que o seletor é redirecionado — se você escolher um número diferente, o tráfego nunca alcançará o pod bridge. - Dependências de saída: esta ponte lida apenas com tráfego de entrada (Cluster → Local).
Se o código local precisar chamar outros serviços do cluster (
.svc.cluster.local), use Routes/Ingress externos ou port-forwards locais. - Segredos e ambiente: nunca execute
oc exec ... env,printenv,setou um comando similar contra um pod de aplicação. Valores podem ser credenciais multilinha e vazarão para a saída do terminal/ferramenta mesmo quando um comando parecer selecionar apenas nomes. Nunca copie valores de ambiente de um pod para um comando local, histórico do shell ou.env. Exija um fluxo de trabalho de gerenciamento de segredos local pré-existente ou peça ao usuário para fornecer o arquivo de ambiente local necessário fora da banda. Verifique apenas se o aplicativo local inicia; não imprima seu ambiente.
O aplicativo local já deve estar em execução antes de você tocar no Serviço. Esta é
uma pré-condição, não uma etapa posterior: o túnel entrega tráfego para 127.0.0.1:<porta>, portanto
se nada estiver escutando lá quando o seletor for alterado, cada requisição interceptada falhará com
conexão-recusada/502 enquanto os pods reais já estão fora de rotação. Peça ao usuário
para iniciar seu aplicativo (com um arquivo de ambiente local provisionado com segurança
— nunca um lido de um pod do cluster) e confirme o listener imediatamente antes da etapa 2:
lsof -nP -iTCP:<porta> -sTCP:LISTEN
Fluxo
-
Configurar a bridge: Antes de executar qualquer coisa, informe claramente ao usuário: "Criarei um Pod real chamado
<bridge-pod>no namespace<namespace>(imagem:<imagem>) — ele consumirá recursos do cluster e será excluído quando terminar." Após obter aprovação, executescripts/bridge-up.sh <namespace> <serviço> <bridge-pod> <imagem> [porta]— isso cria o pod bridge e salva o seletor original tanto no estado local quanto como uma anotaçãok8s-debug-bridge/ownerno Serviço (sem necessidade de python3/jq, bash puro). Não pule esta etapa — nunca redirecione sem ter o seletor original salvo, ou não haverá como voltar. Se o serviço já estiver com bridge criada por outra pessoa, este script falha e para (verificação de anotação) — nesse caso mostre ao usuário o proprietário da mensagem de erro, não tente novamente. -
Redirecionar o serviço: Antes de alterar o seletor do Serviço alvo para o label do pod bridge, informe claramente ao usuário e obtenha aprovação real: "A partir deste momento, TODO o tráfego para
<serviço>irá para seu pod bridge — os pods reais pararão de receber tráfego." Após aprovação, executescripts/redirect-service.sh <namespace> <serviço> --yes—--yespula o prompt interativoreaddo próprio script (já que você obteve aprovação no chat); sem--yeso script aguarda em stdin e pode travar. O script substitui o mapa completo do seletor por apenasdebug=<bridge-pod>; ele não deve mesclar este label no seletor original, porque o pod bridge não carrega os labels originais da aplicação. -
Iniciar o túnel:
scripts/tunnel.sh <namespace> <serviço> <bridge-pod> <porta>— este script bloqueia, portanto deve ser executado em seu próprio terminal ou em um processo em segundo plano. Ele se auto-limpa:trapcaptura EXIT/INT/TERM, então Ctrl+C ou qualquer outra terminação chama automaticamentecleanup.sh. Se você o iniciou em segundo plano, não há Ctrl+C para pressionar — pare-o comkill -TERM <pid>, que o trap também trata; anote o PID ao iniciá-lo e informe ao usuário qual é. Executarcleanup.shmanualmente só é necessário se o processo for morto de forma não capturável (kill -9). Ele carrega uma conexão por vez e cria automaticamente um novo listener no lado do pod após cada conexão ser fechada, portanto mantenha o mesmo túnel em execução entre requisições de depuração sequenciais. Se o túnel registrar repetidamenteWARNING: could not carry a connection, nada está escutando em127.0.0.1:<porta>— o aplicativo local está parado, ainda compilando, ou um recarregamento a quente liberou a porta. Corrija isso em vez de reiniciar o túnel. -
Depuração local: a porta que você faz a bridge é a porta na qual o aplicativo serve (o
targetPortdo Serviço), nunca a porta em que seu depurador escuta. Se o usuário disser "meu depurador está na 2345, faça a bridge disso", ele está confundindo as duas coisas: corrija-o, pergunte em qual porta o aplicativo em si serve, e faça a bridge dessa. O tráfego enviado para uma porta de depurador retorna como 502s que parecem um túnel quebrado. O aplicativo local já está em execução e verificado (veja a pré-condição acima); é ele quem recebe o tráfego do túnel. O depurador de sua linguagem (ex.: Delve, um inspetor Node) se anexa a esse aplicativo em execução separadamente, geralmente em uma porta diferente — ex.: em Go, Air + Delve na porta 2345 enquanto o aplicativo em si escuta na<porta>do túnel. Um recarregamento a quente pode remover brevemente o listener; enquanto ele estiver ausente, a bridge retorna falhas de conexão/502s. Se o aplicativo local não se recuperar rapidamente, pare o túnel e limpe o Serviço em vez de deixar todo o tráfego apontado para uma bridge com falha. A bridge transporta o caminho HTTP inalterado: configure o prefixo base do aplicativo local para corresponder ao caminho entregue ao Serviço; ela não reproduz uma reescrita de Ingress. O tráfego pode vir de qualquer chamador dentro do cluster do Serviço (por exemplo, um proxy Nginx de dashboard); uma Route/Ingress pública não é necessária. Se a execução estiver pausada além do tempo limite do chamador, retome-a e dispare uma nova requisição. Após um recarregamento a quente reiniciar o Delve, a IDE pode precisar se anexar novamente. Quando o Delve foi iniciado com--accept-multiclient, o agente também pode anexar um cliente Delve de terminal à porta2345junto com o VS Code para inspecionar a linha de origem atual, pilha e variáveis. Relate observações ao usuário; obtenha aprovação explícita antes de continuar a execução ou alterar o estado do depurador. -
Quando terminar: Ctrl+C no terminal do túnel, ou
kill -TERM <pid>se você o iniciou em segundo plano — o seletor do serviço e o pod bridge revertem automaticamente, incluindo a restauração do mapa completo do seletor original. Se otrapnão disparou (ex.:kill -9),scripts/cleanup.sh <namespace> <serviço>deve ser executado manualmente; lembre o usuário disso antes de ele sair da sessão.
Nota de segurança
Este método não instala nenhum componente permanente no cluster (sem webhook de mutação,
injetor de sidecar ou RBAC em todo o cluster) — apenas um pod temporário claramente
marcado por seu nome/label (debug=<bridge-pod-name>) e permissões padrão de exec.
Mesmo assim, existem dois riscos reais: avise o usuário sobre eles:
- Esquecer de reverter o seletor do serviço — uma indisponibilidade permanente em prod/staging.
- A permissão
execpode ser abusada via RBAC — esta skill deve ser executada apenas em seu próprio namespace, em seu próprio pod bridge, e nunca deve precisar de cluster-admin.
Para etapas detalhadas e solução de problemas, veja README.md.
Antes de encerrar a sessão, verifique se alguma bridge não limpa permanece. O estado
reside ao lado de scripts/, na própria pasta desta skill — não no diretório de projeto do usuário.
Resolva-o relativo aos scripts que você está executando, ex.:
find "$(dirname <caminho-para>/scripts)/.state" -name '*.env'
Executar um find .state simples a partir do diretório do usuário não encontrará nada e
relatará um estado limpo mesmo quando uma bridge ainda estiver ativa. Se um arquivo .env
existir lá, informe ao usuário qual namespace/serviço ele nomeia e sugira
scripts/cleanup.sh <namespace> <serviço>. Isso é um lembrete, não uma porta determinística
— tome cuidado para não esquecê-lo.