# 機能設計書 131-iSCSIボリューム

## 概要

本ドキュメントは、Kubernetes における iSCSI ボリュームプラグインの機能設計を記述する。iSCSI (Internet Small Computer Systems Interface) プロトコルを介したブロックストレージデバイスを Pod にマウントする機能について、アーキテクチャ、処理フロー、およびインターフェース仕様を定義する。

### 本機能の処理概要

**業務上の目的・背景**：エンタープライズ環境では iSCSI ストレージが広く利用されており、Kubernetes 上のワークロードからこれらの既存ストレージインフラを活用する必要がある。iSCSI ボリュームプラグインは、iSCSI ターゲットに対するセッション管理、ディスクの発見・接続・切断を自動化し、Pod のライフサイクルに合わせたストレージ管理を実現する。

**機能の利用シーン**：データベースやファイルサーバなど、永続的なブロックストレージを必要とするステートフルアプリケーションを Kubernetes 上で運用する際に使用される。PersistentVolume (PV) として iSCSI ターゲットを定義し、PersistentVolumeClaim (PVC) を通じて Pod にマウントする。

**主要な処理内容**：
1. iSCSI ターゲットの発見（Discovery）とログイン（Session確立）
2. CHAP認証によるセキュアなセッション管理
3. マルチパス（multipath）対応によるデバイスの冗長接続
4. ファイルシステムモードおよびブロックデバイスモードでのマウント
5. セッションのログアウトおよびデバイスの切断・クリーンアップ
6. SELinux マウントコンテキストのサポート

**関連システム・外部連携**：iSCSI ターゲット（ストレージアレイ等）、iscsiadm コマンド、Linux SCSI サブシステム、multipath-tools、Kubernetes Secret（CHAP認証情報）

**権限による制御**：CHAP認証情報は Kubernetes Secret として管理され、SecretRef を通じて参照する。PV/PVC の RBAC に基づきアクセス制御が行われる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はCLIやUI画面からの直接操作ではなく、PV/PVC定義を通じてKubeletが自動的に実行する |

## 機能種別

ボリュームライフサイクル管理（Attach/Mount/Unmount/Detach）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| targetPortal | string | Yes | iSCSI ターゲットのアドレス（例: 192.168.1.100:3260） | ホスト:ポート形式。ポート省略時は3260 |
| iqn | string | Yes | iSCSI Qualified Name | iqn. または eui. プレフィックス |
| lun | int32 | Yes | Logical Unit Number | 0以上の整数 |
| portals | []string | No | 追加のターゲットポータル（マルチパス用） | 各要素はホスト:ポート形式 |
| iscsiInterface | string | No | iSCSI インターフェース名 | デフォルトは "default" |
| fsType | string | No | ファイルシステムタイプ | ext4, xfs 等 |
| readOnly | bool | No | 読み取り専用フラグ | true/false |
| chapAuthDiscovery | bool | No | Discovery時のCHAP認証 | true/false |
| chapAuthSession | bool | No | Session時のCHAP認証 | true/false |
| secretRef | SecretReference | No | CHAP認証用Secret参照 | 有効なSecret名 |
| initiatorName | *string | No | iSCSI イニシエータ名 | IQN形式 |

### 入力データソース

- PersistentVolume の `.spec.iscsi` フィールド
- Volume の `.iscsi` フィールド
- Kubernetes Secret（CHAP認証情報）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| devicePath | string | 接続されたデバイスのパス（例: /dev/sda, /dev/dm-0） |
| mountPath | string | Pod 内のマウントポイントパス |
| globalPDPath | string | Kubelet プラグインディレクトリ内のグローバルパス |

### 出力先

- Pod のボリュームマウントパス（ファイルシステムモード）
- Pod のブロックデバイスパス（ブロックモード）
- Kubelet プラグインディレクトリ（iscsi.json 設定ファイル）

## 処理フロー

### 処理シーケンス

