# 機能設計書 40-NodeAffinityプラグイン

## 概要

本ドキュメントは、Kubernetesスケジューラーフレームワークに含まれるNodeAffinityプラグインの機能設計を記述する。PodのNodeAffinityおよびNodeSelectorに基づくノードフィルタリングとスコアリングを実行する。

### 本機能の処理概要

**業務上の目的・背景**：NodeAffinityはKubernetesにおけるPodスケジューリングの基本機能であり、Podが特定のノードラベルを持つノードにスケジュールされるよう制御する。`requiredDuringSchedulingIgnoredDuringExecution`（必須条件）によるフィルタリングと、`preferredDuringSchedulingIgnoredDuringExecution`（優先条件）によるスコアリングの2つの機能を提供する。

**機能の利用シーン**：
- 特定のハードウェア（GPU、SSDなど）を持つノードへのスケジューリング
- リージョン/ゾーンベースのスケジューリング
- スケジューラープロファイルのAddedAffinityによるクラスタレベルのノード制約追加

**主要な処理内容**：
1. **PreFilter**: Pod.Spec.Affinityからrequired条件を抽出しCycleStateに保存。MatchFieldsでnode.Name指定がある場合はPreFilterResultでノード候補を絞り込む
2. **Filter**: ノードラベルがPodのrequired条件およびスケジューラーのAddedAffinityに合致するか判定
3. **PreScore**: Pod.Spec.Affinityからpreferred条件を抽出しCycleStateに保存
4. **Score**: ノードラベルに対するpreferred条件の重み付きスコアを計算
5. **NormalizeScore**: スコアを0-100に正規化
6. **EventsToRegister**: Node Add/UpdateNodeLabel/UpdateNodeTaintイベントの登録
7. **isSchedulableAfterNodeChange**: ノード変更時の再キューイング判定（QueueingHint）

**関連システム・外部連携**：スケジューラーフレームワーク、ノードラベル、KubeSchedulerProfile

**権限による制御**：Nodeに対するGet、List、Watch権限（スケジューラー全体として）。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | Pod.spec.affinity.nodeAffinity | 設定 | PodマニフェストでのNodeAffinity指定 |
| - | KubeSchedulerProfile.pluginConfig | 設定 | AddedAffinityの設定 |

## 機能種別

フィルタリング / スコアリング / QueueingHint

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| Pod.Spec.NodeSelector | map[string]string | No | 旧来のノードセレクタ | - |
| Pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution | *v1.NodeSelector | No | 必須ノード条件 | 有効なNodeSelectorTerms |
| Pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution | []v1.PreferredSchedulingTerm | No | 優先ノード条件 | Weight 1-100 |
| NodeAffinityArgs.AddedAffinity | *v1.NodeAffinity | No | スケジューラーレベルの追加Affinity | プロファイル設定 |

### プラグイン引数

| パラメータ名 | 型 | 説明 |
|-------------|-----|------|
| AddedAffinity.RequiredDuringSchedulingIgnoredDuringExecution | *v1.NodeSelector | スケジューラーが追加する必須条件 |
| AddedAffinity.PreferredDuringSchedulingIgnoredDuringExecution | []v1.PreferredSchedulingTerm | スケジューラーが追加する優先条件 |

## 出力仕様

### PreFilter出力

| 項目名 | 型 | 説明 |
|--------|-----|------|
| PreFilterResult.NodeNames | sets.Set[string] | node.Name指定によるノード候補（nil=全ノード対象） |
| preFilterState | CycleState | requiredNodeSelectorAndAffinityの計算済みオブジェクト |

### Filter出力

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Status | *fwk.Status | nil=通過、UnschedulableAndUnresolvable=除外 |

### Score出力

| 項目名 | 型 | 説明 |
|--------|-----|------|
| score | int64 | preferred条件の重み付きスコア合計 |

## 処理フロー

### PreFilter処理

```
1. Pod.Spec.Affinityを確認
   └─ NodeAffinity=nil AND AddedNodeSelector=nil AND NodeSelector=nil
      → Status(Skip)を返す（Filter/Scoreスキップ）
2. preFilterStateを構築しCycleStateに保存
3. RequiredのNodeSelectorTermsからnode.Name(MatchFields)を抽出
   └─ 各TermのMatchFieldsで metadata.name IN [values] を検出
   └─ Term間はOR、MatchFields間はAND
   └─ node.Name指定がないTermがある場合 → 全ノード対象（return nil）
   └─ 全TermがMatchFieldsを持つ場合 → NodeNames Union を返す
   └─ NodeNamesが空（矛盾）→ UnschedulableAndUnresolvable
```

### Filter処理

