# 機能設計書 11-クラウドプロビジョニング

## 概要

本ドキュメントは、Jenkinsにおけるクラウドプロビジョニング機能の設計について記述する。この機能は、ビルド負荷に応じて動的にエージェントノードを起動・停止する自動スケーリング機能を提供する。

### 本機能の処理概要

クラウドプロビジョニング機能は、AWS、Azure、Kubernetes等のクラウドプラットフォームと連携し、ビルドキューの状態に応じて自動的にエージェントノードを追加・削除する機能である。

**業務上の目的・背景**：CI/CDパイプラインにおいて、ビルド需要は時間帯やプロジェクトの状況によって大きく変動する。固定数のエージェントでは、ピーク時にはビルド待ち時間が発生し、閑散時にはリソースが無駄になる。クラウドプロビジョニングにより、必要な時に必要な数だけエージェントを起動することで、コスト効率とビルド速度の両立を実現する。

**機能の利用シーン**：
- 大規模なプルリクエストマージ後に複数のビルドが同時実行される場合
- 定期的なリリースビルドでエージェント需要が一時的に増加する場合
- 開発チームの作業時間外にリソースを自動的に縮小する場合

**主要な処理内容**：
1. LoadStatisticsによるビルドキュー長と利用可能エグゼキューター数の監視
2. NodeProvisionerによる負荷傾向分析と追加ノード数の算出
3. Cloudプロバイダへのノードプロビジョニング要求の発行
4. PlannedNodeによる非同期ノード起動の管理
5. CloudSlaveRetentionStrategyによるアイドルノードの自動削除

**関連システム・外部連携**：AWS EC2、Azure VM、Google Compute Engine、Kubernetes等のクラウドプラットフォームとAPI連携。各クラウドプロバイダ用のプラグインが具体的な連携処理を実装する。

**権限による制御**：Cloud.PROVISION権限を持つユーザーのみがクラウド設定の変更およびノードのプロビジョニング操作を実行可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | クラウド一覧 | 主画面 | クラウドエージェント設定の一覧表示 |
| 27 | クラウド詳細 | 主画面 | クラウドエージェントの詳細設定 |
| 21 | ノード一覧 | 結果表示画面 | プロビジョニングされたノードの確認 |

## 機能種別

データ連携 / 計算処理 / CRUD操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | String | Yes | クラウド設定の一意識別子 | 空文字不可、重複不可 |
| label | Label | No | プロビジョニング対象のラベル | 有効なラベル式 |
| excessWorkload | int | Yes | 必要な追加エグゼキューター数 | 1以上 |

### 入力データソース

- LoadStatistics：キュー長、利用可能エグゼキューター数等の統計情報
- Jenkins.clouds：登録済みクラウド設定のリスト
- Label.nodeProvisioner：ラベル別のNodeProvisioner

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| PlannedNode | Collection | 非同期プロビジョニング中のノード情報 |
| Node | Node | プロビジョニング完了後のノードオブジェクト |

### 出力先

- Jenkins.nodes：プロビジョニングされたノードの追加先
- NodeProvisioner.pendingLaunches：プロビジョニング中ノードの管理リスト

## 処理フロー

### 処理シーケンス

```
1. NodeProvisionerInvoker.doRun()による定期実行開始
   └─ 10秒間隔でNodeProvisioner.update()を呼び出し
2. LoadStatisticsSnapshotの計算
   └─ キュー長、利用可能・接続中・オンラインエグゼキューター数を集計
3. 過剰負荷の判定（StandardStrategyImpl.apply()）
   └─ 利用可能エグゼキューター < MARGIN かつ キュー長 > 計画キャパシティ
4. 各Cloudに対するプロビジョニング要求
   └─ Cloud.canProvision()で対応可能か確認後、Cloud.provision()を呼び出し
5. PlannedNodeの記録と追跡
   └─ Future<Node>が完了するまで pendingLaunches で管理
6. ノードの追加完了処理
   └─ Jenkins.addNode()でノードを登録、各リスナーに通知
```

### フローチャート