```
1. AttachDisk（ディスク接続）
   └─ iscsiadm で iSCSI インターフェース情報を取得し、トランスポート名を抽出
2. イニシエータ名によるインターフェースクローン
   └─ カスタムイニシエータ名が指定されている場合、既存インターフェースをクローンして設定
3. ターゲットロック取得
   └─ 同一ターゲットへの並行ログイン/ログアウトを防止
4. ポータルごとのログインループ
   └─ 各ポータルに対して discoverydb 作成 → CHAP設定 → discover → node設定 → login
5. SCSI バス再スキャン
   └─ ログイン後に対象 LUN のみをスキャン（scanOneLun）
6. デバイスパス検出
   └─ /dev/disk/by-path/ からデバイスパスを検出し、存在を確認
7. マルチパスデバイス検出
   └─ 複数パスが利用可能な場合、dm-XX デバイスを検出
8. 設定永続化
   └─ iscsi.json にディスク設定を保存
9. MountDevice（デバイスマウント）
   └─ FormatAndMount でファイルシステムをフォーマット・マウント
10. SetUp（Podへのバインドマウント）
    └─ グローバルマウントパスから Pod ディレクトリへバインドマウント
```

### フローチャート

```mermaid
flowchart TD
    A[AttachDisk開始] --> B[iSCSI インターフェース情報取得]
    B --> C{カスタムイニシエータ名?}
    C -->|Yes| D[インターフェースクローン]
    C -->|No| E[ターゲットロック取得]
    D --> E
    E --> F[ポータルごとのログインループ]
    F --> G{ログイン済み?}
    G -->|No| H[discoverydb作成/CHAP設定/discover/login]
    G -->|Yes| I[SCSI LUNスキャン]
    H --> I
    I --> J[デバイスパス検出・待機]
    J --> K{全ポータル処理完了?}
    K -->|No| F
    K -->|Yes| L{マルチパス?}
    L -->|Yes| M[マルチパスデバイス検出]
    L -->|No| N[最初のデバイスパス使用]
    M --> O[iscsi.json保存]
    N --> O
    O --> P[AttachDisk完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-131-01 | マルチパス最小パス数 | マルチパスボリュームは最低2パス以上が必要 | portals が複数指定されている場合 |
| BR-131-02 | 最大接続試行回数 | 最大5回のパス接続試行後、単一パスでもマウントを許可 | マルチパスデバイス接続時 |
| BR-131-03 | デバイス検出タイムアウト | デバイスパスの出現を最大30秒待機 | SCSI LUN スキャン後 |
| BR-131-04 | セッション共有判定 | 同一ターゲットに対する他ボリュームのセッションが存在する場合、ログアウトをスキップ | DetachDisk 時 |
| BR-131-05 | ポートデフォルト | ポータルにポート番号がない場合、3260をデフォルトとして付与 | ポータルアドレス処理時 |

### 計算ロジック

- マルチパスデバイスタイムアウト: ポータル数 > 1 の場合は10秒、1ポータルの場合は1回の試行
- デバイスパス構築: TCP トランスポートの場合 `/dev/disk/by-path/ip-{addr}-iscsi-{iqn}-lun-{lun}`、その他は `/dev/disk/by-path/pci-*-ip-{addr}-iscsi-{iqn}-lun-{lun}`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| iscsi.json保存 | ファイル | WRITE | iSCSI ディスク設定を JSON ファイルとして保存 |
| iscsi.json読込 | ファイル | READ | DetachDisk 時に設定を読み込み |

### テーブル別操作詳細

#### iscsi.json

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| WRITE | Portals, Iqn, Lun, Iface, InitIface, chapDiscovery, chapSession, InitiatorName, VolName | iscsiDisk 構造体の全フィールド | JSON エンコード |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | iscsiadm エラー | iscsiadm コマンドの実行失敗 | 該当ポータルをスキップし次のポータルを試行 |
| exit code 15 | セッション既存 | 同一 iface で既にセッションが存在 | 正常として続行 |
| exit code 2 | セッション未検出 | ログアウト時にセッションが存在しない | 無視して続行 |
| exit code 21 | オブジェクト未検出 | 対象レコードが存在しない | 無視して続行 |
| - | デバイス検出タイムアウト | 30秒以内にデバイスパスが出現しない | エラーを記録し次のポータルを試行 |
| - | パス全滅 | 全てのポータルへの接続に失敗 | cloned iface を削除しエラーを返却 |

### リトライ仕様

- パス接続: 最大5回の試行（maxAttachAttempts）。最低2回試行後に2パス以上あれば続行（minAttachAttempts, minMultipathCount）
- デバイスパス検出: 最大30秒間、1秒間隔でリトライ
- マルチパスデバイス検出: マルチポータルの場合は最大10秒間リトライ

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

iSCSI ボリューム操作は OS レベルの処理であり、データベーストランザクションは存在しない。ただし、ターゲット単位のロック（keymutex）により同一ターゲットへの並行操作を排他制御する。iscsi.json の永続化に失敗した場合は UncertainProgressError を返し、kubelet が Unmount/Unmap を呼び出すようにする。

## パフォーマンス要件

- iSCSI ログイン: ネットワーク遅延に依存。タイムアウトは iscsiadm のデフォルトに従う
- デバイス検出: 最大30秒
- マルチパスデバイス検出: 最大10秒
- iscsiadm コマンド実行時間はログレベル V(5) で記録される

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

- CHAP認証情報は Kubernetes Secret として管理し、Pod の Namespace または SecretRef の Namespace から取得する
- CHAP シークレット値はログに記録されない（execWithLog を使用しない明示的な処理）
- iSCSI セッションはノード再起動時に自動ログインしないよう `node.startup=manual` に設定
- `node.session.scan=manual` に設定し、意図しない LUN スキャンを防止（CVE対応: issue #90982）
- SELinux マウントコンテキストを `SELinuxMountReadWriteOncePod` フィーチャーゲートでサポート

## 備考

- iSCSI ボリュームプラグインは Kubernetes のインツリープラグインであり、CSI マイグレーションの対象外
- ブロックボリュームモード（Raw Block）およびファイルシステムモードの両方をサポート
- プラグイン名: `kubernetes.io/iscsi`
- サポートするアクセスモード: ReadWriteOnce, ReadOnlyMany

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | iscsi.go | `pkg/volume/iscsi/iscsi.go` | iscsiDisk 構造体（314-330行目）が中心的なデータモデル。Portals, Iqn, Lun, Iface 等のフィールドを把握する |
| 1-2 | iscsi.go | `pkg/volume/iscsi/iscsi.go` | iscsiDiskMounter（352-362行目）, iscsiDiskUnmounter（393-398行目）の構造を理解する |

**読解のコツ**: Go の構造体埋め込み（embedding）パターンが多用されている。`*iscsiDisk` がベース構造体として各 Mounter/Unmounter/Mapper に埋め込まれている。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | iscsi.go | `pkg/volume/iscsi/iscsi.go` | ProbeVolumePlugins()（45-47行目）がプラグイン登録のエントリーポイント |
| 2-2 | iscsi.go | `pkg/volume/iscsi/iscsi.go` | iscsiPlugin の Init()（62-66行目）でホストと targetLocks を初期化 |
| 2-3 | iscsi.go | `pkg/volume/iscsi/iscsi.go` | NewMounter()（104-113行目）がマウント処理の起点 |

**主要処理フロー**:
1. **45-47行目**: ProbeVolumePlugins でプラグインインスタンスを返却
2. **62-66行目**: Init でホスト参照と keymutex を初期化
3. **104-113行目**: NewMounter で Secret 取得後、newMounterInternal へ委譲
4. **115-139行目**: newMounterInternal でディスク情報を抽出し iscsiDiskMounter を生成

#### Step 3: Attach/Detach 処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | attacher.go | `pkg/volume/iscsi/attacher.go` | WaitForAttach()（83-90行目）が Attach 処理の実質的な起点 |
| 3-2 | attacher.go | `pkg/volume/iscsi/attacher.go` | MountDevice()（106-141行目）でデバイスをフォーマット＆マウント |
| 3-3 | attacher.go | `pkg/volume/iscsi/attacher.go` | UnmountDevice()（171-184行目）でデバイスをアンマウント＆ディレクトリ削除 |

**主要処理フロー**:
- **70-72行目**: Attach() は no-op（空文字を返す）。実際の接続は WaitForAttach で実行
- **83-90行目**: WaitForAttach() で volumeSpec から mounter を生成し、manager.AttachDisk を呼び出す
- **106-141行目**: MountDevice() で FormatAndMount を実行（SELinux ラベル対応含む）

#### Step 4: iSCSI ユーティリティ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | iscsi_util.go | `pkg/volume/iscsi/iscsi_util.go` | AttachDisk()（289-483行目）が最も重要な処理。iscsiadm コマンドによるセッション管理の全体を実装 |
| 4-2 | iscsi_util.go | `pkg/volume/iscsi/iscsi_util.go` | DetachDisk()（583-656行目）でアンマウント・ログアウト・クリーンアップ |
| 4-3 | iscsi_util.go | `pkg/volume/iscsi/iscsi_util.go` | cloneIface()（858-904行目）でカスタムイニシエータ名のインターフェースクローン |

**主要処理フロー**:
- **289-483行目**: AttachDisk - ポータルループ、discoverydb 管理、CHAP設定、ログイン、LUNスキャン、マルチパス検出
- **487-518行目**: persistISCSI - iscsi.json への設定保存
- **583-656行目**: DetachDisk - アンマウント、デバイス削除、セッションビジー判定、ログアウト
- **726-763行目**: detachISCSIDisk - ポータルごとのログアウト・ノードレコード削除・iface削除

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

```
ProbeVolumePlugins() → iscsiPlugin
    │
    ├─ NewAttacher() → iscsiAttacher
    │      ├─ WaitForAttach() → ISCSIUtil.AttachDisk()
    │      │      ├─ cloneIface()
    │      │      ├─ updateISCSIDiscoverydb()
    │      │      ├─ updateISCSINode()
    │      │      ├─ scanOneLun()
    │      │      ├─ waitForPathToExist()
    │      │      ├─ waitForMultiPathToExist()
    │      │      └─ persistISCSI()
    │      └─ MountDevice() → FormatAndMount()
    │
    ├─ NewMounter() → iscsiDiskMounter
    │      └─ SetUpAt() → diskSetUp()
    │
    ├─ NewDetacher() → iscsiDetacher
    │      └─ UnmountDevice() → ISCSIUtil.DetachDisk()
    │             ├─ loadISCSI()
    │             ├─ deleteDevices()
    │             ├─ isSessionBusy()
    │             └─ detachISCSIDisk()
    │
    └─ NewUnmounter() → iscsiDiskUnmounter
           └─ TearDownAt() → CleanupMountPoint()
