# 機能設計書 130-NFSボリューム

## 概要

本ドキュメントは、NFSボリュームプラグインの設計を記述する。NFSボリュームは、NFSサーバー上のエクスポートパスをPod内にマウントする機能を提供する。PersistentVolumeおよびインラインボリュームの両方をサポートする。

### 本機能の処理概要

**業務上の目的・背景**：ネットワークファイルシステム（NFS）を利用して、複数のノードおよびPod間でデータを共有するためのストレージアクセスを提供する。データの永続化が必要で、かつ複数のPodから同時にアクセスする必要がある場面で使用される。

**機能の利用シーン**：Pod SpecでNFSボリュームを定義し、NFSサーバーのアドレスとエクスポートパスを指定する。共有ファイルストレージ、コンテンツ配信、ログ集約等。

**主要な処理内容**：
1. NFSマウント（server:path形式でMountSensitiveWithoutSystemd呼び出し）
2. NFSアンマウント（CleanupMountPoint/CleanupMountWithForce）
3. PVリサイクル（リサイクルPod起動）
4. IPv6アドレスのブラケット表記対応
5. マウントオプションのサポート

**関連システム・外部連携**：NFSサーバー、mount-utils、PVリサイクラー。

**権限による制御**：NFSサーバー側のexport設定による制御。Kubernetesレベルでの追加の権限制御はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | NFSボリュームはCLI画面を持たないkubelet内部処理 |

## 機能種別

ボリュームプラグイン / ネットワークストレージ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| server | string | Yes | NFSサーバーアドレス（IP/ホスト名） | 空文字チェック、IPv6対応 |
| path | string | Yes | NFSエクスポートパス | 空文字チェック |
| readOnly | bool | No | 読み取り専用フラグ | - |

### 入力データソース

- Pod Spec（volumes[].nfs）
- PersistentVolume Spec（nfs）
- PV mountOptions

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| マウントポイント | ディレクトリ | NFSエクスポートがマウントされたディレクトリ |

### 出力先

- `<kubeletDir>/pods/<podUID>/volumes/kubernetes.io~nfs/<volumeName>/`

## 処理フロー

### 処理シーケンス（SetUpAt）

```
1. マウントポイント確認（IsNotMountPoint）
   └─ 既にマウント済みの場合は即完了（冪等性）
2. ディレクトリ作成（MkdirAll 0750）
3. マウントソース構築（server:path）
   └─ IPv6の場合: [server]:path
4. マウントオプション構築
   └─ readOnly=true時に"ro"追加
   └─ PV mountOptionsとのマージ
5. MountSensitiveWithoutSystemd("nfs")
   └─ 失敗時: IsNotMountPoint確認 → マウント済みならUnmount → ディレクトリ削除
```

### フローチャート

