This topic walks through a complete setup for running Arena in a shared GPU cluster with multiple internal teams. It covers user isolation, role-based access control (RBAC), resource quotas, and shared storage.
This setup uses namespace-based soft isolation and is suited for trusted tenants — internal teams or departments sharing a single ACK cluster. If you need cluster-level isolation between external organizations, use separate ACK clusters instead.
How this setup works
When multiple developers share a GPU cluster, you need a way to divide them into user groups, enforce per-group resource limits, and control job visibility. This setup maps three layers together:
-
Linux user group → Kubernetes namespace → Arena user group
Each layer enforces a different aspect of isolation. The Linux layer controls who can log in and which environment they run in. The namespace layer isolates Kubernetes resources. The Arena layer ensures each user sees only their own jobs.
This setup covers the following five tasks:
-
Task 1: Create user groups dev1 and dev2, and add Bob to dev1 and Tom to dev2.
-
Task 2: Allow Bob and Tom to log in to the client only with their own accounts, each in a separate Arena environment.
-
Task 3: Grant Bob and Tom permission to view and manage only the jobs they submit.
-
Task 4: Allocate GPU, CPU, and memory resources to each user group.
-
Task 5: Create shared volumes accessible within a group only, and shared volumes accessible across groups.
Resource allocation
| User group | User | GPU | CPU | Memory | Shared volumes |
|---|---|---|---|---|---|
| dev1 | Bob | 1 | Unlimited | Unlimited | dev1-public and department1-public-dev1 |
| dev2 | Tom | 2 | 8 cores | 60 GiB | dev2-public and department1-public-dev2 |
department1-public-dev1 and department1-public-dev2 are mounted to the same directory on the Apsara File Storage NAS (NAS) file system, so users in both groups can access that data. dev1-public and dev2-public are mounted to separate directories and are accessible only to Bob and Tom respectively.
Example cluster
| Hostname | Role | IP address | GPUs | CPU cores | Memory |
|---|---|---|---|---|---|
| client01 | Client | 10.0.0.97 (private), 39.98.xxx.xxx (public) | 0 | 2 | 8 GiB |
| master01 | Master | 10.0.0.91 (private) | 0 | 4 | 8 GiB |
| master02 | Master | 10.0.0.92 (private) | 0 | 4 | 8 GiB |
| master03 | Master | 10.0.0.93 (private) | 0 | 4 | 8 GiB |
| worker01 | Worker | 10.0.0.94 (private) | 1 | 4 | 30 GiB |
| worker02 | Worker | 10.0.0.95 (private) | 1 | 4 | 30 GiB |
| worker03 | Worker | 10.0.0.96 (private) | 1 | 4 | 30 GiB |
All operations in this topic are performed using an administrator account on the client, unless otherwise stated.
Prerequisites
Before you begin, ensure that you have:
-
An ACK cluster. For more information, see Create an ACK managed cluster.
-
An Elastic Compute Service (ECS) instance running Linux, created in the same virtual private cloud (VPC) as the ACK cluster. This instance serves as the client (Arena workstation) for submitting jobs. For more information, see Create an instance on the Custom Launch tab.
-
The latest version of the Arena client installed. For more information, see Configure the Arena client.
Step 1: Create users and user groups
Do not install or run Arena directly on a master node. Use the ECS client instead, and connect it to the ACK cluster via a kubeconfig file.
Create Linux users and groups
Create user IDs (UIDs) and group IDs (GIDs) for Bob and Tom on the client. The Linux account system enforces Task 2: each user can only log in with their own credentials and runs Arena in their own environment.
# Create Linux groups: dev1 and dev2.
groupadd -g 10001 dev1
groupadd -g 10002 dev2
# Create Linux users: Bob and Tom.
adduser -u 20001 -s /bin/bash -G dev1 -m bob
adduser -u 20002 -s /bin/bash -G dev2 -m tom
# Set passwords.
passwd bob
passwd tom
Create Kubernetes namespaces and service accounts
After jobs are submitted, they run in the ACK cluster. Each Linux user group maps to a Kubernetes namespace, and each Linux user maps to a service account within that namespace.
Log in to the client as the root user and run the following commands. For prerequisites on connecting kubectl to the ACK cluster, see Obtain the kubeconfig file of a cluster and use kubectl to connect to the cluster.
kubectl version 1.10 or later is required.
# Create namespaces for dev1 and dev2.
kubectl create namespace dev1
kubectl create namespace dev2
# Create service accounts for Bob and Tom.
kubectl create serviceaccount bob -n dev1
kubectl create serviceaccount tom -n dev2
Expected output:
namespace/dev1 created
namespace/dev2 created
serviceaccount/bob created
serviceaccount/tom created
Step 2: Configure Arena for each user
Install Arena
Install Arena once on the client. Log in as the root user, download the latest community release package, decompress it, and run install.sh. For more information, see Configure the Arena client.
Generate kubeconfig files
Each user needs their own kubeconfig file to access the ACK cluster with a scoped identity. Create a script named generate-kubeconfig.sh with the following content:
#!/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 "$@"
Generate kubeconfig files for Bob and Tom. The following commands set the token expiration to 720 hours — adjust this value based on your security policy.
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
Expected output:
Saved kubeconfig to "/home/bob/.kube/config".
Saved kubeconfig to "/home/tom/.kube/config".
After this step, Tasks 1 and 2 are complete. Bob and Tom can each log in to the client and run Arena in their own isolated environment.
Step 3: Configure RBAC permissions
Create roles
Create roles in the dev1 and dev2 namespaces. Bob and Tom are granted the minimum permissions needed to view and manage only their own jobs (Task 3).
The following table summarizes the permissions granted to each user:
| Resource type | Bob (dev1) | Tom (dev2) |
|---|---|---|
| pods, pods/log, services | Full access | Full access |
| deployments, ReplicaSets | Full access | Full access |
| ConfigMaps | Full access | Full access |
| kubeflow.org resources | Full access | Full access |
| batch jobs | Full access | Full access |
| persistentvolumeclaims, events, services/proxy | Read only | Read only |
| Cluster-wide: pods, nodes, services, persistentvolumes | Read only | Read only |
Create dev1_roles.yaml for user group dev1:
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:
- '*'
Create dev2_roles.yaml for user group dev2:
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:
- '*'
Apply both role definition files:
kubectl apply -f dev1_roles.yaml
kubectl apply -f dev2_roles.yaml
Expected output:
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
Verify the roles were created:
kubectl get role -n dev1
kubectl get role -n dev2
Expected output:
NAME CREATED AT
arena 2024-09-14T08:25:34Z
NAME CREATED AT
arena 2024-09-14T08:25:39Z
Bind roles to users
Create 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
Create 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
Apply both files:
kubectl apply -f bob_rolebindings.yaml
kubectl apply -f tom_rolebindings.yaml
Expected output:
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
Verify the role bindings:
kubectl get rolebinding -n dev1
kubectl get rolebinding -n dev2
Expected output:
NAME ROLE AGE
bob-arena Role/arena 34s
NAME ROLE AGE
tom-arena Role/arena 33s
Tasks 1–3 are now complete.
Step 4: Set resource quotas
Kubernetes ResourceQuota objects enforce per-namespace resource limits. When a submitted job requests more resources than the namespace quota allows, the ACK cluster rejects the job immediately. This prevents one user group from consuming resources allocated to another.
The quotas in this example reflect the resource allocation in the Resource allocation table:
-
dev1: 1 GPU, unlimited CPU and memory. The business policy for dev1 allows unrestricted CPU and memory usage, so only the GPU quota is the binding constraint for Bob.
-
dev2: 2 GPUs, 8 CPU cores, and 60 GiB of memory. The business policy for dev2 enforces strict resource bounds. Tom must explicitly request CPU and memory when submitting jobs, or the ACK cluster rejects the request. A quota of 2 GPUs means dev2 can run at most two GPU-requiring jobs simultaneously.
Create 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
Create dev2_quota.yaml:
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev2-compute-resources
namespace: dev2
spec:
hard:
requests.nvidia.com/gpu: 2
Apply both quota files:
kubectl apply -f dev1_quota.yaml
kubectl apply -f dev2_quota.yaml
Verify that the quotas are applied and check current usage:
# Query quotas in dev1 and dev2.
kubectl get resourcequotas -n dev1
kubectl get resourcequotas -n dev2
# Query detailed usage.
kubectl describe resourcequotas dev1-compute-resources -n dev1
kubectl describe resourcequotas dev2-compute-resources -n dev2
Expected output:
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
Task 4 is now complete.
Step 5: Create NAS volumes for shared storage
Create two types of shared volumes:
-
Group-private volumes: accessible only to users within a specific group (
dev1-publicfor Bob,dev2-publicfor Tom) -
Cross-group volumes: accessible to users in both groups (
department1-public-dev1anddepartment1-public-dev2, which are mounted to the same NAS directory)
Create a NAS file system
Log in to the NAS console, create a NAS file system, and add a mount target. For more information, see Configure a shared NAS volume.Alibaba Cloud NAS console
Create persistent volumes and claims
-
Create four persistent volumes (PVs) —
dev1-public,dev2-public,department1-public-dev1, anddepartment1-public-dev2. Mountdepartment1-public-dev1anddepartment1-public-dev2to the same NAS directory so both groups can access the shared data. Mountdev1-publicanddev2-publicto separate directories. For more information, see Mount a statically provisioned NAS volume.Select the mount target you added in the previous step.