```mermaid
flowchart TD
    A[NodeProvisionerInvoker.doRun] --> B[NodeProvisioner.update]
    B --> C{キュー長 > 利用可能エグゼキューター?}
    C -->|No| D[処理終了]
    C -->|Yes| E[StrategyState作成]
    E --> F[Strategy.apply実行]
    F --> G{過剰負荷あり?}
    G -->|No| D
    G -->|Yes| H[各Cloudをループ]
    H --> I{canProvision?}
    I -->|No| J[次のCloud]
    J --> H
    I -->|Yes| K[Cloud.provision呼び出し]
    K --> L[PlannedNode記録]
    L --> M{過剰負荷解消?}
    M -->|No| H
    M -->|Yes| N[PROVISIONING_COMPLETED]
    N --> D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-11-01 | マージン閾値 | 利用可能エグゼキューターがMARGIN（デフォルト10%）未満の場合にプロビジョニング検討開始 | 常時 |
| BR-11-02 | 指数移動平均 | 一時的な負荷変動を吸収するためEMA値とスナップショット値の保守的な見積もりを使用 | 負荷判定時 |
| BR-11-03 | 初期遅延 | 静的接続エージェントのオンライン化を待つため、起動後10秒間はプロビジョニング抑制 | Jenkins起動直後 |
| BR-11-04 | ゼロキャパシティ対応 | エグゼキューターが0でキューに項目がある場合は即座にプロビジョニング | 特殊ケース |

### 計算ロジック

過剰負荷の算出：
```
excessWorkload = queueLength - plannedCapacity - connectingCapacity
thresholdMargin = MARGIN + (MARGIN0 - MARGIN) * MARGIN_DECAY^totalExecutors
```
- MARGIN: 0.1（10%）
- MARGIN0: 0.5（50%）
- MARGIN_DECAY: 0.5

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

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

本機能はデータベースではなく、XMLファイルベースの設定永続化を使用。

| 操作 | 対象ファイル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| クラウド設定保存 | config.xml | UPDATE | Cloudオブジェクトの永続化 |
| ノード追加 | nodes/*/config.xml | INSERT | プロビジョニングされたノードの設定保存 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ExecutionException | ノードプロビジョニング失敗 | ログ出力後、PlannedNodeを破棄 |
| - | AbortException | 明示的なプロビジョニング中止 | 警告なしで処理続行 |
| - | IOException | ノード追加失敗 | onRollbackリスナー呼び出し、ログ出力 |

### リトライ仕様

リトライは各Cloudプラグインの実装に委ねられる。コア機能としてのリトライ機構は存在しない。

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

- provisioningLock（ReentrantLock）により、同一NodeProvisionerの並行実行を防止
- AtomicReference<List<PlannedNode>>によりpendingLaunchesの原子的更新を保証
- BulkChangeによる設定変更時の一括保存

## パフォーマンス要件

- NodeProvisioner.update()は10秒間隔で定期実行
- suggestReviewNow()による即座のレビューは1秒のスロットリング
- TimeScale.SEC10（10秒）の時系列統計を使用

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

- Cloud.PROVISION権限による操作制限
- Jenkins.ADMINISTER権限によるクラウド設定の変更制限
- ACLによるCloud単位のアクセス制御
- クラウドAPI認証情報はSecret型で暗号化保存

## 備考

- 具体的なクラウドプロバイダとの連携は各プラグイン（amazon-ec2、azure-vm-agents、kubernetes等）が実装
- CloudProvisioningListenerによる拡張ポイントでプロビジョニングイベントの監視・介入が可能

---

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

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

### 推奨読解順序

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

クラウドプロビジョニングの中核となるデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Cloud.java | `core/src/main/java/hudson/slaves/Cloud.java` | クラウド抽象基底クラス、CloudState内部クラス |
| 1-2 | PlannedNode | `core/src/main/java/hudson/slaves/NodeProvisioner.java` 内部クラス | 非同期プロビジョニング管理用オブジェクト |

**読解のコツ**: CloudはDescribable<Cloud>を実装しており、各クラウドプロバイダがサブクラスとして実装される。CloudStateはprovision()メソッドに渡されるパラメータオブジェクト。

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

プロビジョニングの起点となる定期実行処理を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | NodeProvisionerInvoker | `core/src/main/java/hudson/slaves/NodeProvisioner.java` | PeriodicWorkを継承した定期実行クラス |

**主要処理フロー**:
1. **799-827行目**: NodeProvisionerInvokerクラス、INITIALDELAY=10秒後に開始
2. **821-826行目**: doRun()でunlabeledNodeProvisionerと各Label.nodeProvisionerを更新

#### Step 3: 負荷分析と判定ロジックを理解する

NodeProvisioner.update()メソッドの処理フローを追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NodeProvisioner.update() | `core/src/main/java/hudson/slaves/NodeProvisioner.java` | メイン更新処理（214-340行目） |
| 3-2 | StandardStrategyImpl.apply() | `core/src/main/java/hudson/slaves/NodeProvisioner.java` | 標準プロビジョニング戦略（625-748行目） |

**主要処理フロー**:
- **216-217行目**: provisioningLockを取得して排他制御
- **226-301行目**: 完了したPlannedNodeの処理（Jenkins.addNode呼び出し）
- **306-318行目**: LoadStatisticsSnapshotの計算とStrategyState作成
- **321-333行目**: Strategyの適用ループ

#### Step 4: クラウドへのプロビジョニング要求を理解する

Cloud.provision()の呼び出しとPlannedNodeの管理を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Cloud.provision() | `core/src/main/java/hudson/slaves/Cloud.java` | プロビジョニング抽象メソッド（200-229行目） |
| 4-2 | Cloud.canProvision() | `core/src/main/java/hudson/slaves/Cloud.java` | プロビジョニング可否判定（235-249行目） |

**主要処理フロー**:
- **157-188行目**: provision(Label, int)（旧API、後方互換性用）
- **200-229行目**: provision(CloudState, int)（新API）
- **279-281行目**: PROVISION権限の定義

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

```
NodeProvisionerInvoker.doRun() [定期実行]
    │
    ├─ Jenkins.unlabeledNodeProvisioner.update()
    │      │
    │      ├─ LoadStatistics.computeSnapshot()
    │      │
    │      ├─ Strategy.apply(StrategyState)
    │      │      │
    │      │      └─ StandardStrategyImpl.apply()
    │      │             │
    │      │             ├─ Cloud.canProvision(CloudState)
    │      │             │
    │      │             └─ Cloud.provision(CloudState, workload)
    │      │                    │
    │      │                    └─ [各プラグイン実装]
    │      │
    │      └─ StrategyState.recordPendingLaunches()
    │
    └─ Label.nodeProvisioner.update() [各ラベルに対して]