```mermaid
flowchart TD
    A[SetUpAt開始] --> B[IsNotMountPoint確認]
    B --> C{既にマウント済み?}
    C -->|Yes| D[完了]
    C -->|No| E[MkdirAll]
    E --> F[マウントソース構築]
    F --> G{IPv6?}
    G -->|Yes| H["[server]:path"]
    G -->|No| I["server:path"]
    H --> J[マウントオプション構築]
    I --> J
    J --> K[MountSensitiveWithoutSystemd]
    K --> L{成功?}
    L -->|Yes| M[完了]
    L -->|No| N[IsNotMountPoint再確認]
    N --> O{マウント残存?}
    O -->|Yes| P[Unmount → 再確認]
    O -->|No| Q[ディレクトリ削除]
    P --> Q
    Q --> R[エラー返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-130-1 | 冪等性 | 既にマウント済みの場合はスキップ | SetUp時 |
| BR-130-2 | IPv6対応 | IPv6アドレスはブラケットで囲む（[addr]:path） | server判定時 |
| BR-130-3 | readOnlyオプション | readOnly=true時に"ro"マウントオプションを追加 | readOnly指定時 |
| BR-130-4 | マウントオプション統合 | PVのmountOptionsとreadOnlyオプションをマージ | PV使用時 |
| BR-130-5 | Managed=false | NFSマウントはkubeletによるManaged操作を行わない | 常時 |
| BR-130-6 | SupportsMountOption=true | PVのmountOptionsをサポート | PV使用時 |
| BR-130-7 | 複数アクセスモード | ReadWriteOnce、ReadOnlyMany、ReadWriteMany全てをサポート | PV使用時 |
| BR-130-8 | Force Unmount | MounterForceUnmounterインターフェースをサポートする場合はForce Unmount | TearDown時 |
| BR-130-9 | extensiveMountPointCheck | /proc/mountsを参照してマウントポイントを確認 | TearDown時 |

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

本機能はデータベースを直接操作しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | マウントエラー | NFSサーバー不通、エクスポート不存在 | NFSサーバー設定の確認 |
| - | マウントポイント確認エラー | /proc/mounts読み取りエラー | ノード状態の確認 |
| - | アンマウントタイムアウト | force unmountが1分でタイムアウト | NFSサーバーの復旧 |

### リトライ仕様

マウント失敗時にマウント残存を検出した場合は、Unmount→再確認を行う。それでもマウント残存する場合はエラーログを出力して次のsync loopに委ねる。TearDown時はForce Unmountが利用可能な場合は1分タイムアウトで強制アンマウントを試行。

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

NFSマウントはオペレーティングシステムレベルの操作であり、アトミック性はOSが保証する。マウント失敗時のクリーンアップ（Unmount+ディレクトリ削除）が実装されている。

## パフォーマンス要件

NFS性能はネットワーク帯域、NFSサーバーの性能、NFS設定（非同期書き込み等）に依存する。マウント処理自体はNFSサーバーへの接続に要する時間に依存。

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

- NFS通信はデフォルトでは暗号化されない（NFSv4+Kerberos設定が推奨）
- NFSサーバー側のexport設定で適切なアクセス制御を行うべき
- root_squashにより、kubeletがlstat/statできない場合がある（TearDownでextensiveMountPointCheckを使用する理由）

## 備考

- NFSボリュームはCSIマイグレーション対象ではない（NFS CSIドライバーは別途存在するが自動移行ではない）
- unMountTimeoutは1分（定数: 68行目）
- GetAccessModesでReadWriteOnce、ReadOnlyMany、ReadWriteManyの3モードを返す

---

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | nfs.go | `pkg/volume/nfs/nfs.go` | nfsPlugin構造体（56-59行目）: host、config |
| 1-2 | nfs.go | `pkg/volume/nfs/nfs.go` | nfs構造体（190-196行目）: volName、pod、mounter |
| 1-3 | nfs.go | `pkg/volume/nfs/nfs.go` | nfsMounter構造体（203-209行目）: server、exportPath、readOnly、mountOptions |
| 1-4 | nfs.go | `pkg/volume/nfs/nfs.go` | インターフェース宣言（61-63行目）: VolumePlugin、PersistentVolumePlugin、RecyclableVolumePlugin |

#### Step 2: マウント処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | nfs.go | `pkg/volume/nfs/nfs.go` | newMounterInternal()（120-138行目）: nfsMounter構築（getServerFromSource使用） |
| 2-2 | nfs.go | `pkg/volume/nfs/nfs.go` | SetUpAt()（226-271行目）: メインのマウント処理 |
| 2-3 | nfs.go | `pkg/volume/nfs/nfs.go` | **227-234行目**: IsNotMountPointで既存マウント確認（冪等性） |
| 2-4 | nfs.go | `pkg/volume/nfs/nfs.go` | **238行目**: "server:path"形式のソース構築 |
| 2-5 | nfs.go | `pkg/volume/nfs/nfs.go` | **243-244行目**: マウントオプション結合とMountSensitiveWithoutSystemd呼び出し |
| 2-6 | nfs.go | `pkg/volume/nfs/nfs.go` | **245-269行目**: マウント失敗時のクリーンアップ処理 |

#### Step 3: アンマウント処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | nfs.go | `pkg/volume/nfs/nfs.go` | TearDownAt()（283-293行目）: force unmount対応のアンマウント |
| 3-2 | nfs.go | `pkg/volume/nfs/nfs.go` | **287-290行目**: MounterForceUnmounterインターフェース確認とCleanupMountWithForce |
| 3-3 | nfs.go | `pkg/volume/nfs/nfs.go` | **292行目**: 通常のCleanupMountPoint（extensiveMountPointCheck=true） |

**読解のコツ**: NFS特有の問題として、root_squashによりlstat()が失敗する場合がある。そのため、TearDownAtではextensiveMountPointCheck=trueを指定し、/proc/mountsを直接参照してマウントポイントを判定する。

#### Step 4: IPv6とユーティリティを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | nfs.go | `pkg/volume/nfs/nfs.go` | getServerFromSource()（306-311行目）: IPv6アドレスのブラケット付加 |
| 4-2 | nfs.go | `pkg/volume/nfs/nfs.go` | Recycle()（156-173行目）: リサイクルPod起動 |

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

```
kubelet volume manager
    |
    ├─ nfsPlugin.NewMounter(spec, pod)
    |      └─ newMounterInternal(spec, pod, mounter)
    |             ├─ getVolumeSource(spec)
    |             ├─ getServerFromSource(source)  [IPv6対応]
    |             └─ nfsMounter{} 構築
    |
    ├─ nfsMounter.SetUp()
    |      └─ SetUpAt(dir)
    |             ├─ mount.IsNotMountPoint()  [冪等性チェック]
    |             ├─ os.MkdirAll(dir, 0750)
    |             ├─ util.JoinMountOptions()
    |             └─ mounter.MountSensitiveWithoutSystemd("nfs")
    |                    └─ [失敗時] IsNotMountPoint → Unmount → Remove
    |
    ├─ nfsUnmounter.TearDown()
    |      └─ TearDownAt(dir)
    |             ├─ [ForceUnmount] mount.CleanupMountWithForce(dir, 1min)
    |             └─ [通常] mount.CleanupMountPoint(dir, extensiveCheck=true)
    |
    └─ nfsPlugin.Recycle()
           └─ recyclerclient.RecycleVolumeByWatchingPodUntilCompletion()
```

### データフロー図

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

NFSVolumeSource ───────▶ newMounterInternal()
  server ──────────────▶    │
  path ────────────────▶    │
  readOnly ────────────▶    │
mountOptions ──────────▶    │
                             │
nfsMounter ────────────▶ SetUpAt()
                             ├──▶ ディレクトリ作成
                             └──▶ NFSマウント (server:path → dir)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| nfs.go | `pkg/volume/nfs/nfs.go` | ソース | メイン実装（プラグイン、マウンター、アンマウンター、リサイクラー） |
| mount-utils | `k8s.io/mount-utils` | 外部依存 | マウント/アンマウントユーティリティ |
| recyclerclient/ | `pkg/volume/util/recyclerclient/` | ソース | リサイクルPod管理 |
| util/mount_option.go | `pkg/volume/util/` | ソース | マウントオプションユーティリティ |
