見出し画像

[Converged] slurm-bridgeで実現するK8s-Slurm統合スケジューリング


K8sとSlurmを統合し、K8s Podのスケジューリングを Slurm が担う「Converged パターン」を、slurm-bridge v1.0.0 を使って実際に構築した。本記事では、構築過程で発見したアーキテクチャ要件、そしてGPUワークロードでの性能評価結果を報告する。

Converged パターンとは

Converged パターンは、K8s と Slurm を統合し、同一ノード上で kubelet と slurmd を共存させるアーキテクチャだ。slurm-bridge は K8s スケジューラーエクステンションとして動作し、Pod のスケジューリングを Slurm REST API 経由で制御する。

┌─────────────────────────────────────────────────────────────────┐
│                    slurm-bridge Converged Pattern               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  K8s Control Plane                                              │
│  ┌─────────────────────────────────────────────────────┐        │
│  │  apiserver + slurm-bridge (scheduler/controller)    │        │
│  └───────────────────────┬─────────────────────────────┘        │
│                          │                                      │
│                          │ Pod Scheduling Request               │
│                          ▼                                      │
│  ┌─────────────────────────────────────────────────────┐        │
│  │              slurm-bridge-scheduler                 │        │
│  │     schedulerName: slurm-bridge-scheduler           │        │
│  └───────────────────────┬─────────────────────────────┘        │
│                          │                                      │
│                          │ REST API (v0.0.44)                   │
│                          ▼                                      │
│  Slurm Control Plane                                            │
│  ┌─────────────────────────────────────────────────────┐        │
│  │  slurmctld + slurmrestd (port 6820)                 │        │
│  └───────────────────────┬─────────────────────────────┘        │
│                          │                                      │
│                          │ Job Dispatch                         │
│                          ▼                                      │
│  GPU Worker Node (Converged)                                    │
│  ┌─────────────────────────────────────────────────────┐        │
│  │  kubelet + slurmd + NVIDIA T4 GPU                   │        │
│  │  (同一ノードで K8s/Slurm 両方のワークロード実行)       │        │
│  └─────────────────────────────────────────────────────┘        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

このアプローチにより、K8s のコンテナオーケストレーションと Slurm のHPCジョブスケジューリングを透過的に統合できる。

検証環境

インフラ構成

$$
\begin{array}{|l|l|l|}
\hline
\text{\textbf{コンポーネント}} & \text{\textbf{インスタンス}} & \text{\textbf{役割}} \\
\hline
\text{K8s Control Plane} & \text{t3.medium} & \text{apiserver + slurm-bridge} \\
\hline
\text{Slurm Head Node} & \text{t3.medium} & \text{slurmctld + slurmrestd} \\
\hline
\text{GPU Worker} & \text{g4dn.xlarge} & \text{kubelet + slurmd + GPU} \\
\hline
\end{array}
$$

ソフトウェア構成

$$
\begin{array}{|l|l|}
\hline
\text{\textbf{コンポーネント}} & \text{\textbf{バージョン}} \\
\hline
\text{slurm-bridge} & \text{v1.0.0} \\
\hline
\text{Slurm} & \text{25.11.0} \\
\hline
\text{REST API} & \text{v0.0.44} \\
\hline
\text{認証} & \text{JWT (AuthAltTypes=auth/jwt)} \\
\hline
\text{Kubernetes} & \text{kubeadm} \\
\hline
\text{containerd} & \text{1.7.28 (nvidia runtime)} \\
\hline
\text{NVIDIA Driver} & \text{535.274.02} \\
\hline
\text{CUDA} & \text{12.2} \\
\hline
\end{array}
$$

slurm-bridge のアーキテクチャ要件

最初の検証で発見した制約

slurm-bridge を単純にデプロイしただけでは動作しなかった。エラーログから以下の要件が判明した。

E1209 15:30:49.683922 1 slurmcontrol.go:214] "could not create placeholder job"
  err="[Internal Server Error, Access/permission denied]" pod="default/slurm-bridge-test-1"

根本原因: K8s ノード上で slurmd が稼働していなかった。

NodeName=ip-10-0-3-20    State=UNKNOWN+NOT_RESPONDING
NodeName=ip-10-0-3-21    State=UNKNOWN+NOT_RESPONDING

必須要件

slurm-bridge は「K8s ノード = Slurm ノード」という前提で設計されている。

$$
\begin{array}{|l|l|}
\hline
\text{\textbf{要件}} & \text{\textbf{説明}} \\
\hline
\text{slurmd のインストール} & \text{各 K8s ワーカーノードに slurmd が稼働} \\
\hline
\text{Munge 認証} & \text{K8s ノードと Slurm コントローラー間で認証設定} \\
\hline
\text{共有ファイルシステム} & \text{ジョブスクリプト書き込み用} \\
\hline
\text{JWT 認証} & \text{slurmrestd への認証} \\
\hline
\end{array}
$$

これらの要件から、slurm-bridge は「Adjacent(隣接)」ではなく「Converged(統合)」パターンを前提としていることがわかった。

構築手順

1. Slurm REST API (slurmrestd) の設定

