Communitygithub.com

K8s Debug Bridge

Tunnel en direct le trafic d'un service Kubernetes/OpenShift vers votre machine locale pour déboguer — cinq scripts bash, sans agent ni opérateur.

Qu'est-ce que K8s Debug Bridge ?

K8s Debug Bridge est une compétence d'agent qui crée un tunnel inverse éphémère entre un service Kubernetes (ou OpenShift) et une machine locale, en utilisant uniquement `kubectl`/`oc exec`, `/dev/tcp` de bash et `nc` — sans installer de binaire supplémentaire. Elle fonctionne sur toutes les distributions (vanilla K8s, OpenShift, EKS/GKE/AKS) en détectant automatiquement `oc` ou `kubectl`. Le mécanisme est simple : un pod pont temporaire est créé, le sélecteur du service cible est basculé sur ce pod, et le trafic est redirigé vers le port local. La compétence gère la sauvegarde et la restauration automatique du sélecteur d'origine, avec un nettoyage par `trap` pour éviter les fuites. Elle est indépendante du langage : le tunnel transporte uniquement le trafic ; le débogueur (Delve, Node inspector, debugpy) s'attache séparément à l'application locale. Une attention particulière est portée à la sécurité : pas de composant permanent, état stocké localement, et validation préalable de l'environnement via `check-env.sh`.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add bilgihankose/k8s-debug-bridge

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait K8s Debug Bridge ?

Tunnelise le trafic en direct d'un service Kubernetes/OpenShift vers une machine locale pour déboguer en utilisant uniquement les commandes standard kubectl/oc exec, le /dev/tcp intégré de bash et le nc à l'intérieur du pod pont — pas besoin d'outil CLI ou d'agent supplémentaire à installer. Mécanisme : un "pod pont" temporaire est créé, le spec.selector du service cible est temporairement basculé sur l'étiquette de ce pod, et un tunnel inverse relie le pod et le port local. Fonctionne sur toute distribution Kubernetes (vanilla K8s, OpenShift, EKS/GKE/AKS) — utilise oc si disponible, sinon kubectl (voir scripts/lib-kube-cli.sh). Le fichier README.md couvre les justifications et les compromis par rapport à des outils comme Telepresence et mirrord.

Ce n'est pas lié à un langage ou framework unique : le pont ne fait que transporter le trafic. VS Code + Air + Delve (Go) n'est qu'un exemple d'intégration — si vous travaillez dans un autre langage, connectez votre propre watcher/débogueur (Node : nodemon+inspector, Python : watchdog+debugpy, etc.) via le même tunnel. Si le côté local de l'utilisateur n'est pas encore configuré — pas de rechargement, pas de débogueur attaché, ou s'il souhaite l'adapter à son propre projet — lisez docs/local-debug-setup.md à ce moment-là et suivez-le. Ne reproduisez pas son contenu de mémoire, et ne le lisez pas lorsque l'application locale est déjà en cours d'exécution et à l'écoute.

Cette compétence crée une ressource réelle dans votre cluster et modifie une ressource réelle : elle crée un Pod et modifie temporairement le spec.selector d'un Service. Ce ne sont pas des simulations — c'est un état réel du cluster. Il doit y avoir un plan de restauration à chaque étape — ne modifiez jamais le sélecteur sans l'oublier.

Prérequis — vérifier avant de demander à l'utilisateur

Ne demandez pas à l'utilisateur le nom du namespace/service et ne le passez pas aveuglément à bridge-up.sh — un nom mal orthographié échouera en plein milieu du script. Exécutez d'abord scripts/check-env.sh [namespace] (lecture seule, ne change rien) :

  • Exécuté sans argument : liste l'état de connexion et les namespaces accessibles. Si vous n'êtes pas connecté, arrêtez-vous ici et dites à l'utilisateur d'exécuter oc login/kubectl config use-context.
  • Exécuté avec un namespace : vérifie que le namespace existe et liste les Services qu'il contient (nom + port + targetPort, y compris ceux déjà pontés).