```

### データフロー図

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

PV/PVC Spec          ──▶ iscsiPlugin.NewMounter()     ──▶ iscsiDiskMounter
  (targetPortal,          createISCSIDisk()
   iqn, lun, ...)         createSecretMap()

iscsiadm commands    ──▶ ISCSIUtil.AttachDisk()       ──▶ devicePath (/dev/sdX or /dev/dm-X)
                          discoverydb/login/scan

devicePath           ──▶ MountDevice()                ──▶ globalMountPath (formatted & mounted)
                          FormatAndMount()

globalMountPath      ──▶ SetUpAt()                    ──▶ podVolumePath (bind mount)
                          diskSetUp()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| iscsi.go | `pkg/volume/iscsi/iscsi.go` | ソース | プラグイン本体。データ構造定義、Mounter/Unmounter/Mapper 生成 |
| iscsi_util.go | `pkg/volume/iscsi/iscsi_util.go` | ソース | iSCSI ユーティリティ。AttachDisk/DetachDisk の実装 |
| attacher.go | `pkg/volume/iscsi/attacher.go` | ソース | Attacher/Detacher/DeviceMounter 実装 |
| disk_manager.go | `pkg/volume/iscsi/disk_manager.go` | ソース | diskManager インターフェース定義とディスクセットアップ |
| doc.go | `pkg/volume/iscsi/doc.go` | ソース | パッケージドキュメント |
| iscsi_test.go | `pkg/volume/iscsi/iscsi_test.go` | テスト | プラグインのユニットテスト |
| iscsi_util_test.go | `pkg/volume/iscsi/iscsi_util_test.go` | テスト | ユーティリティのユニットテスト |
