[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-bridge4. デプロイ確認
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 0GPU ワークロードの実行
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 yoloslurm-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 パターンの構築に成功し、以下を確認した。
主要な発見
slurm-bridge は Converged パターン向け: K8s ノード上で slurmd が稼働していることが前提
K8s 直接実行と近い性能: 141.93 FPS を計測(ただし厳密な A/B テストではないため、オーバーヘッドの有無は断定できない)
Slurm スケジューリングの透過的利用: Pod マニフェストの `schedulerName` 指定だけで Slurm 経由のスケジューリングが実現
JWT 認証の設定が重要: slurmrestd の正しい起動方法を把握する必要あり
構築時の注意点
K8s ノードに slurmd をインストール: これが最も重要な前提条件
Munge 認証の統一: K8s ノードと Slurm クラスタで同一の munge.key を使用
REST API バージョン: slurm-bridge v1.0.0 は v0.0.44 を要求
共有ストレージ: ジョブスクリプトの書き込み用に必要
Converged パターンは構築は複雑だが、K8s の操作性と Slurm のHPCスケジューリング機能を最も効率的に統合できるアプローチだ。