```
1. AddedNodeSelectorチェック
   └─ 不一致 → UnschedulableAndUnresolvable（スケジューラー強制条件）
2. preFilterState（またはフォールバック計算）からrequiredNodeSelectorAndAffinityを取得
3. Match判定
   └─ 不一致 → UnschedulableAndUnresolvable（Pod条件不一致）
```

### Score処理

```
1. AddedPrefSchedTermsのスコアを計算
2. preScoreState（またはフォールバック計算）からpreferredNodeAffinityを取得
3. preferredNodeAffinityのスコアを計算
4. 合計スコアを返す
5. NormalizeScoreで0-100に正規化
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-40-01 | Required条件 | requiredDuringSchedulingIgnoredDuringExecution条件をすべて満たすノードのみ通過 | Filter時 |
| BR-40-02 | Preferred条件 | preferredDuringSchedulingIgnoredDuringExecution条件の加重スコアでノードを優先 | Score時 |
| BR-40-03 | AddedAffinity | スケジューラープロファイルで追加されたAffinityも評価に含める | Filter/Score時 |
| BR-40-04 | NodeSelector互換 | Pod.Spec.NodeSelectorも必須条件として評価する | Filter時 |
| BR-40-05 | node.Name最適化 | MatchFieldsでnode.Nameが指定されている場合、PreFilterで候補ノードを絞り込む | PreFilter時 |
| BR-40-06 | Skip最適化 | NodeAffinity/NodeSelector/AddedAffinityがすべてない場合はSkipを返す | PreFilter/PreScore時 |
| BR-40-07 | QueueingHint | ノードラベル変更時のみ再スケジューリングを試行（Taint変更は対象外、QH有効時） | EventsToRegister |
| BR-40-08 | 矛盾検出 | NodeSelectorTermsが全て矛盾する場合（node.Name候補が空）はUnschedulableAndUnresolvable | PreFilter時 |

### 計算ロジック

- Requiredマッチ: `nodeaffinity.GetRequiredNodeAffinity(pod).Match(node)` - NodeSelectorとNodeAffinityの両方を評価
- Preferredスコア: `preferredSchedulingTerms.Score(node)` - Weight * マッチ数の合計
- NormalizeScore: `helper.DefaultNormalizeScore(MaxNodeScore, false, scores)` - 最大値基準で0-100正規化

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

プラグイン自体はデータベース操作を行わない。フィルタリングとスコアリングの結果を呼び出し元に返す。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| UnschedulableAndUnresolvable | 必須条件不一致 | ノードラベルがrequired条件に合致しない | 別ノードで試行 |
| UnschedulableAndUnresolvable | AddedAffinity不一致 | スケジューラー追加条件に合致しない | 別ノードで試行 |
| UnschedulableAndUnresolvable | 矛盾検出 | node.Name条件が矛盾（空候補） | スケジューリング不可 |
| Error | 引数エラー | NodeAffinityArgsの型不一致 | プラグイン初期化失敗 |

## パフォーマンス要件

- PreFilterでのnode.Name絞り込みにより、不要なフィルタリングを回避
- Skipステータスにより、NodeAffinity未使用Podのオーバーヘッドをゼロに
- CycleStateキャッシュにより、Filter/Score間での再計算を回避
- QueueingHintでノードラベル変更時のみ再スケジューリング試行

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

- AddedAffinityはKubeSchedulerConfigurationで設定され、一般ユーザーからは変更不可
- ノードラベルへのアクセスは読み取りのみ

## 備考

- NodeAffinityプラグインはPreFilterPlugin、FilterPlugin、PreScorePlugin、ScorePlugin、EnqueueExtensions、SignPluginの6インターフェースを実装
- QueueingHint有効時はUpdateNodeLabelイベントのみで再キューイング、無効時はUpdateNodeTaintも登録
- SignPluginインターフェースによりPodのNodeAffinity情報のダイジェストを提供

---

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

### 推奨読解順序

#### Step 1: プラグイン構造と初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | NodeAffinity構造体（39-44行目）、実装インターフェース一覧（46-51行目） |

**読解のコツ**: `var _ fwk.PreFilterPlugin = &NodeAffinity{}`等のinterface assertion（46-51行目）で実装インターフェースを一目で確認できる。

#### Step 2: 初期化とAddedAffinityを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | New関数（310-335行目） |

**主要処理**:
- **311行目**: getArgs()でNodeAffinityArgsを取得
- **315-318行目**: NodeAffinity構造体初期化（handle, enableSchedulingQueueHint）
- **319-325行目**: AddedAffinity.Required → addedNodeSelector構築
- **327-332行目**: AddedAffinity.Preferred → addedPrefSchedTerms構築

#### Step 3: PreFilter/Filterを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | PreFilter関数（159-209行目）、Filter関数（218-239行目） |

**PreFilter主要処理**:
- **161-167行目**: NodeAffinity/NodeSelector/AddedNodeSelectorがすべてない場合はSkip
- **169-170行目**: preFilterState構築（requiredNodeSelectorAndAffinity）
- **177-206行目**: MatchFieldsからnode.Name候補を抽出（Term=OR、Field=AND）
- **202-203行目**: 矛盾検出（nodeNamesが空）

**Filter主要処理**:
- **221-223行目**: addedNodeSelectorチェック（スケジューラー強制条件）
- **225-229行目**: preFilterStateまたはフォールバック計算
- **233-235行目**: requiredNodeSelectorAndAffinity.Match判定

#### Step 4: PreScore/Score/NormalizeScoreを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | PreScore（253-267行目）、Score（272-297行目）、NormalizeScore（300-302行目） |

**Score主要処理**:
- **276-278行目**: addedPrefSchedTermsのスコア計算
- **292-294行目**: preferredNodeAffinityのスコア計算
- **296行目**: 合計を返す

#### Step 5: QueueingHintを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | EventsToRegister（101-115行目）、isSchedulableAfterNodeChange（119-156行目） |

**主要処理**:
- **106-110行目**: QH有効時はAdd|UpdateNodeLabel、無効時はAdd|UpdateNodeLabel|UpdateNodeTaint
- **125-128行目**: addedNodeSelectorの事前チェック
- **130-138行目**: requiredNodeAffinityの一致確認
- **140-143行目**: Node Add時は即Queue
- **146-153行目**: Node Update時はunmatch→matchの変化のみQueue

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

```
New (310行目)
    ├─ getArgs (337行目) → config.NodeAffinityArgs取得
    ├─ nodeaffinity.NewNodeSelector → addedNodeSelector構築
    └─ nodeaffinity.NewPreferredSchedulingTerms → addedPrefSchedTerms構築