```

### データフロー図

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

LoadStatistics ────────▶ NodeProvisioner.update() ─────▶ PlannedNode
  (キュー長、                     │                      (Future<Node>)
   エグゼキューター数)              │
                                 ▼
Jenkins.clouds ────────▶ StandardStrategyImpl.apply()
  (登録済みCloud)                  │
                                 ▼
                         Cloud.provision() ────────────▶ Node
                                 │                      (プロビジョニング完了)
                                 ▼
                         Jenkins.addNode() ────────────▶ nodes/*/config.xml
                                                        (設定永続化)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Cloud.java | `core/src/main/java/hudson/slaves/Cloud.java` | ソース | クラウド抽象基底クラス |
| NodeProvisioner.java | `core/src/main/java/hudson/slaves/NodeProvisioner.java` | ソース | プロビジョニング制御クラス |
| CloudSlaveRetentionStrategy.java | `core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java` | ソース | アイドルノード削除戦略 |
| CloudProvisioningListener.java | `core/src/main/java/hudson/slaves/CloudProvisioningListener.java` | ソース | プロビジョニングイベントリスナー |
| LoadStatistics.java | `core/src/main/java/hudson/model/LoadStatistics.java` | ソース | 負荷統計計算 |
| AbstractCloudImpl.java | `core/src/main/java/hudson/slaves/AbstractCloudImpl.java` | ソース | Cloud実装ヘルパー |