Slurm 25.11.0 での slurmrestd 設定:

# JWT 認証での起動
sudo -u slurmrestd bash -c 'export SLURM_JWT=daemon && /usr/local/sbin/slurmrestd -vvv \
    -a rest_auth/jwt \
    -s openapi/slurmctld \
    -d data_parser/v0.0.44 \
    0.0.0.0:6820'

重要: `SLURM_JWT=daemon` 環境変数が JWT 認証に必須。

2. REST API 動作確認

# ping エンドポイント
curl -s -H "X-SLURM-USER-NAME: ubuntu" -H "X-SLURM-USER-TOKEN: $TOKEN" \
  http://localhost:6820/slurm/v0.0.44/ping

# 応答例
{
  "pings": [
    {
      "hostname": "manual-slurm-head",
      "pinged": "UP",
      "responding": true
    }
  ],
  "meta": {
    "slurm": {
      "version": { "major": "25", "minor": "11", "micro": "0" },
      "release": "25.11.0"
    }
  }
}

3. slurm-bridge Helm デプロイ

helm install slurm-bridge oci://ghcr.io/slinkyproject/charts/slurm-bridge \
  --version 1.0.0 \
  --namespace slinky \
  --set config.slurmRestApi=http://10.0.3.10:6820 \
  --set config.partition=slurm-bridge

4. デプロイ確認

kubectl get pods -n slinky
NAME                                         READY   STATUS    RESTARTS
slurm-bridge-admission-7c7b4fb67f-7wrdw      1/1     Running   0
slurm-bridge-controllers-5c9f7c9c99-mbncm    1/1     Running   0
slurm-bridge-scheduler-75785bf655-t9mtd      1/1     Running   0

GPU ワークロードの実行

Pod マニフェスト

slurm-bridge-scheduler を使用する Pod:

apiVersion: v1
kind: Pod
metadata:
  name: yolo-benchmark-slurm-bridge
  labels:
    app: yolo-benchmark
    scheduler: slurm-bridge
spec:
  schedulerName: slurm-bridge-scheduler  # ここがポイント
  restartPolicy: Never
  containers:
  - name: yolo
    image: ultralytics/ultralytics:latest
    command:
    - python3
    - -c
    - |
      import torch
      from ultralytics import YOLO
      import time
      import numpy as np

      print("=== slurm-bridge Scheduler E2E Benchmark ===")
      print("PyTorch version:", torch.__version__)
      print("CUDA available:", torch.cuda.is_available())
      if torch.cuda.is_available():
          print("GPU:", torch.cuda.get_device_name(0))

      dummy_img = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
      model = YOLO("yolov8n.pt")

      # Warmup
      for _ in range(10):
          model(dummy_img, verbose=False)

      # Benchmark
      start = time.time()
      for _ in range(100):
          model(dummy_img, verbose=False)
      elapsed = time.time() - start

      print("FPS: {:.2f}".format(100 / elapsed))
    resources:
      limits:
        nvidia.com/gpu: 1
      requests:
        nvidia.com/gpu: 1

スケジューリングの流れ

Pod イベントから確認できるスケジューリングの流れ:

Events:
  Warning  FailedScheduling  110s  slurm-bridge-scheduler  0/2 nodes are available
  Warning  FailedScheduling  110s  slurm-bridge-scheduler  running PreFilter plugin "SlurmBridge"
  Normal   Scheduled         108s  slurm-bridge-scheduler  Successfully assigned default/yolo-benchmark to ip-10-0-3-21
  Normal   Pulling           108s  kubelet                 Pulling image "ultralytics/ultralytics:latest"
  Normal   Pulled            108s  kubelet                 Successfully pulled image in 112ms
  Normal   Created           108s  kubelet                 Created container: yolo
  Normal   Started           108s  kubelet                 Started container yolo

slurm-bridge-scheduler が Pod を GPU ワーカーノードに割り当て、kubelet がコンテナを実行している。

ベンチマーク結果

E2E テスト結果(slurm-bridge-scheduler 経由)

=== slurm-bridge Scheduler E2E Benchmark ===
PyTorch version: 2.9.1+cu128
CUDA available: True
GPU: Tesla T4

=== Results ===
Total time: 0.70s
FPS: 141.93
Pattern: slurm-bridge Scheduler E2E

各検証での FPS 参考値

注意: 以下の FPS 値は、各検証で異なるハードウェア・ソフトウェア構成で計測されたものであり、アーキテクチャパターンの優劣を示すものではない。Under パターンは T4G/ARM64/PyTorch 2.7.0+cu128、その他は T4/x86/各種 PyTorch/CUDA で計測されている。

