Communitygithub.com

bilgihankose/k8s-debug-bridge

Depura un servicio de Kubernetes en vivo desde tu portátil. Cinco scripts bash + una habilidad de agente — sin agente, sin operador, sin instalación.

¿Qué es k8s-debug-bridge?

K8s Debug Bridge es una habilidad de agente AI que permite a los desarrolladores depurar servicios de Kubernetes/OpenShift en vivo desde su máquina local, utilizando únicamente herramientas estándar como `kubectl`/`oc exec`, bash `/dev/tcp` y `nc` dentro de un pod bridge temporal. No requiere instalar agentes, CLI adicionales ni operadores en el clúster. Funciona con cualquier distribución de Kubernetes (vanilla, OpenShift, EKS, GKE, AKS) y se gestiona a través de cinco scripts bash que verifican el entorno, crean un pod bridge, redirigen temporalmente el selector del servicio, establecen un túnel inverso y limpian los recursos al finalizar. La habilidad es independiente del lenguaje de programación: solo transporta tráfico, y el usuario puede conectar su propio recargador/depurador (como nodemon+inspector para Node.js o watchdog+debugpy para Python). El flujo incluye pasos críticos de seguridad: siempre pide confirmación antes de modificar el selector del servicio, guarda el selector original (tanto en estado local como en una anotación del servicio) para garantizar la reversión, y se autolimpia mediante traps de señal. Es importante que la aplicación local ya esté escuchando en el puerto antes de redirigir el tráfico para evitar errores de conexión. La habilidad también advierte sobre los riesgos reales de olvidar revertir el selector o de usar permisos exec de forma inadecuada.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

K8s Debug Bridge

Redirige tráfico en vivo de un servicio de Kubernetes/OpenShift a una máquina local para depuración, usando solo kubectl/oc exec, el /dev/tcp integrado de bash y el nc dentro del pod bridge — sin necesidad de instalar CLI o agente adicional. Mecanismo: se crea un "pod bridge" temporal, el spec.selector del servicio destino se cambia temporalmente a la etiqueta de este pod, y un túnel inverso conecta el pod con el puerto local. Funciona en cualquier distribución de Kubernetes (vanilla K8s, OpenShift, EKS/GKE/AKS) — usa oc si está disponible, si no, kubectl (ver scripts/lib-kube-cli.sh). README.md cubre la justificación y las ventajas/desventajas frente a herramientas como Telepresence y mirrord.

Esto no está ligado a un solo lenguaje/framework: el bridge solo transporta tráfico. VS Code + Air + Delve (Go) es solo un ejemplo de integración — si trabajas en otro lenguaje, conecta tu propio watcher/depurador (Node: nodemon+inspector, Python: watchdog+debugpy, etc.) sobre el mismo túnel. Si el lado local del usuario aún no está configurado — sin recargador, sin depurador adjunto, o si quiere que se adapte a su propio proyecto — lee docs/local-debug-setup.md en ese momento y síguelo. No reproduzcas su contenido de memoria, y no lo leas cuando la aplicación local ya se esté ejecutando y escuchando.

Esta habilidad crea un recurso real en tu clúster y modifica un recurso real: crea un Pod y cambia temporalmente el spec.selector de un Servicio. No son simulaciones — es estado real del clúster. Debe haber un plan de reversión en cada paso — nunca cambies el selector y te olvides de ello.

Prerrequisitos — verificar antes de preguntar al usuario

No le preguntes al usuario el nombre del namespace/servicio y lo pases ciegamente a bridge-up.sh — un nombre mal escrito fallará a mitad del script. Primero ejecuta scripts/check-env.sh [namespace] (solo lectura, no cambia nada):

  • Ejecutar sin argumentos: muestra el estado de inicio de sesión y los namespaces accesibles. Si no has iniciado sesión, detente aquí y dile al usuario que ejecute oc login/kubectl config use-context.
  • Ejecutar con un namespace: verifica que el namespace existe y lista los Servicios dentro de él (nombre + puerto + targetPort, incluidos los que ya están en bridge).

