本トピックでは、複数の内部チームが共有する GPU クラスター上で Arena を実行するための完全なセットアップ手順について説明します。ユーザーアイソレーション、ロールベースアクセス制御 (RBAC)、リソースクォータ、および共有ストレージについて取り上げます。
このセットアップでは、名前空間に基づくソフトアイソレーションを採用しており、信頼されたテナント(内部チームまたは部門)が単一の ACK クラスターを共有する場合に適しています。外部組織間でクラスターレベルのアイソレーションが必要な場合は、代わりに個別の ACK クラスターをご利用ください。
このセットアップの仕組み
複数の開発者が GPU クラスターを共有する場合、ユーザーをユーザーグループに分割し、グループごとのリソース制限を適用し、ジョブの可視性を制御する必要があります。本セットアップでは、以下の 3 つのレイヤーを連携させます:
-
Linux ユーザーグループ → Kubernetes 名前空間 → Arena ユーザーグループ
各レイヤーは異なる側面のアイソレーションを実現します。Linux レイヤーはログイン可能なユーザーとその実行環境を制御します。名前空間レイヤーは Kubernetes リソースを分離します。Arena レイヤーは各ユーザーが自身のジョブのみを参照・管理できることを保証します。
このセットアップでは、以下の 5 つのタスクを実施します:
-
タスク 1:ユーザーグループ dev1 および dev2 を作成し、Bob を dev1 に、Tom を dev2 に追加します。
-
タスク 2:Bob および Tom がそれぞれ独自のアカウントでクライアントにログインし、別々の Arena 環境で操作できるようにします。
-
タスク 3:Bob および Tom が自身が送信したジョブのみを参照・管理できる権限を付与します。
-
タスク 4:各ユーザーグループに GPU、CPU、メモリリソースを割り当てます。
-
タスク 5:グループ内でのみアクセス可能な共有ボリュームおよび、複数のグループ間で共有可能なボリュームを作成します。
リソース割り当て
| ユーザーグループ | ユーザー | GPU | CPU | メモリ | 共有ボリューム |
|---|---|---|---|---|---|
| dev1 | Bob | 1 | 無制限版 | 無制限版 | dev1-public および department1-public-dev1 |
| dev2 | Tom | 2 | 8 コア | 60 GiB | dev2-public および department1-public-dev2 |
department1-public-dev1 および department1-public-dev2 は、Apsara File Storage NAS (NAS) ファイルシステム上の同一ディレクトリにマウントされるため、両方のグループのユーザーが当該データにアクセスできます。dev1-public および dev2-public は別々のディレクトリにマウントされ、それぞれ Bob および Tom のみがアクセス可能です。
サンプルクラスター
| ホスト名 | 役割 | IP アドレス | GPU | CPU コア | メモリ |
|---|---|---|---|---|---|
| client01 | クライアント | 10.0.0.97(プライベート)、39.98.xxx.xxx(パブリック) | 0 | 2 | 8 GiB |
| master01 | Master | 10.0.0.91(プライベート) | 0 | 4 | 8 GiB |
| master02 | Master | 10.0.0.92(プライベート) | 0 | 4 | 8 GiB |
| master03 | Master | 10.0.0.93(プライベート) | 0 | 4 | 8 GiB |
| worker01 | Worker | 10.0.0.94(プライベート) | 1 | 4 | 30 GiB |
| worker02 | Worker | 10.0.0.95(プライベート) | 1 | 4 | 30 GiB |
| worker03 | Worker | 10.0.0.96(プライベート) | 1 | 4 | 30 GiB |
本トピックで実行するすべての操作は、特に明記されていない限り、クライアント上の管理者アカウントを使用して実行します。
前提条件
開始する前に、以下の条件を満たしていることを確認してください。
-
ACK クラスター。詳細については、「マネージドクラスターの作成」をご参照ください。
-
ACK クラスターと同じ仮想プライベートクラウド (VPC) 内に作成された Linux を実行する Elastic Compute Service (ECS) インスタンス。このインスタンスは、ジョブの送信元となるクライアント(Arena ワークステーション)として機能します。詳細については、「カスタム起動タブでのインスタンス作成」をご参照ください。
-
最新バージョンの Arena クライアントがインストール済みであること。詳細については、「Arena クライアントの構成」をご参照ください。
ステップ 1:ユーザーおよびユーザーグループの作成
Arena をマスターノード上に直接インストールまたは実行しないでください。代わりに ECS クライアントを使用し、kubeconfig ファイル経由で ACK クラスターに接続します。
Linux ユーザーおよびグループの作成
クライアント上で Bob および Tom のユーザー ID (UID) およびグループ ID (GID) を作成します。Linux アカウントシステムにより、タスク 2(各ユーザーが自身の認証情報を用いてのみログイン可能であり、独自の Arena 環境で実行される)が実現されます。
# Linux グループ dev1 および dev2 を作成します。
groupadd -g 10001 dev1
groupadd -g 10002 dev2
# Linux ユーザー Bob および Tom を作成します。
adduser -u 20001 -s /bin/bash -G dev1 -m bob
adduser -u 20002 -s /bin/bash -G dev2 -m tom
# パスワードを設定します。
passwd bob
passwd tom
Kubernetes 名前空間およびサービスアカウントの作成
ジョブが送信されると、ACK クラスター内で実行されます。各 Linux ユーザーグループは Kubernetes 名前空間に、各 Linux ユーザーはその名前空間内のサービスアカウントにマッピングされます。
root ユーザーとしてクライアントにログインし、以下のコマンドを実行します。kubectl を ACK クラスターに接続するための前提条件については、「クラスターの kubeconfig ファイルの取得および kubectl を用いたクラスターへの接続」をご参照ください。
kubectl のバージョンは 1.10 以降である必要があります。
# dev1 および dev2 の名前空間を作成します。
kubectl create namespace dev1
kubectl create namespace dev2
# Bob および Tom のサービスアカウントを作成します。
kubectl create serviceaccount bob -n dev1
kubectl create serviceaccount tom -n dev2
期待される出力:
namespace/dev1 created
namespace/dev2 created
serviceaccount/bob created
serviceaccount/tom created
ステップ 2:各ユーザー向けの Arena の構成
Arena のインストール
Arena はクライアント上で 1 回だけインストールします。root ユーザーとしてログインし、最新のコミュニティリリースパッケージをダウンロード、解凍し、install.sh を実行します。詳細については、「Arena クライアントの構成」をご参照ください。
kubeconfig ファイルの生成
各ユーザーは、スコープ付きの ID で ACK クラスターにアクセスするための独自の kubeconfig ファイルを必要とします。generate-kubeconfig.sh という名前のスクリプトを、以下の内容で作成します:
#!/usr/bin/env bash
set -e
NAMESPACE=
SERVICE_ACCOUNT=
DURATION=
OUTPUT=
help() {
echo "Usage: $0 -n <namespace> -s <service-account> -d <duration> -o <output-file>"
echo ""
echo "Options:"
echo "-n, --namespace <namespace> Namespace of the service account."
echo "-s, --service-account <name> Name of the service account."
echo "-d, --duration <duration> Duration of the token e.g. 30d."
echo "-o, --output <file> Output file name. If not set, a temporary file will be created."
}
parse() {
while [ $# -gt 0 ]; do
case $1 in
-n | --namespace)
NAMESPACE="$2"
shift 2
;;
-s | --service-account)
SERVICE_ACCOUNT="$2"
shift 2
;;
-d | --duration)
DURATION="$2"
shift 2
;;
-o | --output)
OUTPUT="$2"
shift 2
;;
*)
help
exit 0
;;
esac
done
if [ -z "${NAMESPACE}" ] || [ -z "${SERVICE_ACCOUNT}" ] || [ -z "${DURATION}" ]; then
help
exit 0
fi
if [ -z "${OUTPUT}" ]; then
OUTPUT=$(mktemp -d)/config
elif [ -f "${OUTPUT}" ]; then
echo "Output file \"${OUTPUT}\" already exists."
exit 1
fi
}
# Generate kubeconfig
generate_kubeconfig() {
CONTEXT=$(kubectl config current-context)
CLUSTER=$(kubectl config view -o jsonpath="{.contexts[?(@.name==\"${CONTEXT}\")].context.cluster}")
SERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"${CLUSTER}\")].cluster.server}")
TOKEN=$(kubectl create token "${SERVICE_ACCOUNT}" --namespace "${NAMESPACE}" --duration="${DURATION}")
CERT=$(mktemp)
mkdir -p "$(dirname "${OUTPUT}")"
kubectl config view --raw=true -o jsonpath="{.clusters[?(@.name==\"${CLUSTER}\")].cluster.certificate-authority-data}" | base64 -d >"${CERT}"
kubectl config set-cluster "${CLUSTER}" --kubeconfig="${OUTPUT}" --server="${SERVER}" --embed-certs=true --certificate-authority="${CERT}" >/dev/null
kubectl config set-credentials "${SERVICE_ACCOUNT}" --kubeconfig="${OUTPUT}" --token="${TOKEN}" >/dev/null
kubectl config set-context "${CLUSTER}-${NAMESPACE}-${SERVICE_ACCOUNT}-context" --kubeconfig="${OUTPUT}" --cluster="${CLUSTER}" --user="${SERVICE_ACCOUNT}" --namespace="${NAMESPACE}" >/dev/null
kubectl config use-context "${CLUSTER}-${NAMESPACE}-${SERVICE_ACCOUNT}-context" --kubeconfig="${OUTPUT}" >/dev/null
rm "${CERT}"
echo "Saved kubeconfig to \"${OUTPUT}\"."
}
main() {
parse "$@"
generate_kubeconfig
}
main "$@"
Bob および Tom 向けの kubeconfig ファイルを生成します。以下のコマンドではトークンの有効期限を 720 時間に設定していますが、セキュリティポリシーに応じてこの値を調整してください。
bash generate-kubeconfig.sh -n dev1 -s bob -d 720h -o /home/bob/.kube/config
bash generate-kubeconfig.sh -n dev2 -s tom -d 720h -o /home/tom/.kube/config
期待される出力:
Saved kubeconfig to "/home/bob/.kube/config".
Saved kubeconfig to "/home/tom/.kube/config".
このステップ完了後、タスク 1 およびタスク 2 が完了します。Bob および Tom はそれぞれクライアントにログインし、独自の隔離された環境で Arena を実行できます。
ステップ 3:RBAC 権限の構成
ロールの作成
dev1 および dev2 名前空間にロールを作成します。Bob および Tom には、自身のジョブのみを参照・管理するために必要な最小限の権限が付与されます(タスク 3)。
以下に、各ユーザーに付与される権限をまとめます:
| リソースタイプ | Bob(dev1) | Tom(dev2) |
|---|---|---|
| pods、pods/log、services | フルアクセス | フルアクセス |
| deployments、ReplicaSets | フルアクセス | フルアクセス |
| ConfigMaps | フルアクセス | フルアクセス |
| kubeflow.org リソース | フルアクセス | フルアクセス |
| batch ジョブ | フルアクセス | フルアクセス |
| persistentvolumeclaims、events、services/proxy | 読み取り専用 | 読み取り専用 |
| クラスターレベル:pods、nodes、services、persistentvolumes | 読み取り専用 | 読み取り専用 |
ユーザーグループ dev1 向けに dev1_roles.yaml を作成します:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: arena-topnode
rules:
- apiGroups:
- ""
resources:
- pods
- services
- deployments
- nodes
- nodes/*
- services/proxy
- persistentvolumes
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: arena
namespace: dev1
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- '*'
- apiGroups:
- ""
resources:
- services/proxy
- persistentvolumeclaims
- events
verbs:
- get
- list
- apiGroups:
- ""
resources:
- pods
- pods/log
- services
verbs:
- '*'
- apiGroups:
- ""
- apps
- extensions
resources:
- deployments
- replicasets
verbs:
- '*'
- apiGroups:
- kubeflow.org
resources:
- '*'
verbs:
- '*'
- apiGroups:
- batch
resources:
- jobs
verbs:
- '*'
ユーザーグループ dev2 向けに dev2_roles.yaml を作成します:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: arena-topnode
rules:
- apiGroups:
- ""
resources:
- pods
- services
- deployments
- nodes
- nodes/*
- services/proxy
- persistentvolumes
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: arena
namespace: dev2
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- '*'
- apiGroups:
- ""
resources:
- services/proxy
- persistentvolumeclaims
- events
verbs:
- get
- list
- apiGroups:
- ""
resources:
- pods
- pods/log
- services
verbs:
- '*'
- apiGroups:
- ""
- apps
- extensions
resources:
- deployments
- replicasets
verbs:
- '*'
- apiGroups:
- kubeflow.org
resources:
- '*'
verbs:
- '*'
- apiGroups:
- batch
resources:
- jobs
verbs:
- '*'
両方のロール定義ファイルを適用します:
kubectl apply -f dev1_roles.yaml
kubectl apply -f dev2_roles.yaml
期待される出力:
clusterrole.rbac.authorization.k8s.io/arena-topnode created
role.rbac.authorization.k8s.io/arena created
clusterrole.rbac.authorization.k8s.io/arena-topnode unchanged
role.rbac.authorization.k8s.io/arena created
ロールが正しく作成されたかを確認します:
kubectl get role -n dev1
kubectl get role -n dev2
期待される出力:
NAME CREATED AT
arena 2024-09-14T08:25:34Z
NAME CREATED AT
arena 2024-09-14T08:25:39Z
ロールのユーザーへのバインド
bob_rolebindings.yaml を作成します:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: bob-arena-topnode
namespace: dev1
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: arena-topnode
subjects:
- kind: ServiceAccount
name: bob
namespace: dev1
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: bob-arena
namespace: dev1
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: arena
subjects:
- kind: ServiceAccount
name: bob
namespace: dev1
tom_rolebindings.yaml を作成します:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tom-arena-topnode
namespace: dev2
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: arena-topnode
subjects:
- kind: ServiceAccount
name: tom
namespace: dev2
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: tom-arena
namespace: dev2
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: arena
subjects:
- kind: ServiceAccount
name: tom
namespace: dev2
両方のファイルを適用します:
kubectl apply -f bob_rolebindings.yaml
kubectl apply -f tom_rolebindings.yaml
期待される出力:
clusterrolebinding.rbac.authorization.k8s.io/bob-arena-topnode created
rolebinding.rbac.authorization.k8s.io/bob-arena created
clusterrolebinding.rbac.authorization.k8s.io/tom-arena-topnode created
rolebinding.rbac.authorization.k8s.io/tom-arena created
ロールバインディングが正しく作成されたかを確認します:
kubectl get rolebinding -n dev1
kubectl get rolebinding -n dev2
期待される出力:
NAME ROLE AGE
bob-arena Role/arena 34s
NAME ROLE AGE
tom-arena Role/arena 33s
タスク 1~3 が完了しました。
ステップ 4:リソースクォータの設定
Kubernetes の ResourceQuota オブジェクトは、名前空間単位のリソース制限を強制します。送信されたジョブが名前空間のクォータを超えるリソースを要求すると、ACK クラスターはそのジョブを即座に拒否します。これにより、あるユーザーグループが他のグループに割り当てられたリソースを消費することを防ぎます。
本例のクォータは、「リソース割り当て」表に示すリソース割り当てを反映しています:
-
dev1:GPU 1 台、CPU およびメモリは無制限。dev1 のビジネスポリシーでは CPU およびメモリの使用量に制限がないため、Bob にとっての拘束条件は GPU クォータのみです。
-
dev2:GPU 2 台、CPU 8 コア、メモリ 60 GiB。dev2 のビジネスポリシーでは、厳格なリソース制限が適用されます。Tom はジョブ送信時に CPU およびメモリを明示的に要求する必要があります。そうでない場合、ACK クラスターはその要求を拒否します。GPU 2 台のクォータは、dev2 が同時に実行できる GPU 必須ジョブの最大数が 2 であることを意味します。
dev1_quota.yaml を作成します:
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev1-compute-resources
namespace: dev1
spec:
hard:
requests.cpu: "10"
requests.memory: 10Gi
limits.cpu: "15"
limits.memory: 20Gi
requests.nvidia.com/gpu: 2
dev2_quota.yaml を作成します:
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev2-compute-resources
namespace: dev2
spec:
hard:
requests.nvidia.com/gpu: 2
両方のクォータファイルを適用します:
kubectl apply -f dev1_quota.yaml
kubectl apply -f dev2_quota.yaml
クォータが正しく適用されているかを確認し、現在の使用状況を照会します:
# dev1 および dev2 のクォータを照会します。
kubectl get resourcequotas -n dev1
kubectl get resourcequotas -n dev2
# 詳細な使用状況を照会します。
kubectl describe resourcequotas dev1-compute-resources -n dev1
kubectl describe resourcequotas dev2-compute-resources -n dev2
期待される出力:
NAME AGE REQUEST LIMIT
dev1-compute-resources 9s requests.cpu: 0/10, requests.memory: 0/10Gi, requests.nvidia.com/gpu: 0/2 limits.cpu: 0/15, limits.memory: 0/20Gi
NAME AGE REQUEST LIMIT
dev2-compute-resources 10s requests.nvidia.com/gpu: 0/2
Name: dev1-compute-resources
Namespace: dev1
Resource Used Hard
-------- ---- ----
limits.cpu 0 15
limits.memory 0 20Gi
requests.cpu 0 10
requests.memory 0 10Gi
requests.nvidia.com/gpu 0 2
Name: dev2-compute-resources
Namespace: dev2
Resource Used Hard
-------- ---- ----
requests.nvidia.com/gpu 0 2
タスク 4 が完了しました。
ステップ 5:共有ストレージ用の NAS ボリュームの作成
以下の 2 種類の共有ボリュームを作成します:
-
グループ専用ボリューム:特定のグループ内のユーザーのみがアクセス可能(Bob 向けの
dev1-public、Tom 向けのdev2-public) -
グループ横断ボリューム:両方のグループのユーザーがアクセス可能(
department1-public-dev1およびdepartment1-public-dev2を同一 NAS ディレクトリにマウント)
NAS ファイルシステムの作成
NAS コンソール にログインし、NAS ファイルシステムを作成してマウントポイントを追加します。詳細については、「共有 NAS ボリュームの構成」をご参照ください。Alibaba Cloud NAS コンソール
永続ボリュームおよび永続ボリューム要求の作成
-
4 つの永続ボリューム(PV)—
dev1-public、dev2-public、department1-public-dev1、およびdepartment1-public-dev2— を作成します。department1-public-dev1およびdepartment1-public-dev2を同一 NAS ディレクトリにマウントすることで、両方のグループが共有データにアクセスできるようにします。dev1-publicおよびdev2-publicは別々のディレクトリにマウントします。詳細については、「静的プロビジョニング NAS ボリュームのマウント」をご参照ください。直前のステップで追加したマウントポイントを選択してください。

-
各 PV に対して 1 つずつ永続ボリューム要求(PVC)を作成します。詳細については、「静的プロビジョニング NAS ボリュームのマウント」をご参照ください。PVC が作成されると、
department1-public-dev1およびdev1-publicが名前空間 dev1 に、department1-public-dev2およびdev2-publicが名前空間 dev2 に表示されます。
ボリューム構成の検証
root ユーザーとしてクライアントにログインし、以下のコマンドを実行します:
# dev1 で利用可能なボリュームを照会します。
arena data list -n dev1
# dev2 で利用可能なボリュームを照会します。
arena data list -n dev2期待される出力:
すべてのタスクが完了しました。
ステップ 6:Bob および Tom として Arena を実行
Bob のアカウント
-
Bob としてクライアントにログインし、利用可能なボリュームを照会します:
ssh bob@39.98.xxx.xx arena data list期待される出力:

-
GPU 1 台を必要とするトレーニングジョブを送信します:
arena submit tf \ --name=tf-git-bob-01 \ --gpus=1 \ --image=tensorflow/tensorflow:1.5.0-devel-gpu \ --sync-mode=git \ --sync-source=https://code.aliyun.com/xiaozhou/tensorflow-sample-code.git \ "python code/tensorflow-sample-code/tfjob/docker/mnist/main.py --max_steps 10000 --data_dir=code/tensorflow-sample-code/data" -
Bob が送信したすべてのジョブを一覧表示します:
arena list期待される出力:

-
GPU 1 台を要求する第 2 のジョブを送信します:
arena submit tf \ --name=tf-git-bob-02 \ --gpus=1 \ --image=tensorflow/tensorflow:1.5.0-devel-gpu \ --sync-mode=git \ --sync-source=https://code.aliyun.com/xiaozhou/tensorflow-sample-code.git \ "python code/tensorflow-sample-code/tfjob/docker/mnist/main.py --max_steps 10000 --data_dir=code/tensorflow-sample-code/data"dev1 のクォータは GPU 1 個のみで、すでに最初のジョブが占有しています。クラスター全体ではまだ空き GPU がありますが、ACK クラスターは 2 番目のジョブを一時停止します。

クォータが使い切られていることを確認するには、次のコマンドを実行します。kubectl describe resourcequotas dev1-compute-resources -n dev1
Tom のアカウント
-
Tom としてクライアントにログインし、利用可能なボリュームを照会します:
ssh tom@39.98.xx.xx arena data list期待される出力:

-
Tom が送信したすべてのジョブを一覧表示します:
arena listTom は Bob が送信したジョブを参照できません。名前空間によるアイソレーションにより、グループ間のジョブ可視性が防止されています。

-
トレーニングジョブを送信します。dev2 には CPU およびメモリのクォータがあるため、送信時にこれらを明示的に指定する必要があります:
arena submit tf \ --name=tf-git-tom-01 \ --gpus=1 \ --chief-cpu=2 \ --chief-memory=10Gi \ --image=tensorflow/tensorflow:1.5.0-devel-gpu \ --sync-mode=git \ --sync-source=https://code.aliyun.com/xiaozhou/tensorflow-sample-code.git \ "python code/tensorflow-sample-code/tfjob/docker/mnist/main.py --max_steps 10000 --data_dir=code/tensorflow-sample-code/data" -
第 2 のジョブを送信します:
arena submit tf \ --name=tf-git-tom-02 \ --gpus=1 \ --chief-cpu=2 \ --chief-memory=10Gi \ --image=tensorflow/tensorflow:1.5.0-devel-gpu \ --sync-mode=git \ --sync-source=https://code.aliyun.com/xiaozhou/tensorflow-sample-code.git \ "python code/tensorflow-sample-code/tfjob/docker/mnist/main.py --max_steps 10000 --data_dir=code/tensorflow-sample-code/data" -
Tom が送信したすべてのジョブを一覧表示します:
arena list期待される出力:Tom の dev2 グループには GPU 2 台のクォータがあるため、両方のジョブが実行されます。

結果
本セットアップにより、Arena を用いたエンドツーエンドのマルチテナントアイソレーションが実現されます:
-
Bob および Tom はそれぞれ独自のアカウントでログインし、別々の環境で Arena を実行します。
-
各ユーザーは自身のジョブのみを参照・管理できます。
-
リソースクォータにより、あるユーザーグループが他のグループに割り当てられたリソースを消費することを防ぎます。
-
共有ボリュームにより、各グループはグループ専用データおよびグループ横断の共有データにアクセスできます。