PreFilter (159行目)
    ├─ nodeaffinity.GetRequiredNodeAffinity(pod) → preFilterState
    └─ MatchFields解析 → PreFilterResult{NodeNames}

Filter (218行目)
    ├─ addedNodeSelector.Match(node) → スケジューラー条件
    ├─ getPreFilterState(state) → requiredNodeSelectorAndAffinity
    └─ requiredNodeSelectorAndAffinity.Match(node) → Pod条件

PreScore (253行目)
    └─ getPodPreferredNodeAffinity(pod) → preScoreState

Score (272行目)
    ├─ addedPrefSchedTerms.Score(node)
    └─ preferredNodeAffinity.Score(node)

NormalizeScore (300行目)
    └─ helper.DefaultNormalizeScore(MaxNodeScore, false, scores)

EventsToRegister (101行目)
    └─ isSchedulableAfterNodeChange (119行目)
        ├─ addedNodeSelector.Match(modifiedNode)
        ├─ requiredNodeAffinity.Match(modifiedNode)
        └─ requiredNodeAffinity.Match(originalNode) [unmatch→match検出]
```

### データフロー図

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

Pod.Spec.Affinity ──▶ PreFilter ──▶ preFilterState (CycleState) ──▶ PreFilterResult
Pod.Spec.NodeSelector     │                                         (NodeNames)
                          │
Node Labels         ──▶ Filter  ──▶ addedNodeSelector.Match     ──▶ Status
                          │         requiredNodeSelectorAndAffinity   (Pass/Reject)
                          │         .Match
                          │
Pod Preferred Terms ──▶ PreScore ──▶ preScoreState (CycleState)
                          │
                     Score      ──▶ addedPrefSchedTerms.Score   ──▶ int64 score
                          │         preferredNodeAffinity.Score
                          │
                     NormalizeScore                              ──▶ 0-100 score
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| node_affinity.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity.go` | ソース | NodeAffinityプラグイン本体 |
| node_affinity_test.go | `pkg/scheduler/framework/plugins/nodeaffinity/node_affinity_test.go` | テスト | ユニットテスト |
| nodeaffinity | `k8s.io/component-helpers/scheduling/corev1/nodeaffinity/` | 外部ライブラリ | NodeSelector/PreferredSchedulingTermsの実装 |
| config | `pkg/scheduler/apis/config/types.go` | ソース | NodeAffinityArgs定義 |
| validation | `pkg/scheduler/apis/config/validation/` | ソース | NodeAffinityArgsバリデーション |
| helper | `pkg/scheduler/framework/plugins/helper/` | ソース | DefaultNormalizeScore等のヘルパー |
| names | `pkg/scheduler/framework/plugins/names/` | ソース | プラグイン名定数 |
| feature | `pkg/scheduler/framework/plugins/feature/` | ソース | Feature Gatesの共有構造体 |
