仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

KubernetesとKueueでGPUを賢く使う(後編)

今回の記事はKubernetesとKueue、そしてKubeflowを連携したGPUの利用に関してです、 KueueやKubeflowは感嘆にまとめるのが難しかったため、前編、中編、そして今回の後編の3つ二分割しました。

Kueueの基礎については、前編に書いているつもりです。

tech.virtualtech.jp

KubernetesでGPUを使う方法とKueueのセットアップについては、中編でまとめています。

tech.virtualtech.jp

今回は、KueueとKubeflowの連携について書いています。

目的

後編ではKubeflowをKueueと組み合わせて動かすのを試してみます。 GPU利用可能なKubernetes + Kueue環境の上で、以下の3点を動かして確認します。

  • Kubeflow Trainer v1PyTorchJob
  • Kubeflow Trainer v2TrainJob
  • KueueによるGPUジョブのAdmission / 実行制御

なお、この後はやたら長文とYAMLファイルのオンパレードになります。内容が内容なのでご了承ください。


目次

かなり長いので、今回は目次を付けました。


0. Kubeflow と本書の構成

KubeflowはKubernetes上で機械学習ワークロードを管理するプラットフォームです。Kubeflowは色々なプロジェクトが存在しています。今回はその中からKubeflow TrainerとKubeflow Pipelinesを使って最初に説明したジョブを実行します。

ちなみにKubeflowのプロジェクトは次のページにまとめられています。

まずは「学習ジョブの実行」に特化した Kubeflow Trainer を使ってみます。 Trainerにはv1とv2があります。v2の方が新しいバージョンです。ただ、新しいバージョンはKubeflow Trainerの初学者には分かりにくいため、まずはv1を試して、そのあとv2を試す 方針をとります。

観点 v1(Training Operator) v2(Trainer)
API PyTorchJob など framework ごと TrainJob に統一
記述の重心 ReplicaSpec / Pod テンプレート Runtime + Job 定義
見え方 Kubernetes 寄り アプリケーション寄り
構造の見えやすさ 裏側が見えやすい 書きやすい
将来性 legacy 本命

1. 前提条件と使用する名前

前提

以下はすでに完了している前提です。

  • Kubernetesクラスタが構築済みで利用可能
  • NVIDIA Driver / Container Runtime / Device PluginのGPU利用準備が完了
  • runtimeClassName: nvidia が利用可能
  • Kueueがインストール済み
  • Queueが作成済み

本書で使う名前

中編で作成したQueue定義をそのまま今回も使います。 自分の環境で名前が異なる場合は、YAML内の該当箇所を読み替えてください。

項目
Namespace ml-jobs
LocalQueue gpu-queue
ClusterQueue gpu-clusterqueue
RuntimeClass nvidia

2. 事前確認(Kueue / GPU)

Kubeflow Jobを投入する前に、KueueのQueueとGPUのノード認識を確認します。

2-1. Queue の存在確認

kubectl get localqueues -A
kubectl get clusterqueues
kubectl get resourceflavors

次のように表示されれば想定通りです。

  • ml-jobs namespaceに gpu-queue が表示される
  • gpu-clusterqueue が表示される
  • ResourceFlavor gpu が表示される

次の場合は対処の項に従って、問題を解決してください。

症状 対処
gpu-queue が無い LocalQueue / ClusterQueue の作成が未完了。Queue 作成手順を先に実施する
ResourceFlavor が無い Flavor が無いと Resource 割り当てが成立しない。Kueue の ResourceFlavor 定義を確認する。

2-2. ClusterQueue の quota 確認

Queueが存在していてもGPU quotaが無ければGPU JobはAdmissionされません。

kubectl describe clusterqueue gpu-clusterqueue

次のように表示されれば想定通りです。

  • Resource Groupscpu, memory, nvidia.com/gpu が含まれている
  • ConditionsType: Active / Status: TrueMessage: Can admit new workloads が表示される

次の場合は対処の項に従って、問題を解決してください。

症状 対処
nvidia.com/gpu が無い GPU quota が未設定。ClusterQueue に ResourceGroup / quota を追加する
Active=False 設定ミスか依存リソース不足。kubectl describe clusterqueue の Conditions を確認する

