# 機能設計書 89-ポートフォワード（port-forward）

## 概要

本ドキュメントは、kubectlの`port-forward`コマンドによるポートフォワーディング機能の設計を記述する。ローカルポートとPodのポート間でTCP接続を転送し、クラスター内のサービスにローカルからアクセス可能にする。

### 本機能の処理概要

**業務上の目的・背景**：クラスター内部のPod/Serviceに対して、ローカルマシンから直接TCP接続でアクセスするための機能を提供する。Ingressやサービス公開なしにアプリケーションへアクセスできるため、開発・デバッグ時に極めて有用である。

**機能の利用シーン**：ローカルからのデータベースアクセス（mysql/postgres）、Webアプリケーションのデバッグ、APIサーバーへの直接アクセス、Serviceポート経由のアクセス（targetPort自動解決）など。

**主要な処理内容**：
1. コマンドライン引数の解析（Complete）
2. Pod/Service/Deploymentの解決とAttachable Pod取得
3. ポートマッピングの解決（Service targetPort解決、名前付きポート解決）
4. UDPポートのチェック
5. SPDY/WebSocket接続によるポートフォワーディング開始

**関連システム・外部連携**：API Serverのportforwardサブリソースを経由してkubeletに接続し、Podのポートにトンネルを確立する。SPDY/WebSocketプロトコルを使用する。

**権限による制御**：対象Podに対するpods/portforward権限が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | kubectl port-forward | 主機能 | ポートフォワーディングを行う主処理 |

## 機能種別

ネットワーク操作 / ポートフォワーディング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| TYPE/NAME | string | Yes | Pod/Service/Deployment名 | 必須 |
| [LOCAL_PORT:]REMOTE_PORT | string[] | Yes | ポートマッピング（複数可） | 少なくとも1つ必須 |
| --address | string[] | No | リッスンアドレス | デフォルト: localhost |
| --pod-running-timeout | duration | No | Pod起動待機タイムアウト | デフォルト60秒 |

### 入力データソース

- コマンドライン引数

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| フォワーディング情報 | string | 例: Forwarding from 127.0.0.1:8080 -> 5000 |

### 出力先

- 標準出力（stdout）：フォワーディング開始メッセージ
- 標準エラー出力（stderr）：エラーメッセージ

## 処理フロー

### 処理シーケンス

```
1. NewCmdPortForward: コマンド定義
2. Complete: オプション補完
   ├─ Namespace解決
   ├─ Builder構築とオブジェクト取得
   ├─ AttachablePodForObjectFn: Pod解決
   ├─ ポートマッピング解決
   │   ├─ [Service] translateServicePortToTargetPort
   │   └─ [Pod等] convertPodNamedPortToNumber
   ├─ UDPポートチェック
   └─ RESTClient/Config初期化
3. Validate: バリデーション
   ├─ Pod名必須チェック
   ├─ ポート数チェック（1つ以上）
   └─ クライアント初期化チェック
4. RunPortForward: フォワーディング実行
   └─ RunPortForwardContext
       ├─ Pod状態チェック（Runningのみ）
       ├─ シグナルハンドラ設定
       ├─ RESTClient.Post().SubResource("portforward")
       └─ PortForwarder.ForwardPorts
           ├─ createDialer（SPDY + WebSocketフォールバック）
           └─ portforward.NewOnAddresses
```

### フローチャート