Muestra esta salida al usuario y deja que elija el Servicio destino de la lista — no le hagas adivinar. Luego:

  • La imagen del pod bridge — siempre pregunta, nunca asumas. No elijas un valor por defecto y lo ejecutes: pregunta directamente al usuario "¿qué imagen debería usar el pod bridge?" y espera una respuesta. La imagen debe estar aprobada y ser accesible desde ese clúster específico, y solo ellos saben cuál es — muchos clústeres solo extraen de un registro interno aprobado, por lo que una imagen adivinada puede fallar al extraerse con el Servicio ya redirigido. nicolaka/netshoot es un ejemplo que se puede ofrecer, no un fallback para usar en silencio. Cualquiera que elijan debe incluir nc: el lado del pod usa nc en lugar de socat porque las imágenes endurecidas a menudo eliminan socat pero mantienen el nc de Nmap. tunnel.sh verifica la presencia de nc antes de abrir el túnel y se detiene con un error claro si la imagen no lo tiene.
  • Manifiesto del pod: el pod bridge se renderiza desde assets/bridge-pod.yaml. Si el clúster requiere imagePullSecrets, un securityContext, nodeSelector/tolerations o límites de recursos diferentes, edita ese archivo — no lo resuelvas en los scripts. Mantén la etiqueta debug: "__BRIDGE_NAME__" y el nombre del contenedor debug-bridge; los otros scripts se dirigen al pod a través de ellos.
  • El puerto: usa el targetPort numérico del Servicio de la salida de check-env.sh, NO un puerto libre arbitrario. Si el Servicio usa un targetPort con nombre (ej. http), resuélvelo a su puerto de contenedor numérico. kube-proxy reenvía el tráfico al targetPort del pod una vez que el selector se redirige — si eliges un número diferente, el tráfico nunca llega al pod bridge.
  • Dependencias salientes: este bridge solo maneja tráfico entrante (Clúster → Local). Si el código local debe llamar a otros servicios del clúster (.svc.cluster.local), usa Routes/Ingress externos o port-forwards locales.
  • Secretos y entorno: nunca ejecutes oc exec ... env, printenv, set, o un comando similar contra un pod de aplicación. Los valores pueden ser credenciales multilínea y se filtrarán en la salida del terminal/herramienta incluso cuando un comando parece seleccionar solo nombres. Nunca copies valores de entorno de un pod en un comando local, historial de shell o .env. Requiere un flujo de trabajo de gestión de secretos local preexistente o haz que el usuario proporcione el archivo de entorno local necesario fuera de banda. Verifica solo que la aplicación local se inicie; no imprimas su entorno.

La aplicación local ya debe estar ejecutándose antes de tocar el Servicio. Esto es una condición previa, no un paso posterior: el túnel entrega tráfico a 127.0.0.1:<puerto>, por lo que si nada escucha allí cuando el selector cambia, cada solicitud interceptada falla con connection-refused/502 mientras los pods reales ya están fuera de rotación. Haz que el usuario inicie su aplicación (con un archivo de entorno local provisionado de forma segura — nunca uno leído de un pod del clúster) y confirma el listener inmediatamente antes del paso 2:

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