$$
\begin{array}{|l|l|l|l|l|}
\hline
\text{\textbf{パターン}} & \text{\textbf{構成}} & \text{\textbf{FPS}} & \text{\textbf{環境}} \\
\hline
\text{Converged} & \text{slurm-bridge E2E} & \text{141.93} & \text{T4/x86/PyTorch 2.9.1+cu128} \\
\hline
\text{Adjacent} & \text{K8s 直接} & \text{143.83} & \text{T4/x86/PyTorch 2.9.1+cu128} \\
\hline
\text{Adjacent} & \text{Slurm 直接} & \text{128.74} & \text{T4/x86/PyTorch 2.5.1+cu121} \\
\hline
\text{Converged} & \text{srun + ctr} & \text{68.85} & \text{T4/x86} \\
\hline
\text{Over} & \text{kubeadm (永続)} & \text{68.06} & \text{T4/x86} \\
\hline
\text{Over} & \text{microk8s} & \text{62.71} & \text{T4/x86} \\
\hline
\text{Under} & \text{Slinky (slurmd Pod)} & \text{55.34} & \text{T4G/ARM64/PyTorch 2.7.0+cu128} \\
\hline
\end{array}
$$

性能分析

slurm-bridge-scheduler 経由の実行(141.93 FPS)は、同一構成(T4/x86/PyTorch 2.9.1+cu128)での K8s 直接実行(143.83 FPS)と近い値を示した。ただし、この比較は同一ソフトウェアスタック・同一 GPU での厳密な A/B テストではないため、「オーバーヘッドゼロ」と断定することはできない。

一方、srun + containerd での実行(68.85 FPS)は約半分の性能となった。これは srun 経由のコンテナ起動オーバーヘッドとマウント処理が原因と考えられる。

発見事項

slurm-bridge の誤解しやすい点

ドキュメントでは「adjacent cluster」と記載されているが、実際には K8s ノード上で slurmd の稼働が必須。これは Adjacent ではなく **Converged** パターンだ。

REST API バージョン互換性

slurm-bridge v1.0.0 は REST API v0.0.44 を要求する。Slurm 25.11.0 でこのバージョンがサポートされていることを確認した。

JWT 認証の設定

slurmrestd の JWT 認証には以下が必要:

  • `AuthAltTypes=auth/jwt` を slurm.conf に設定

  • `SLURM_JWT=daemon` 環境変数で slurmrestd を起動

  • JWT token を X-SLURM-USER-TOKEN ヘッダーで送信

構成選択のガイドライン

slurm-bridge が適しているケース

$$
\begin{array}{|l|l|l|}
\hline
\text{\textbf{ユースケース}} & \text{\textbf{適性}} & \text{\textbf{理由}} \\
\hline
\text{K8s ネイティブな操作でSlurm活用} & \text{最適} & \text{kubectl でジョブ投入、Slurm がスケジューリング} \\
\hline
\text{既存 Slurm クラスタの K8s 統合} & \text{適切} & \text{ノードに kubelet を追加するだけ} \\
\hline
\text{GPU リソースの統合管理} & \text{最適} & \text{Slurm GRES と K8s Device Plugin の両方を活用} \\
\hline
\text{K8s/Slurm 両方のスケジューリング機能を活用} & \text{適切} & \text{Slurm の公平性スケジューリングを K8s から利用可能} \\
\hline
\end{array}
$$

他パターンとの比較

$$
\begin{array}{|l|l|l|l|}
\hline
\text{\textbf{観点}} & \text{\textbf{Converged (slurm-bridge)}} & \text{\textbf{Under (Slinky)}} & \text{\textbf{Over (K8s on Slurm)}} \\
\hline
\text{スケジューラ} & \text{Slurm} & \text{Slurm + K8s} & \text{K8s} \\
\hline
\text{K8s 操作} & \text{kubectl} & \text{sbatch} & \text{kubectl} \\
\hline
\text{性能} & \text{141 FPS*} & \text{55 FPS*} & \text{68 FPS*} \\
\hline
\text{構築複雑度} & \text{高} & \text{高} & \text{中} \\
\hline
\text{既存HPC親和性} & \text{高} & \text{中} & \text{高} \\
\hline
\end{array}
$$

*注: 各パターンは異なる GPU/アーキテクチャ/PyTorch バージョンで計測されているため、FPS 値の直接比較は参考程度に留める必要がある。

まとめ

slurm-bridge v1.0.0 を使った Converged パターンの構築に成功し、以下を確認した。

主要な発見

  1. slurm-bridge は Converged パターン向け: K8s ノード上で slurmd が稼働していることが前提

  2. K8s 直接実行と近い性能: 141.93 FPS を計測(ただし厳密な A/B テストではないため、オーバーヘッドの有無は断定できない)

  3. Slurm スケジューリングの透過的利用: Pod マニフェストの `schedulerName` 指定だけで Slurm 経由のスケジューリングが実現

  4. JWT 認証の設定が重要: slurmrestd の正しい起動方法を把握する必要あり

構築時の注意点

  1. K8s ノードに slurmd をインストール: これが最も重要な前提条件

  2. Munge 認証の統一: K8s ノードと Slurm クラスタで同一の munge.key を使用

  3. REST API バージョン: slurm-bridge v1.0.0 は v0.0.44 を要求

  4. 共有ストレージ: ジョブスクリプトの書き込み用に必要

Converged パターンは構築は複雑だが、K8s の操作性と Slurm のHPCスケジューリング機能を最も効率的に統合できるアプローチだ。

参考資料

いいなと思ったら応援しよう!