Affichez cette sortie à l'utilisateur et laissez-le choisir le Service cible dans la liste — ne le faites pas deviner. Ensuite :

  • L'image du pod pont — demandez toujours, ne présumez jamais. Ne choisissez pas une valeur par défaut et exécutez-la : demandez carrément à l'utilisateur "quelle image le pod pont doit-il utiliser ?" et attendez une réponse. L'image doit être approuvée et accessible depuis ce cluster spécifique, et seul l'utilisateur sait laquelle — de nombreux clusters ne tirent que depuis un registre interne approuvé, donc une image devinée peut échouer au moment du pull avec le Service déjà redirigé. nicolaka/netshoot est un exemple à proposer, pas un fallback à utiliser silencieusement. Quel que soit le choix, il doit fournir nc : le côté pod utilise nc plutôt que socat car les images durcies retirent souvent socat tout en conservant le nc de Nmap. tunnel.sh vérifie la présence de nc avant d'ouvrir le tunnel et s'arrête avec une erreur claire si l'image ne l'a pas.
  • Manifeste du pod : le pod pont est rendu à partir de assets/bridge-pod.yaml. Si le cluster nécessite imagePullSecrets, un securityContext, nodeSelector/tolerations ou des limites de ressources différentes, modifiez ce fichier — ne contournez pas cela dans les scripts. Conservez l'étiquette debug: "__BRIDGE_NAME__" et le nom de conteneur debug-bridge ; les autres scripts adressent le pod via eux.
  • Le port : utilisez le targetPort numérique du Service depuis la sortie de check-env.sh, PAS un port libre arbitraire. Si le Service utilise un targetPort nommé (par ex. http), résolvez-le en son port de conteneur numérique. kube-proxy transfère le trafic vers le targetPort du pod une fois le sélecteur redirigé — si vous choisissez un numéro différent, le trafic n'atteint jamais le pod pont.
  • Dépendances sortantes : ce pont ne gère que le trafic entrant (Cluster → Local). Si le code local doit appeler d'autres services du cluster (.svc.cluster.local), utilisez des Routes/Ingress externes ou des port-forwards locaux.
  • Secrets et environnement : n'exécutez jamais oc exec ... env, printenv, set, ou une commande similaire contre un pod applicatif. Les valeurs peuvent être des identifiants multilignes et fuiront dans la sortie du terminal/outil même lorsqu'une commande semble sélectionner uniquement les noms. Ne copiez jamais les valeurs d'environnement d'un pod dans une commande locale, un historique de shell ou un fichier .env. Exigez un workflow de gestion des secrets local préexistant ou demandez à l'utilisateur de fournir le fichier d'environnement local nécessaire hors bande. Vérifiez uniquement que l'application locale démarre ; n'affichez pas son environnement.

L'application locale doit déjà être en cours d'exécution avant de toucher au Service. C'est une condition préalable, pas une étape ultérieure : le tunnel délivre le trafic vers 127.0.0.1:<port>, donc si rien n'écoute là au moment du basculement du sélecteur, chaque requête interceptée échoue avec connection-refused/502 pendant que les vrais pods sont déjà hors rotation. Demandez à l'utilisateur de démarrer son application (avec un fichier d'environnement local provisionné en toute sécurité — jamais lu depuis un pod du cluster) et confirmez l'écouteur immédiatement avant l'étape 2 :

lsof -nP -iTCP:<port> -sTCP:LISTEN