-
Create one persistent volume claim (PVC) for each PV. For more information, see Mount a statically provisioned NAS volume. After the PVCs are created,
department1-public-dev1anddev1-publicappear in namespace dev1, anddepartment1-public-dev2anddev2-publicappear in namespace dev2.
Verify volume configuration
Log in to the client as the root user and run:
# Query volumes available to dev1.
arena data list -n dev1
# Query volumes available to dev2.
arena data list -n dev2
Expected output:
All five tasks are now complete.
Step 6: Run Arena as Bob and Tom
Bob's account
-
Log in to the client as Bob and query available volumes:
ssh bob@39.98.xxx.xx arena data listExpected output:

-
Submit a training job that requires one GPU:
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" -
List all jobs submitted by Bob:
arena listExpected output:

-
Submit a second job requesting one GPU:
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 has a quota of only 1 GPU, which the first job already occupies. The ACK cluster suspends the second job even though the cluster overall still has free GPUs.

To confirm the quota is exhausted, run:kubectl describe resourcequotas dev1-compute-resources -n dev1
Tom's account
-
Log in to the client as Tom and query available volumes:
ssh tom@39.98.xx.xx arena data listExpected output:

-
List all jobs submitted by Tom:
arena listTom cannot see the jobs submitted by Bob — namespace isolation prevents cross-group job visibility.

-
Submit a training job. Because dev2 has CPU and memory quotas, specify both when submitting:
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" -
Submit a second job:
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" -
List all jobs submitted by Tom:
arena listExpected output: Tom's dev2 group has a quota of 2 GPUs, so both jobs run.

Results
The setup demonstrates end-to-end multi-tenant isolation with Arena:
-
Bob and Tom each log in with their own accounts and run Arena in separate environments.
-
Each user sees and manages only their own jobs.
-
Resource quotas prevent one user group from consuming resources allocated to another.
-
Shared volumes give each group access to both group-private data and cross-group shared data.