```mermaid
flowchart TD
    A[開始: RunPortForwardContext] --> B[Pod.Get]
    B --> C{Pod.Status == Running?}
    C -->|No| D[エラー: pod not running]
    C -->|Yes| E[シグナルハンドラ設定]
    E --> F[RESTClient.Post /portforward]
    F --> G[createDialer]
    G --> H{WebSocket有効?}
    H -->|Yes| I[SPDYOverWebsocketDialer + SPDYDialer フォールバック]
    H -->|No| J[SPDYDialer]
    I --> K[portforward.NewOnAddresses]
    J --> K
    K --> L[ForwardPorts]
    L --> M{StopChannel or シグナル?}
    M -->|Yes| N[停止]
    M -->|No| L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | UDPポート不可 | UDPのみのポートはフォワード不可 | ポート指定時 |
| BR-02 | Runningのみ | Podが Running状態のときのみフォワード可能 | 常時 |
| BR-03 | Service targetPort解決 | Service指定時にtargetPortを自動解決 | Service指定時 |
| BR-04 | 名前付きポート解決 | ポート名をポート番号に変換 | 名前付きポート時 |
| BR-05 | ランダムローカルポート | LOCAL_PORTを省略（:REMOTE_PORT）するとランダムポート | LOCAL_PORT省略時 |
| BR-06 | WebSocketフォールバック | WebSocket接続失敗時にSPDYにフォールバック | 常時 |
| BR-07 | localhostバインド | デフォルトで127.0.0.1と::1の両方にバインド試行 | --address=localhost時 |

### 計算ロジック

- **splitPort（172-179行目）**: `LOCAL:REMOTE`形式のポート文字列を分割。`:`がない場合はLOCAL=REMOTE
- **translateServicePortToTargetPort（185-218行目）**: Serviceのspec.ports.portからtargetPortを解決
- **convertPodNamedPortToNumber（222-248行目）**: Podコンテナの名前付きポートを番号に変換

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| Pod取得 | etcd（API Server経由） | SELECT | 対象Podの情報取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バリデーションエラー | TYPE/NAMEとポートリスト未指定 | 引数を正しく指定 |
| - | 実行エラー | PodがRunning状態でない | Podの状態を確認 |
| - | ポート解決エラー | 名前付きポートがPodに存在しない | ポート名を確認 |
| - | UDPエラー | UDPのみのポートを指定 | TCPポートを指定 |
| - | 接続エラー | SPDY/WebSocket接続失敗 | ネットワーク設定を確認 |

### リトライ仕様

WebSocket接続失敗時にSPDYへのフォールバックを行う。ポートフォワードセッション終了時は再実行が必要。

## トランザクション仕様

ステートフルなTCPトンネルであり、トランザクション管理は不要。

## パフォーマンス要件

- defaultPodPortForwardWaitTimeout = 60秒
- TCPストリームの転送遅延はネットワーク遅延に依存

## セキュリティ考慮事項

- pods/portforward権限が必要（RBAC）
- --address 0.0.0.0を使用すると外部からのアクセスが可能になるため注意
- 転送データはSPDY/WebSocket経由で暗号化される（TLS）

## 備考

- ポートフォワードセッションはPodの終了で自動的に終了する
- 再接続機能はなく、手動で再実行する必要がある
- os.Interruptシグナルでセッションを停止可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | PortForwardOptions構造体（51-62行目）でオプション全体を把握 |
| 1-2 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | portForwarderインターフェース（131-133行目）でフォワーダー抽象を把握 |

#### Step 2: ポートマッピング解決を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | splitPort関数（172-179行目）でポート文字列の分割を理解 |
| 2-2 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | translateServicePortToTargetPort関数（185-218行目）でService→Pod ポート解決を理解 |
| 2-3 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | convertPodNamedPortToNumber関数（222-248行目）で名前付きポート解決を理解 |

#### Step 3: UDPチェックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | checkUDPPortInService/checkUDPPortInPod関数（283-315行目） |

#### Step 4: フォワーディング実行を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | RunPortForwardContext関数（422-456行目）で実行処理を理解 |
| 4-2 | portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | createDialer関数（139-156行目）でDialer構成を理解 |

**主要処理フロー**:
- **423-424行目**: Pod.Getで状態確認
- **428-429行目**: Running状態チェック
- **432-434行目**: シグナルハンドラ設定
- **449-455行目**: RESTClient.Post → PortForwarder.ForwardPorts

### プログラム呼び出し階層図

```
NewCmdPortForward (portforward.go:102)
    │
    ├─ Complete (portforward.go:318)
    │      ├─ Builder.Do().Object()
    │      ├─ AttachablePodForObjectFn
    │      ├─ [Service] translateServicePortToTargetPort
    │      │      └─ LookupContainerPortNumberByServicePort
    │      ├─ [Pod] convertPodNamedPortToNumber
    │      │      └─ LookupContainerPortNumberByName
    │      └─ checkUDPPortInService / checkUDPPortInPod
    │
    ├─ Validate (portforward.go:398)
    │
    └─ RunPortForward (portforward.go:415)
           └─ RunPortForwardContext (portforward.go:422)
                  ├─ PodClient.Pods.Get
                  ├─ signal.Notify(os.Interrupt)
                  ├─ RESTClient.Post().SubResource("portforward")
                  └─ PortForwarder.ForwardPorts
                         └─ createDialer (portforward.go:139)
                                ├─ spdy.NewDialer
                                └─ portforward.NewSPDYOverWebsocketDialer
```

### データフロー図

```
[入力]                        [処理]                              [出力]

TYPE/NAME + ポート ──▶ Complete ──▶ Pod解決 + ポート解決
                                        │
                                        ▼
                                 RunPortForwardContext
                                        │
                        ┌───── SPDY/WebSocket ─────┐
                        │                           │
                ローカルポート ◀── トンネル ──▶ Podポート
                        │                           │
                  [localhost]              [kubelet → コンテナ]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| portforward.go | `staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go` | ソース | コマンド定義・フォワーディング処理 |
| util.go | `staging/src/k8s.io/kubectl/pkg/util/util.go` | ソース | ポート解決ユーティリティ |