Déroulement

  1. Configurer le pont : Avant d'exécuter quoi que ce soit, dites clairement à l'utilisateur : "Je vais créer un vrai Pod nommé <bridge-pod> dans le namespace <namespace> (image : <image>) — il consomme des ressources du cluster et sera supprimé une fois que vous aurez terminé." Après approbation, exécutez scripts/bridge-up.sh <namespace> <service> <bridge-pod> <image> [port] — cela crée le pod pont et enregistre le sélecteur d'origine à la fois dans l'état local et comme annotation k8s-debug-bridge/owner sur le Service (pas besoin de python3/jq, bash simple). Ne sautez pas cette étape — ne redirigez jamais sans que le sélecteur d'origine soit enregistré, sinon il n'y a aucun moyen de revenir en arrière. Si le service est déjà ponté par quelqu'un d'autre, ce script échoue et s'arrête (vérification de l'annotation) — dans ce cas, montrez à l'utilisateur le propriétaire depuis le message d'erreur, ne réessayez pas vous-même.

  2. Rediriger le service : Avant de basculer le sélecteur du Service cible sur l'étiquette du pod pont, dites clairement à l'utilisateur et obtenez une approbation réelle : "À partir de ce moment, TOUT le trafic vers <service> ira vers votre pod pont — les vrais pods cesseront de recevoir du trafic." Après approbation, exécutez scripts/redirect-service.sh <namespace> <service> --yes — --yes ignore la propre invite interactive read du script (puisque vous avez déjà obtenu l'approbation dans le chat) ; sans --yes, le script attend sur stdin et peut se bloquer. Le script remplace la carte complète du sélecteur par uniquement debug=<bridge-pod> ; il ne doit pas fusionner cette étiquette dans le sélecteur d'origine, car le pod pont ne porte pas les étiquettes originales de l'application.

  3. Démarrer le tunnel : scripts/tunnel.sh <namespace> <service> <bridge-pod> <port> — ce script bloque, donc il doit être dans son propre terminal ou un processus en arrière-plan. Il s'auto-nettoie : trap intercepte EXIT/INT/TERM, donc Ctrl+C ou toute autre terminaison appelle automatiquement cleanup.sh. Si vous l'avez démarré en arrière-plan vous-même, il n'y a pas de Ctrl+C à presser — arrêtez-le avec kill -TERM <pid>, que le trap gère également ; notez le PID lorsque vous le lancez et dites à l'utilisateur ce que c'est. Exécuter cleanup.sh manuellement n'est nécessaire que si le processus est tué de manière non interceptable (kill -9). Il transporte une connexion à la fois et crée automatiquement un nouvel écouteur côté pod après chaque fermeture de connexion, donc laissez le même tunnel en cours d'exécution entre les requêtes de débogage séquentielles. Si le tunnel enregistre de manière répétée WARNING : n'a pas pu transporter une connexion, rien n'écoute sur 127.0.0.1:<port> — l'application locale est arrêtée, encore en cours de construction, ou un rechargement à chaud a libéré le port. Corrigez cela plutôt que de redémarrer le tunnel.

  4. Débogage local : le port que vous pontiez est le port sur lequel l'application sert (le targetPort du Service), jamais le port sur lequel son débogueur écoute. Si l'utilisateur dit "mon débogueur est sur 2345, pont celui-là", il confond les deux : corrigez-le, demandez sur quel port l'application elle-même sert, et pont celui-là. Le trafic envoyé vers un port de débogueur revient sous forme de 502 qui ressemblent à un tunnel cassé. L'application locale est déjà en cours d'exécution et vérifiée (voir la condition préalable ci-dessus) ; c'est elle qui reçoit le trafic tunnelisé. Le débogueur de leur langage (par ex. Delve, un inspecteur Node) s'attache à cette application en cours d'exécution séparément, généralement sur un port différent — par ex. en Go, Air + Delve sur le port 2345 pendant que l'application elle-même écoute sur le <port> tunnelisé. Un rechargement à chaud peut supprimer brièvement l'écouteur ; pendant qu'il est absent, le pont renvoie des échecs de connexion/502. Si l'application locale ne récupère pas rapidement, arrêtez le tunnel et nettoyez le Service au lieu de laisser tout le trafic pointé vers un pont défaillant. Le pont transporte le chemin HTTP inchangé : configurez le préfixe de base de l'application locale pour correspondre au chemin délivré au Service ; il ne reproduit pas une réécriture Ingress. Le trafic peut provenir de tout appelant intra-cluster du Service (par exemple, un proxy Nginx de tableau de bord) ; une Route/Ingress publique n'est pas nécessaire. Si l'exécution est en pause au-delà du délai d'attente de l'appelant, reprenez-la et déclenchez une nouvelle requête. Après un rechargement à chaud qui redémarre Delve, l'IDE peut avoir besoin de s'attacher à nouveau. Lorsque Delve a été démarré avec --accept-multiclient, l'agent peut également attacher un client Delve en terminal au port 2345 parallèlement à VS Code pour inspecter la ligne source actuelle, la pile et les variables. Rapportez les observations à l'utilisateur ; obtenez une approbation explicite avant de continuer l'exécution ou de modifier autrement l'état du débogueur.

  5. Une fois terminé : Ctrl+C dans le terminal du tunnel, ou kill -TERM <pid> si vous l'avez démarré en arrière-plan — le sélecteur du service et le pod pont reviennent automatiquement, y compris la restauration de la carte complète du sélecteur d'origine. Si le trap n'a pas été déclenché (par ex. kill -9), scripts/cleanup.sh <namespace> <service> doit être exécuté manuellement ; rappelez-le à l'utilisateur avant qu'il ne quitte la session.

Note de sécurité

Cette méthode n'installe aucun composant permanent dans le cluster (pas de webhook mutant, d'injecteur sidecar, ou de RBAC à l'échelle du cluster) — seulement un pod temporaire clairement marqué par son nom/étiquette (debug=<bridge-pod-name>) et des permissions exec standard. Malgré cela, il y a deux risques réels, prévenez l'utilisateur :

  1. Oublier de rétablir le sélecteur du service — une panne permanente en production/staging.
  2. La permission exec pouvant être abusée via RBAC — cette compétence ne doit être exécutée que dans votre propre namespace, sur votre propre pod pont, et ne doit jamais nécessiter cluster-admin.

Pour des étapes détaillées et le dépannage, consultez README.md.

Avant de terminer la session, vérifiez si un pont non nettoyé reste. L'état se trouve à côté de scripts/, dans le propre dossier de cette compétence — pas dans le répertoire du projet de l'utilisateur. Résolvez-le par rapport aux scripts que vous exécutez, par exemple :

find "$(dirname <chemin-vers>/scripts)/.state" -name '*.env'

Exécuter un simple find .state depuis le projet de l'utilisateur ne trouvera silencieusement rien et signalera un état propre même si un pont est toujours actif. Si un fichier .env existe là, dites à l'utilisateur quel namespace/service il nomme et suggérez scripts/cleanup.sh <namespace> <service>. C'est un rappel, pas une porte déterministe — faites attention à ne pas l'oublier.

Skills associés