2-3. GPU のノード認識確認

Kueueにquotaがあっても、ノードがGPUをAdvertiseしていなければPodは起動できません。

# ノード名を確認
kubectl get nodes

# GPU ノードの Capacity / Allocatable を確認
kubectl describe node <GPUノード名> | grep -A5 -E 'Capacity:|Allocatable:'
kubectl describe node <GPUノード名> | grep nvidia.com/gpu

次のように表示されれば想定通りです。

  • Capacity または Allocatablenvidia.com/gpu: 1(以上)が表示される

次の場合は対処の項に従って、問題を解決してください。

症状 対処
nvidia.com/gpu が表示されない NVIDIA Device Plugin が動いていない可能性がある。kubectl get pods -A | grep -i nvidia で確認する
Device Plugin は動いているが GPU が出ない ホスト側で nvidia-smi が動くか確認し、Driver / Container Runtime / Plugin 登録状態を切り分ける

3. Trainer v1 セットアップ

まずは旧来のTrainer v1を使って、ジョブを流す方法を試してみます。

3-1. インストール

kubectl apply --server-side -k \
  "github.com/kubeflow/training-operator.git/manifests/overlays/standalone?ref=v1.8.1"

次のように表示されれば想定通りです。

  • CRD, ServiceAccount, Deploymentなどに serverside-applied または created が並ぶ。

次の場合は対処の項に従って、問題を解決してください。

症状 対処
ネットワークエラー GitHub への到達または名前解決の問題。時間をおいて再実行する
エラーなく完了したが Pod が起動しない 次のステップで Pod 状態を確認する

3-2. Controller の起動確認

applyが完了してもController Podが起動していなければ PyTorchJob は処理されません。

kubectl get pods -n kubeflow
kubectl get crd | grep kubeflow.org

次のように表示されれば想定通りです。

  • training-operator-xxxxx 1/1 Running
  • pytorchjobs.kubeflow.org などのCRDが表示される

次の場合は対処の項に従って、問題を解決してください。

症状 対処
Pod が存在しない インストールに失敗している。前ステップの apply を再実行する
CrashLoopBackOff kubectl logs -n kubeflow <pod名> でログを確認する

4. v1 動作検証(PyTorchJob)

4-1. YAML の作成

Kueue経由でGPUを利用する最小構成の PyTorchJob を作成します。

cat <<'EOF' > 10-pytorchjob-v1.yaml
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
  name: pytorch-test
  namespace: ml-jobs
  labels:
    kueue.x-k8s.io/queue-name: gpu-queue
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      restartPolicy: Never
      template:
        spec:
          runtimeClassName: nvidia
          containers:
          - name: pytorch
            image: docker.io/kubeflowkatib/pytorch-mnist-gpu:latest
            command:
              - python
              - mnist.py
              - --epochs=1
            resources:
              limits:
                nvidia.com/gpu: "1"
EOF

YAML を作成したら内容を確認:

cat 10-pytorchjob-v1.yaml
症状 対処
gpu-queue が自分の環境に存在しない Kueue 管理下に入らない。自分の LocalQueue 名に合わせて修正する
runtimeClassName: nvidia が存在しない Pod 起動に失敗する。自分の RuntimeClass 名に合わせて修正する

4-2. 投入

kubectl apply -f 10-pytorchjob-v1.yaml

次のように表示されれば想定通りです。

  • pytorchjob.kubeflow.org/pytorch-test created

次の場合は対処の項に従って、問題を解決してください。

症状 対処
YAML 構文エラー インデントやフィールド名のミス。YAMLファイルの内容を見直す。構文チェックを実施する
Webhook / Admission エラー Controller がまだ起動途中の可能性がある。もう数十秒待って再実行する

4-3. 実行状態の確認

kubectl get pytorchjobs -n ml-jobs
kubectl get workloads -n ml-jobs
kubectl get pods -n ml-jobs

Podが起動したらログを確認します。

kubectl logs -n ml-jobs pytorch-test-master-0

次のように表示されれば想定通りです。

  1. PyTorchJob のSTATUS → RunningSucceeded
  2. 対応する Workload が作られ ADMITTED=True, FINISHED=True
  3. pytorch-test-master-0 Podが Completed
  4. ログに学習の進捗(Train Epoch, loss=... など)が出る

