K8s Debug Bridge
ライブトラフィックを Kubernetes/OpenShift サービスからローカルマシンにトンネリングしてデバッグします。
使用するのは標準の kubectl/oc exec、bash の組み込み /dev/tcp、およびブリッジポッド内の nc のみ — 追加のCLIやエージェントは不要です。
仕組み: 一時的な「ブリッジポッド」が起動され、対象サービスの
spec.selector が一時的にこのポッドのラベルに切り替えられ、リバーストンネルが
ポッドとローカルポートを接続します。任意の Kubernetes ディストリビューション(Vanilla
K8s、OpenShift、EKS/GKE/AKS)で動作します — oc が利用可能な場合はそれを使用し、それ以外では kubectl を使用します(
scripts/lib-kube-cli.sh を参照)。README.md では、その根拠と、
Telepresence や mirrord などのツールとのトレードオフについて説明しています。
これは単一の言語/フレームワークに依存しません。ブリッジはトラフィックを転送するだけです。
VS Code + Air + Delve(Go)は単なる例の統合です — 他の言語で作業している場合は、
同じトンネル上で独自のウォッチャー/デバッガー(Node: nodemon+inspector、Python:
watchdog+debugpy など)を接続してください。
ユーザーのローカル側がまだセットアップされていない場合 — リローダーがない、デバッガーが
アタッチされていない、または独自のプロジェクトに適応させたい場合は、その時点で
docs/local-debug-setup.md を読み、それに従ってください。その内容を記憶から再現したり、
ローカルアプリがすでに実行されリッスンしているときにそれを読んだりしないでください。
このスキルはクラスター内に実際のリソースを作成し、実際のリソースを変更します:
Podを作成し、Serviceの spec.selector を一時的に変更します。これらは
シミュレーションではありません — これは実際のクラスター状態です。すべてのステップに
ロールバック計画がなければなりません — セレクターを変更したまま忘れることは絶対にしないでください。
前提条件 — ユーザーに確認する前に検証すること
ユーザーに名前空間/サービス名を尋ねてそのまま
bridge-up.sh に渡さないでください — 名前を間違えるとスクリプトの途中で失敗します。
最初に scripts/check-env.sh [namespace] を実行してください(読み取り専用、何も変更しません):
- 引数なしで実行: ログイン状態とアクセス可能な名前空間を一覧表示します。ログインしていない場合は、
ここで停止し、
oc login/kubectl config use-contextを実行するようにユーザーに指示してください。 - 名前空間を指定して実行: 名前空間が存在することを確認し、その内部のサービスを一覧表示します(名前 + ポート + targetPort、 すでにブリッジされているものも含む)。
この出力をユーザーに表示し、リストから対象のサービスを選択させてください — 推測させないでください。その後:
- ブリッジポッドイメージ — 常に尋ね、決して推測しないでください。 デフォルトを選んでそのまま実行しないでください: ユーザーに
「ブリッジポッドはどのイメージを使用しますか?」 と明確に尋ね、回答を待ってください。
イメージはその特定のクラスターで承認され、到達可能でなければならず、
それを知っているのはユーザーだけです — 多くのクラスターは承認された内部レジストリからのみプルするため、
推測したイメージは、サービスがすでにリダイレクトされた状態でプル時に失敗する可能性があります。
nicolaka/netshootは提供する例であり、黙って使用するフォールバックではありません。 ユーザーが選ぶものはncを同梱している必要があります: ポッド側では socat ではなくncを使用します。これは、 強化されたイメージではsocatが削除されていることが多い一方で、Nmap のncは保持されているためです。tunnel.shはトンネルを開く前にncをチェックし、イメージにそれがない場合は明確なエラーで停止します。 - Pod マニフェスト: ブリッジポッドは
assets/bridge-pod.yamlからレンダリングされます。クラスターが imagePullSecrets、securityContext、nodeSelector/tolerations、または 異なるリソース制限を必要とする場合は、そのファイルを編集してください — スクリプト内で回避しないでください。debug: "__BRIDGE_NAME__"ラベルとdebug-bridgeコンテナ名は保持してください。他の スクリプトはこれらを通じてポッドを参照します。 - ポート:
check-env.shの出力からサービスの数値 targetPort を使用し、 任意の空きポートは使用しないでください。Service が名前付き targetPort(例:http)を使用している場合は、 それを数値のコンテナポートに解決してください。セレクターがリダイレクトされると、kube-proxy はトラフィックをポッドのtargetPortに転送します — 異なる番号を選んだ場合、 トラフィックはブリッジポッドにまったく到達しません。 - アウトバウンド依存関係: このブリッジはインバウンドトラフィック(クラスター→ローカル)のみを処理します。
ローカルコードが他のクラスターサービス(
.svc.cluster.local)を呼び出す必要がある場合は、外部の Routes/Ingress またはローカルポートフォワードを使用してください。 - シークレットと環境変数: アプリケーションポッドに対して
oc exec ... env、printenv、set、または 同様のコマンドを絶対に実行しないでください。値が複数行の資格情報である可能性があり、 コマンドが名前のみを選択するように見えても、ターミナル/ツールの出力に漏洩します。 ポッドの環境変数の値をローカルコマンド、シェル履歴、または.envファイルに決してコピーしないでください。 事前に存在するローカルシークレット管理ワークフローを要求するか、ユーザーに必要な ローカル env ファイルを帯域外で提供させてください。ローカルアプリケーションが起動することのみを確認してください。 その環境変数を出力しないでください。
ローカルアプリケーションは、Service に触れる前に、すでに実行されている必要があります。 これは
後のステップではなく前提条件です: トンネルは 127.0.0.1:<port> にトラフィックを配信するため、
セレクターが切り替わったときにそこでリッスンしているものがない場合、
実際のポッドがローテーションから外れた状態で、すべてのインターセプトされたリクエストは
connection-refused/502 で失敗します。ユーザーにアプリを起動させ(安全にプロビジョニングされたローカル env ファイルを使用 —
クラスターポッドから読み取ったものは決して使用しないでください)、
ステップ2の直前にリスナーを確認してください:
lsof -nP -iTCP:<port> -sTCP:LISTEN
フロー
-
ブリッジをセットアップ: 何かを実行する前に、ユーザーに明確に伝えてください: 「
<namespace>名前空間に<bridge-pod>という名前の実際のPodを作成します(イメージ:<image>)— これはクラスターリソースを消費し、完了後に削除されます。」 承認を得たら、scripts/bridge-up.sh <namespace> <service> <bridge-pod> <image> [port]を実行します — これによりブリッジポッドが作成され、元のセレクターがローカル状態と Service 上のk8s-debug-bridge/ownerアノテーションの両方に保存されます(python3/jq は不要、プレーンな bash)。このステップをスキップしないでください — 元のセレクターを保存せずにリダイレクトしないでください。保存しないと元に戻す方法がなくなります。サービスがすでに他の誰かによってブリッジされている場合、このスクリプトは失敗して停止します(アノテーションチェック)— その場合はエラーメッセージから所有者をユーザーに表示し、自分で再試行しないでください。 -
サービスをリダイレクト: 対象サービスのセレクターをブリッジポッドのラベルに切り替える前に、ユーザーに明確に伝え、本当の承認を得てください: 「この瞬間から、
<service>へのすべてのトラフィックはブリッジポッドに送られます — 実際のポッドはトラフィックを受け取らなくなります。」 承認後、scripts/redirect-service.sh <namespace> <service> --yesを実行します —--yesはスクリプト自身のインタラクティブなreadプロンプトをスキップします(チャットですでに承認を得ているため);--yesがない場合、スクリプトは stdin で待機し、ハングする可能性があります。 このスクリプトは完全なセレクターマップをdebug=<bridge-pod>のみに置き換えます。このラベルを元のセレクターにマージしてはいけません。なぜなら、ブリッジポッドはアプリケーションの元のラベルを持っていないからです。 -
トンネルを開始:
scripts/tunnel.sh <namespace> <service> <bridge-pod> <port>を実行します — このスクリプトはブロックするため、専用のターミナルまたはバックグラウンドプロセスで実行します。自動クリーンアップ:trapが EXIT/INT/TERM をキャッチするため、Ctrl+C またはその他の終了は自動的にcleanup.shを呼び出します。バックグラウンドで自分で起動した場合、Ctrl+C を押すことはできません —kill -TERM <pid>で停止してください。これも trap で処理されます。起動時に PID をメモし、ユーザーにその PID を伝えてください。cleanup.shを手動で実行する必要があるのは、プロセスがキャッチ不可で強制終了された場合のみです(kill -9)。 このスクリプトは一度に1つの接続を処理し、各接続が閉じられた後に自動的に新しいポッド側リスナーを作成するため、連続するデバッグリクエストの間、同じトンネルを実行したままにできます。 トンネルが繰り返しWARNING: could not carry a connectionをログに記録する場合、127.0.0.1:<port>でリッスンしているものはありません — ローカルアプリがダウンしている、まだビルド中である、またはホットリロードがポートを解放した可能性があります。トンネルを再起動するのではなく、そちらを修正してください。 -
ローカルデバッグ: ブリッジするポートは、アプリケーションが稼働するポート(Service の
targetPort)であり、デバッガーがリッスンするポートではありません。ユーザーが「私のデバッガーは2345にあります。それをブリッジしてください」と言った場合、それらを混同しています: 訂正し、アプリケーション自体がどのポートで稼働しているかを尋ね、そのポートをブリッジしてください。デバッガーポートに送信されたトラフィックは、壊れたトンネルのように見える502として返ってきます。 ローカルアプリはすでに実行され、確認されています(上記の前提条件を参照)。トンネルされたトラフィックを受け取るのはこのアプリです。各言語のデバッガー(例: Delve、Node インスペクター)は、通常は別のポートで、その実行中のアプリケーションに個別にアタッチします。たとえば Go では、Air + Delve がポート2345でリッスンしている間、アプリ自体はトンネルされた<port>でリッスンします。 ホットリロードは一時的にリスナーを削除する可能性があります。リスナーがない間、ブリッジは接続失敗/502を返します。ローカルアプリが迅速に回復しない場合は、すべてのトラフィックが失敗しているブリッジを指したままにするのではなく、トンネルを停止し Service をクリーンアップしてください。ブリッジは HTTP パスを変更せずに転送します: Service に配信されるパスに一致するようにローカルアプリのベースプレフィックスを設定してください。Ingress の書き換えは再現しません。 トラフィックはクラスター内の Service の任意の呼び出し元(たとえば、ダッシュボード Nginx プロキシ)から送信される可能性があります。パブリック Route/Ingress は必要ありません。呼び出し元のタイムアウトを超えて実行が一時停止された場合は、再開して新しいリクエストをトリガーしてください。ホットリロード後に Delve が再起動された場合、IDE は再度アタッチする必要があるかもしれません。 Delve が--accept-multiclientで起動された場合、エージェントは VS Code と一緒に、ターミナルの Delve クライアントをポート2345にアタッチして、現在のソース行、スタック、変数を検査することもできます。観察結果をユーザーに報告してください。実行を続行したり、デバッガーの状態を変更したりする前に、明示的な承認を得てください。 -
終了時: トンネルターミナルで Ctrl+C を押すか、バックグラウンドで起動した場合は
kill -TERM <pid>を実行します — Service セレクターとブリッジポッドは自動的に元に戻ります。これには元の完全なセレクターマップの復元も含まれます。trap が発火しなかった場合(例:kill -9)、scripts/cleanup.sh <namespace> <service>を手動で実行する必要があります。セッションを終了する前に、そのことをユーザーに忘れずに伝えてください。
セキュリティに関する注意
この方法はクラスターに永続的なコンポーネント(変更可能な Webhook、サイドカーインジェクター、クラスター全体の RBAC など)をインストールしません — 名前/ラベル(debug=<bridge-pod-name>)と標準の exec 権限によって明確にマークされた一時的なポッドのみです。それでも、2つの実際のリスクがあるため、ユーザーに警告してください:
- Service セレクターを元に戻し忘れる — プロダクション/ステージングでの永続的な障害。
exec権限が RBAC を介して悪用される可能性 — このスキルは自分の名前空間内、自分のブリッジポッド上でのみ実行し、cluster-admin は決して必要としないでください。
詳細な手順とトラブルシューティングについては、README.md を参照してください。
セッションを終了する前に、クリーンアップされていないブリッジが残っていないか確認してください。状態は scripts/ の隣、このスキル自身のフォルダにあります — ユーザーのプロジェクトディレクトリにはありません。
実行するスクリプトからの相対パスで解決してください。例:
find "$(dirname <path-to>/scripts)/.state" -name '*.env'
ユーザーのプロジェクトから裸の find .state を実行すると、何も見つからず、ブリッジがまだ稼働している場合でもクリーンな状態を報告します。.env ファイルがそこに存在する場合、どの名前空間/サービスを指定しているかをユーザーに伝え、scripts/cleanup.sh <namespace> <service> を提案してください。これは忘れずに行うためのリマインダーであり、決定的なゲートではありません。