Flujo

  1. Configurar el bridge: Antes de ejecutar nada, dile claramente al usuario: "Crearé un Pod real llamado <bridge-pod> en el namespace <namespace> (imagen: <image>) — consume recursos del clúster y se eliminará cuando termines." Después de obtener aprobación, ejecuta scripts/bridge-up.sh <namespace> <service> <bridge-pod> <image> [port] — esto crea el pod bridge y guarda el selector original tanto en estado local como en una anotación k8s-debug-bridge/owner en el Servicio (no requiere python3/jq, bash simple). No te saltes este paso — nunca redirijas sin haber guardado el selector original, o no habrá forma de volver atrás. Si el servicio ya está en bridge por otra persona, este script falla y se detiene (verificación de anotación) — en ese caso, muestra al usuario el propietario del mensaje de error, no lo intentes tú mismo.

  2. Redirigir el servicio: Antes de cambiar el selector del Servicio destino por la etiqueta del pod bridge, dile claramente al usuario y obtén aprobación real: "A partir de este momento, TODO el tráfico a <service> irá a tu pod bridge — los pods reales dejarán de recibir tráfico." Después de la aprobación, ejecuta scripts/redirect-service.sh <namespace> <service> --yes — --yes omite el prompt interactivo read del script (ya que obtuviste aprobación en el chat); sin --yes el script espera en stdin y puede colgarse. El script reemplaza el mapa de selector completo solo con debug=<bridge-pod>; no debe fusionar esta etiqueta en el selector original, porque el pod bridge no lleva las etiquetas originales de la aplicación.

  3. Iniciar el túnel: scripts/tunnel.sh <namespace> <service> <bridge-pod> <port> — este script se bloquea, por lo que debe ejecutarse en su propio terminal o en un proceso en segundo plano. Se autolimpia: el trap captura EXIT/INT/TERM, por lo que Ctrl+C o cualquier otra terminación llama automáticamente a cleanup.sh. Si lo iniciaste en segundo plano tú mismo, no hay Ctrl+C que presionar — detenlo con kill -TERM <pid>, que el trap también maneja; anota el PID cuando lo lances y dile al usuario cuál es. Ejecutar cleanup.sh manualmente solo es necesario si el proceso se mata de forma inatrapable (kill -9). Transporta una conexión a la vez y crea automáticamente un nuevo listener del lado del pod después de que cada conexión se cierra, así que deja el mismo túnel funcionando entre solicitudes de depuración secuenciales. Si el túnel registra repetidamente WARNING: could not carry a connection, nada está escuchando en 127.0.0.1:<puerto> — la aplicación local está caída, aún compilando, o una recarga en caliente liberó el puerto. Arregla eso en lugar de reiniciar el túnel.

  4. Depuración local: el puerto que puenteas es el puerto en el que sirve la aplicación (el targetPort del Servicio), nunca el puerto en el que escucha su depurador. Si el usuario dice "mi depurador está en 2345, puentea eso", está confundiendo los dos: corrígelo, pregúntale en qué puerto sirve la aplicación misma, y puentea ese. El tráfico enviado a un puerto de depurador devuelve 502 que parecen un túnel roto. La aplicación local ya se está ejecutando y verificada (ver la condición previa anterior); es la que recibe el tráfico del túnel. El depurador de su lenguaje (ej. Delve, un inspector de Node) se adjunta a esa aplicación en ejecución por separado, generalmente en un puerto diferente — ej. en Go, Air + Delve en el puerto 2345 mientras la aplicación misma escucha en el <puerto> del túnel. Una recarga en caliente puede eliminar brevemente el listener; mientras está ausente, el bridge devuelve fallos de conexión/502. Si la aplicación local no se recupera rápidamente, detén el túnel y limpia el Servicio en lugar de dejar todo el tráfico apuntando a un bridge fallido. El bridge transporta la ruta HTTP sin cambios: configura el prefijo base de la aplicación local para que coincida con la ruta entregada al Servicio; no reproduce una reescritura de Ingress. El tráfico puede venir de cualquier llamante dentro del clúster del Servicio (por ejemplo, un proxy Nginx de panel); no se requiere una Route/Ingress pública. Si la ejecución se pausa más allá del tiempo de espera del llamante, reanúdala y dispara una nueva solicitud. Después de que una recarga en caliente reinicie Delve, el IDE puede necesitar adjuntarse de nuevo. Cuando Delve se inició con --accept-multiclient, el agente también puede adjuntar un cliente Delve de terminal al puerto 2345 junto a VS Code para inspeccionar la línea de código actual, la pila y las variables. Informa las observaciones al usuario; obtén aprobación explícita antes de continuar la ejecución o cambiar el estado del depurador.

  5. Al terminar: Ctrl+C en el terminal del túnel, o kill -TERM <pid> si lo iniciaste en segundo plano — el selector del servicio y el pod bridge se revierten automáticamente, incluida la restauración del mapa de selector original completo. Si el trap no se disparó (ej. kill -9), scripts/cleanup.sh <namespace> <service> debe ejecutarse manualmente; recuérdale al usuario esto antes de que termine la sesión.

Nota de seguridad

Este método no instala ningún componente permanente en el clúster (sin webhook mutante, inyector de sidecar o RBAC a nivel de clúster) — solo un pod temporal claramente marcado por su nombre/etiqueta (debug=<nombre-pod-bridge>) y permisos exec estándar. Aun así, hay dos riesgos reales, advierte al usuario sobre ellos:

  1. Olvidar revertir el selector del servicio — una interrupción permanente en prod/stage.
  2. El permiso exec puede ser abusado a través de RBAC — esta habilidad solo debe ejecutarse en tu propio namespace, en tu propio pod bridge, y nunca debería necesitar cluster-admin.

Para pasos detallados y solución de problemas, consulta README.md.

Antes de finalizar la sesión, verifica si queda un bridge sin limpiar. El estado reside junto a scripts/, en la propia carpeta de esta habilidad — no en el directorio del proyecto del usuario. Resuélvelo relativo a los scripts que estás ejecutando, por ejemplo:

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

Ejecutar un simple find .state desde el proyecto del usuario encontrará silenciosamente nada y reportará un estado limpio incluso cuando un bridge sigue activo. Si existe un archivo .env allí, dile al usuario qué namespace/servicio nombra y sugiere scripts/cleanup.sh <namespace> <service>. Esto es un recordatorio, no una compuerta determinista — ten cuidado de no olvidarlo.

Skills relacionados