次の場合は対処の項に従って、問題を解決してください。

症状 対処
Workload が Admitted にならない GPU quota が空いていないか Queue 設定に問題がある。kubectl describe workload -n ml-jobs を確認する
Pod が Pending のまま GPU スケジューリングや RuntimeClass の問題。kubectl describe pod -n ml-jobs <pod名> を確認する
Pod が起動したがすぐ失敗する イメージ内のコマンドや GPU 利用設定の問題。kubectl logs でコンテナログを確認する

5. Trainer v2 セットアップ

次に新しいTrainer v2を使って、ジョブを流してみます。

5-1. インストール

v2はmanagerとruntimesの2つによって構成されます。manager の Pod が Running になってから runtimes を apply してください。 起動前にruntimesをapplyするとWebhookエラーになります。

export VERSION=v2.1.0

# Step 1: manager を導入
kubectl apply --server-side -k \
  "https://github.com/kubeflow/trainer.git/manifests/overlays/manager?ref=${VERSION}"

# Step 2: Manager Pod が Running になるまで待つ
kubectl get pods -n kubeflow-system

# Step 3: runtimes を導入
kubectl apply --server-side -k \
  "https://github.com/kubeflow/trainer.git/manifests/overlays/runtimes?ref=${VERSION}"

次の場合は対処の項に従って、問題を解決してください。

症状 対処
connection refused のような Webhook エラー manager Pod がまだ起動前に runtimes を apply した。Pod を確認してから runtimes apply を再実行する
GitHub 到達エラー ネットワークやプロキシ設定を確認する
Pod が存在しない apply に失敗している。インストール手順を再実行する
CrashLoopBackOff kubectl logs -n kubeflow-system <pod名> でログを確認する
Running だが直後に Webhook エラー 起動直後で Webhook server が安定していない。数十秒待って再実行する

5-2. Runtime の登録確認

TrainJob が参照するRuntimeが存在していなければJobが意図した動作になりません。

kubectl get crd | grep trainer.kubeflow.org
kubectl get clustertrainingruntimes

次のように表示されれば想定通りです。

  • clustertrainingruntimes.trainer.kubeflow.org
  • trainjobs.trainer.kubeflow.org
  • torch-distributed(ClusterTrainingRuntimeとして表示)

次の場合は対処の項に従って、問題を解決してください。

症状 対処
CRD はあるが Runtime が無い runtimes の apply が未実行か失敗している。インストール手順を見直す
torch-distributed が無い YAML の runtimeRef.namekubectl get clustertrainingruntimes の出力に合わせて修正する

6. v2 動作検証(TrainJob)

6-1. YAML の作成

v2.1.0のschemaに合わせた TrainJob です。スキーマの落とし穴が多いため、以下のYAMLをそのまま使うことを推奨します。

cat <<'EOF' > 20-trainjob-v2.yaml
apiVersion: trainer.kubeflow.org/v1alpha1
kind: TrainJob
metadata:
  name: trainjob-test
  namespace: ml-jobs
  labels:
    kueue.x-k8s.io/queue-name: gpu-queue
spec:
  runtimeRef:
    name: torch-distributed
  trainer:
    image: docker.io/kubeflowkatib/pytorch-mnist-gpu:latest
    command:
      - python
      - mnist.py
      - --epochs=1
    numNodes: 1              # 文字列 "1" ではなく整数で書く
    numProcPerNode: 1        # 文字列 "1" ではなく整数で書く
    resourcesPerNode:
      requests:
        cpu: "4"
        memory: "8Gi"
        nvidia.com/gpu: "1"
      limits:
        cpu: "4"
        memory: "8Gi"
        nvidia.com/gpu: "1"
EOF

よくある YAML のミス:

ミス 症状 正しい書き方
spec.model を使っている v2.1.0 では不一致になる 上記 YAML をそのまま使う
resourcesPerNode.cpu と直書き フィールド不一致 resourcesPerNode.requests.cpu の階層で書く
numProcPerNode: "1"(文字列) Webhook に弾かれる numProcPerNode: 1(整数)

6-2. 投入と初期状態の確認

kubectl apply -f 20-trainjob-v2.yaml
kubectl get trainjobs -n ml-jobs

次のように表示されれば想定通りです。

  • trainjob.trainer.kubeflow.org/trainjob-test created

作成直後はSTATEが空または一時的に Suspended に見える場合があります。これは正常です。

次の場合は対処の項に従って、問題を解決してください。

症状 対処
strict decoding error フィールド名が schema と一致していない。上記 YAML を使う
Webhook で denied the request numProcPerNode の型、resourcesPerNode の階層を見直す

6-3. 実行状態の確認

kubectl get workloads -n ml-jobs
kubectl get trainjobs -n ml-jobs
kubectl get pods -n ml-jobs

Pod名が分かったらログを確認します。

kubectl logs -n ml-jobs <pod名>

次のように表示されれば想定通りです。

  1. Workloadが作られ ADMITTED=True
  2. Podが Running
  3. ログに Train Epoch: 1 ... loss=... が出る

補足:STATESuspended に見えるとき

kubectl get trainjobs の表示だけでは誤解しやすいことがあります。次のコマンドで詳細を確認してください。

kubectl describe trainjob -n ml-jobs trainjob-test

以下であれば正常に動作しています。

  • Spec.Suspend: false
  • Type: Suspended / Status: False
  • Message: TrainJob is resumed

次の場合は対処の項に従って、問題を解決してください。

症状 対処
Workload が Admitted にならない GPU quota / Queue 設定を確認する。kubectl describe workload -n ml-jobs を見る
Admitted だが Pod が起動しない Pod 側のスケジューリング問題。kubectl describe pod -n ml-jobs <pod名> を確認する
Pod は起動したがログが進まない イメージ、コマンド、データセット取得、GPU 初期化のいずれかの問題。kubectl logskubectl describe pod を確認する

6-4. 完了確認

kubectl get pods -n ml-jobs -w    # -w で完了まで監視

完了後:

kubectl get trainjobs -n ml-jobs
kubectl get workloads -n ml-jobs

次のように表示されれば想定通りです。

  • Pod: Completed
  • TrainJob: STATE Complete
  • Workload: ADMITTED=True のまま完了

次の場合は対処の項に従って、問題を解決してください。

症状 対処
Pod が Error で終了 kubectl logs -n ml-jobs <pod名> でコンテナログを確認する
Pod は終わったのに TrainJob が更新されない Controller の status 反映遅延の可能性がある。kubectl describe trainjob -n ml-jobs trainjob-test を確認する

7. v1 と v2 の見え方の違い

観点 v1(PyTorchJob) v2(TrainJob)
Job の書き方 ReplicaSpec + Pod テンプレートをそのまま書く Runtime を参照し、trainer ブロックだけ書く
Pod 構成の見え方 Master Replica が直接見える JobSet が中間に入るため間接的
Kueue との接点 label をつけるだけ。構造は同じ label をつけるだけ。構造は同じ
向いている場面 Kubernetes リソースの構造理解 実運用・今後の標準として学ぶ

v1とv2のYAMLを書いたので、比較してみましょう。vimdiffという便利なコマンドがあるので、それを使って比較してみます。 両者は同じイメージを使ってアプリケーションを実行しています。細かい部分までみる必要は無いのですが、v1はkubeflow.org/v1のPyTorchJobのAPIを使っているのに対して、v2はtrainer.kubeflow.org/v1alpha1のTrainJobを使っているのがわかります。 スペックの書き方も少々異なるようです。

(画像差し込み)

現時点のバージョンではレガシーのv1、新しい設計のv2のどちらでも、Kueueはサポートしています。


8. 片付け

検証後にJobを削除して、再実験しやすい状態に戻します。

kubectl delete -f 10-pytorchjob-v1.yaml
kubectl delete -f 20-trainjob-v2.yaml

削除後にJob / Pod / Workloadが消えたことを確認します。

kubectl get pytorchjobs,trainjobs,pods,workloads -n ml-jobs

9. Kubeflow Pipelinesとの連携

9-0. 事前準備

次に、Kubeflow Pipelinesを使ったジョブの実行についても試してみます。

9-0-1. ストレージの準備

これを試す場合、事前にストレージが必要です。 シングルノードではlocal-path-storageが利用可能です。デプロイ後、標準のストレージと設定してください。

マルチノードの場合は、マルチノードでも動作する適切なストレージを展開してください。

kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-storage.yaml
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

9-0-2. 一般ユーザーでの利用

この後の流れでPythonスクリプトでJobを流しますが、root権限のユーザーではこのコードは動作しません。 しかしこれはKubefrow Pipelineの制限では無く、あくまでPythonの制限です。

実行するとKubefrowのジョブにはグリーンマークがつくのですが、そのログをみると「rootユーザーで実行したので阻止した」といわれてジョブが正常にどうさしません。

任意の一般ユーザーでシステムにログインして、Python仮想環境を作成した後に仮想環境内でpip install kfpを実行して実行環境を準備してください。 また、kubeconfigをそのユーザーの.kubeディレクトリーに格納してください。 Pythonのkfpモジュールのついての詳細は、以下をご覧ください。このモジュール名はKubeFlow Pipelinesと頭文字をとってkfpとなっています。

次はシステムにインストール済みのPython 3.12仮想環境を作って、kfpモジュールをインストールする例です。

python3.12 -m venv .venv
source ./.venv/bin/activate 
pip install kfp

KUBECONFIGについてはkubeadmを使って構築した場合は、次のように実行すると良いでしょう。

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

9-1. Pipelines を組み合わせる意味

Trainer + KueueだけでもGPU学習ジョブは実行できます。Pipelineを加えることで「学習ジョブを部品として、前処理・学習・評価などの一連のワークフローにつなぐ」ことが可能になります。

コンポーネント 役割
Kueue GPU などの限られたリソースを考慮して賢くジョブの実行順を制御する
Trainer 学習ジョブを表現して実行する
Pipeline 前処理、学習、評価などをひとつのワークフローとしてつなぐ

学習→再現可能なワークフローの例:

データ前処理 → TrainJob 投入 → 学習完了 → 評価 → 基準を満たした場合のみ次へ

Trainerだけの場合、この流れは手動の kubectl apply やシェルスクリプトで繋ぐ必要があります。Pipelineを使うと、これを1つの実行単位として定義・再実行できます。

9-2. Kubeflow Pipelines のインストール

export PIPELINE_VERSION=2.15.0

kubectl apply -k \
  "github.com/kubeflow/pipelines/manifests/kustomize/cluster-scoped-resources?ref=${PIPELINE_VERSION}"
kubectl wait --for condition=established --timeout=60s crd/applications.app.k8s.io
kubectl apply -k \
  "github.com/kubeflow/pipelines/manifests/kustomize/env/dev?ref=${PIPELINE_VERSION}"

確認:

kubectl get pods -A | grep -E 'pipeline|argo|ml-pipeline'

デプロイ後、次のようなステータスになります。 そのうちproxy-agentCrashLoopBackOffの状態が続きますが、これは後述にあるように、名前解決ができないためです。どうやらKubeflow Pipelinesの現在のバージョンのマニフェストがGCP上の仮想マシンで動かす前提のコードになっており、それ以外の環境(オンプレK8sなど)ではproxy-agentが上手く動かないためです。

今回の検証ではこれが動かなくても支障は無いため、そのまま無視して構いません。気になるようであれば手順に従ってPodを削除してください。

# kubectl get pods -n kubeflow
NAME                                               READY   STATUS             RESTARTS        AGE
cache-deployer-deployment-56d88b59cc-pjxlf         1/1     Running            0               12m
cache-server-6d8bfc7488-vb99v                      1/1     Running            0               12m
controller-manager-6685d7dcc7-g2glt                1/1     Running            0               12m
metadata-envoy-deployment-7c499db4bf-f42h7         1/1     Running            0               12m
metadata-grpc-deployment-74b7fd6cb6-hbpxf          1/1     Running            7 (6m12s ago)   12m
metadata-writer-6f8b89765-2mdzp                    1/1     Running            3 (3m40s ago)   12m
ml-pipeline-6c4b9d6f9d-f2jdz                       1/1     Running            4 (5m13s ago)   12m
ml-pipeline-persistenceagent-75dd4b54cf-dwkl7      1/1     Running            2 (6m43s ago)   12m
ml-pipeline-scheduledworkflow-67f4f96c8d-n49jr     1/1     Running            4 (6m6s ago)    12m
ml-pipeline-ui-69c7f869dd-ttczc                    1/1     Running            0               12m
ml-pipeline-viewer-crd-7d8d667b5c-m9s6r            1/1     Running            0               12m
ml-pipeline-visualizationserver-76cc49c944-8rqs7   1/1     Running            0               12m
mysql-66f7cd88c6-cpjsv                             1/1     Running            0               12m
proxy-agent-5f586ff77-9d82w                        0/1     CrashLoopBackOff   6 (3m25s ago)   12m
seaweedfs-6c5cff9744-2tbzk                         1/1     Running            0               12m
training-operator-84469657ff-x25gn                 1/1     Running            0               28h
workflow-controller-c5cd4b8d9-mlvwj                1/1     Running            0               12m
# kubectl logs proxy-agent-5f586ff77-9d82w -n kubeflow
+++ dirname /opt/proxy/attempt-register-vm-on-proxy.sh
++ cd /opt/proxy
++ pwd
+ DIR=/opt/proxy
++ kubectl get configmap inverse-proxy-config -o json
++ jq -r '.data.Hostname // empty'
+ HOSTNAME=
+ [[ -n '' ]]
+ [[ ! -z '' ]]
++ curl http://metadata.google.internal/computeMetadata/v1/instance/zone -H 'Metadata-Flavor: Google'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (6) Could not resolve host: metadata.google.internal
+ INSTANCE_ZONE=/

そのまま動かしても良いみたいですが、なんか気持ち悪ければ消します。

kubectl delete deployment proxy-agent -n kubeflow

その他のPodが落ち続ける場合は、公式ドキュメントの platform-agnostic 構成を検討してください。

9-3. Argo Workflowsパッチと権限の付与

Argo Workflows のセキュリティパッチ (PEP 668対応)

パイプラインの初期化コンテナがrootで動こうとすると弾かれます。これに対応するため、次のように一般ユーザー(ID: 1000)で動かすためのパッチを適用します。

kubectl patch configmap workflow-controller-configmap -n kubeflow \
  --patch '{"data":{"executor":"securityContext:\n  runAsUser: 1000\n  runAsNonRoot: true\n"}}'
kubectl rollout restart deployment workflow-controller -n kubeflow

パイプライン実行役へ管理者権限を付与

パイプライン(pipeline-runner)が他の名前空間(ml-jobs)にジョブを作るためには権限の付与が必要です。権限の付与は次のように実行できます。

kubectl create clusterrolebinding pipeline-runner-admin-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=kubeflow:pipeline-runner

9-4. Pipeline から PyTorchJob を投入する最小サンプル

このコードはPythonのkfpモジュールを使って、kubeflow Pipelineを流して最終的にPyTorchJobをクラスターで実行します。実行するジョブはGPU版のpytorch-mnistです。これまでと同じものを実行します。

注意: これは構造を掴むための最小例です。実運用では ServiceAccount、RBAC、イメージ、Artifact の扱いを別途整理してください。

注意: パイプライン内でダウンロードする kubectl のバージョンは、クラスターのバージョンと一致させる必要があります。今回の環境はv1.35.3を想定しているため、v1.35.3を設定しています。現在のクラスターに合うバージョンを指定しないと互換性エラーが発生する可能性があるため、今回の場合はv1.35.3または stable.txt から取得する方式を採用してください。

注意: 初稿ではport-forwardを使っていたのですが、かえって説明が難しくなりすぎたのでNodePortを使う方法に書き換えました。こっちの方がすっきりします。テスト環境ではこれで十分ですが、本番環境ではIngressやLoadBalancer等を採用してください。

次の方法で、ml-pipeline-uiとml-pipelineをNodePortサービスで公開します。ml-pipeline-uiがKubernetes pipeline UIへのアクセスを、ml-pipeline はパイプラインを流すAPIエンドポイントを公開するものです。

//ml-pipeline-uiとml-pipelineをサービスで公開
kubectl patch svc ml-pipeline-ui -n kubeflow -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "nodePort": 30080}]}}'
kubectl patch svc ml-pipeline -n kubeflow -p '{"spec": {"type": "NodePort", "ports": [{"port": 8888, "nodePort": 30088}]}}'

//確認
kubectl get svc -n kubeflow | grep -E 'ml-pipeline-ui|ml-pipeline '
ml-pipeline         NodePort    10.110.131.20    <none>        8888:30088/TCP,8887:30937/TCP         4d1h
ml-pipeline-ui      NodePort    10.109.207.146   <none>        80:30080/TCP                          4d1h

ml-pipeline-uiのNodePortにアクセスすると、次のようなKubeflow Pipelineのインターフェースにアクセスできます。

準備が完了したら次のファイルを作ってpython main.pyのように実行してください。

from kfp import dsl
from kfp import client  # ← これも必要です

# 1. コンポーネント(部品)の定義
@dsl.component(base_image="python:3.12-slim")
def submit_pytorchjob():
    import subprocess
    import os
    import urllib.request

    # kubectlの準備
    kubectl_url = "https://dl.k8s.io/release/v1.35.3/bin/linux/amd64/kubectl"
    kubectl_path = "/tmp/kubectl"
    urllib.request.urlretrieve(kubectl_url, kubectl_path)
    os.chmod(kubectl_path, 0o755)

    # PyTorchJobのYAML
    yaml_content = """
apiVersion: "kubeflow.org/v1"
kind: "PyTorchJob"
metadata:
  name: "pipeline-pytorchjob"
  namespace: "ml-jobs"
spec:
  pytorchReplicaSpecs:
    Master:
      replicas: 1
      template:
        spec:
          containers:
            - name: pytorch
              image: docker.io/kubeflowkatib/pytorch-mnist-gpu:latest
"""
    # 実行
    subprocess.run([kubectl_path, "apply", "-f", "-"], input=yaml_content, text=True, check=True)

# 2. パイプライン(流れ)の定義
@dsl.pipeline(name="submit-pytorchjob-pipeline")
def my_pipeline():
    submit_pytorchjob()

# 3. 実行(ここで client を使います)
if __name__ == "__main__":
    SERVER_IP = "192.168.x.x"  # あなたの環境のサーバーIPに書き換えてください
    ENDPOINT = f"http://{SERVER_IP}:30088" 
    kfp_client = client.Client(host=ENDPOINT)

    run = kfp_client.create_run_from_pipeline_func(
        my_pipeline,
        arguments={}
    )
    print(f"Run submitted! ID: {run.run_id}")

実行すると次のように表示されます。URLがちょっと変なのですが、Run detailsのほうのURLのNodePortを30088から30080に変えてアクセスすると次のような感じで発行したJobの状況を確認できます。ログなども表示されるので分かりやすいです。

Kubeflow Pipelineを使ったとしても、Kueue との接点は基本的には変わりません。 変わるのは「誰が PyTorchJob を作るか」だけです。

見かけ上はPythonのスクリプトを実行するだけでKubernetes上のKubefrow Pipelineがジョブを実行してくれるようになリます。

9-5. よくある落とし穴

症状 原因
Pipeline は通るが Job が作られない Pipeline 実行用 ServiceAccount に PyTorchJob 作成権限がない
Job は作られるが Workload が pending のまま Queue quota / GPU quota / node 側 GPU 状態を確認する
Podのkubectl から API サーバーに到達できない クラスタ内の DNS / ネットワーク設定を確認する
Cannot find context... システム起動直後の DB 書き込み遅延。数分待つか再起動で解決
CreateContainerConfigError Argo の executor が root 権限で動こうとしている。上記パッチを当てる
sh: python3: command not found ベースイメージに Python がない。python:3.x-slim を使用する
Forbidden: ... cannot get pytorchjobs pipeline-runner の権限不足。ClusterRoleBinding を作成する

確認コマンド:

kubectl get pytorchjobs -n ml-jobs
kubectl get workloads -n ml-jobs
kubectl get pods -n ml-jobs
kubectl describe workload -n ml-jobs

動作確認が済んだら、次を実行してJobを片づけてください。

kubectl delete pytorchjobs -n ml-jobs --all