Getting started with Kubernetes
Creating your first pod template within a job yaml file
To access the GPUs on the service, it is recommended to start with one of the prebuilt container images provided by Nvidia, these images are intended to perform different tasks using Nvidia GPUs.
The list of Nvidia images is available on their website.
The following example uses their CUDA sample code simulating nbody interactions.
apiVersion: batch/v1
kind: Job
metadata:
generateName: jobtest-
labels:
kueue.x-k8s.io/queue-name: <tre-project-namespace>-user-queue
spec:
completions: 1
backoffLimit: 1
ttlSecondsAfterFinished: 1800
template:
metadata:
name: job-test
spec:
containers:
- name: cudasample
image: tre-ghcr-proxy.nsh.loc:5000/<github_user>/cuda-sample:nbody-cuda11.7.1
args: ["-benchmark", "-numbodies=512000", "-fp64", "-fullscreen"]
resources:
requests:
cpu: 2
memory: '1Gi'
limits:
cpu: 2
memory: '4Gi'
nvidia.com/gpu: 1
restartPolicy: Never
The pod resources are defined under the resources tags using the requests and limits tags.
Resources defined under the requests tags are the reserved resources required for the pod to be scheduled.
If a pod is assigned to a node with unused resources then it may spontaneously burst up to use resources beyond those requested, and up to the limits. This may allow the task within the pod to run faster, but it will also throttle back down when further pods are scheduled to the node.
The SHS GPU Cluster requires that all pods have requests and limits tags for CPU and memory defined in order to be accepted.
GPU resources requests are optional and only an entry under the limits tag is needed to specify the use of a GPU, nvidia.com/gpu: 1. Without this no GPU will be available to the pod.
The label kueue.x-k8s.io/queue-name specifies the queue you are submitting your job to. This is part of the Kueue system in operation on the Cluster, to allow for improved resource management for users.
Submitting your first job
- Open an editor of your choice and create the file test_NBody.yml
- Copy the above job yaml in to the file, filling in
<tre-project-namespace>-user-queue, e.g. nsh-2024-0000-ns-user-queue: - Save the file and exit the editor
- Run
kubectl -n <tre-project-namespace> create -f test_NBody.yml -
This will output something like the following:
job.batch/jobtest-b92qg createdThe five character code appended to the job name, i.e.
b92qgabove, is randomly generated and will differ from your run. -
Run
kubectl -n <tre-project-namespace> get jobs -
This will output something like the following:
NAME COMPLETIONS DURATION AGE jobtest-b92qg 1/1 48s 29mThere may be more than one entry, as this displays all the jobs in the current namespace, listing their name, number of completions against required completions, duration and age.
-
Inspect your job further using the command
kubectl -n <tre-project-namespace> describe job jobtest-b92qg, updating the job name with your five character code. -
This will output something like the following:
Name: jobtest-b92qg Namespace: t4 Selector: controller-uid=d3233fee-794e-466f-9655-1fe32d1f06d3 Labels: kueue.x-k8s.io/queue-name=t4-user-queue Annotations: batch.kubernetes.io/job-tracking: Parallelism: 1 Completions: 3 Completion Mode: NonIndexed Start Time: Wed, 14 Feb 2024 14:07:44 +0000 Completed At: Wed, 14 Feb 2024 14:08:32 +0000 Duration: 48s Pods Statuses: 0 Active (0 Ready) / 3 Succeeded / 0 Failed Pod Template: Labels: controller-uid=d3233fee-794e-466f-9655-1fe32d1f06d3 job-name=jobtest-b92qg Containers: cudasample: Image: tre-ghcr-proxy.nsh.loc:5000/<github_user>/cuda-sample:nbody-cuda11.7.1 Port: <none> Host Port: <none> Args: -benchmark -numbodies=512000 -fp64 -fullscreen Limits: cpu: 2 memory: 4Gi nvidia.com/gpu: 1 Requests: cpu: 2 memory: 1Gi Environment: <none> Mounts: <none> Volumes: <none> Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Suspended 8m1s job-controller Job suspended Normal CreatedWorkload 8m1s batch/job-kueue-controller Created Workload: t4/job-jobtest-b92qg-3b890 Normal Started 8m1s batch/job-kueue-controller Admitted by clusterQueue project-cq Normal SuccessfulCreate 8m job-controller Created pod: jobtest-b92qg-lh64s Normal Resumed 8m job-controller Job resumed Normal SuccessfulCreate 7m44s job-controller Created pod: jobtest-b92qg-xhvdm Normal SuccessfulCreate 7m28s job-controller Created pod: jobtest-b92qg-lvmrf Normal Completed 7m12s job-controller Job completed -
Run
kubectl -n <tre-project-namespace> get pods -
This will output something like the following:
NAME READY STATUS RESTARTS AGE jobtest-b92qg-lh64s 0/1 Completed 0 11mAgain, there may be more than one entry as this displays all the jobs in the current namespace. Also, each pod within a job is given another unique five-character code appended to the job name.
-
View the logs of a pod from the job you ran
kubectl -n <tre-project-namespace> logs jobtest-b92qg-lh64s- again update with you run's pod and job five-letter code. -
This will output something like the following:
Run "nbody -benchmark [-numbodies=<numBodies>]" to measure performance. -fullscreen (run n-body simulation in fullscreen mode) -fp64 (use double precision floating point values for simulation) -hostmem (stores simulation data in host memory) -benchmark (run benchmark to measure performance) -numbodies=<N> (number of bodies (>= 1) to run in simulation) -device=<d> (where d=0,1,2.... for the CUDA device to use) -numdevices=<i> (where i=(number of CUDA devices > 0) to use for simulation) -compare (compares simulation results running once on the default GPU and once on the CPU) -cpu (run n-body simulation on the CPU) -tipsy=<file.bin> (load a tipsy model file for simulation) NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled. > Fullscreen mode > Simulation data stored in video memory > Double precision floating point simulation > 1 Devices used for simulation GPU Device 0: "Ampere" with compute capability 8.0 > Compute 8.0 CUDA device: [NVIDIA A100-SXM4-40GB] number of bodies = 512000 512000 bodies, total time for 10 iterations: 10570.778 ms = 247.989 billion interactions per second = 7439.679 double-precision GFLOP/s at 30 flops per interaction -
Delete your job with
kubectl -n <tre-project-namespace> delete job jobtest-b92qg- this will delete the associated pods as well.
Specifying GPU requirements
At present, the SHS GPU Cluster only provides one type of GPU:[NVIDIA A100-SXM4-40GB].
If you create multiple jobs with the same definition file and compare their log files you may notice the CUDA device may differ from Compute 8.0 CUDA device: [NVIDIA A100-SXM4-40GB].
The GPU Operator on K8s is allocating the pod to the first node with a GPU free that matches the other resource specifications irrespective of the type of GPU present on the node.
For future reference (if additional GPU-types become available), you can make the GPU resource request more specific by adding a node selector to your pod template, as per the following example:
nvidia.com/gpu.product: 'NVIDIA-A100-SXM4-40GB'
Example yaml file with GPU type specified
The nodeSelector: key at the bottom of the pod template states the pod should be ran on a node with a NVIDIA-A100-SXM4-40GB GPU.
Exact GPU product names only
K8s will fail to assign the pod if you misspell the GPU type.
Be especially careful with memory allocation, as attempting to load GPUs with more data than its memory can handle can have unexpected consequences.
apiVersion: batch/v1
kind: Job
metadata:
generateName: jobtest-
labels:
kueue.x-k8s.io/queue-name: <tre-project-namespace>-user-queue
spec:
completions: 1
backoffLimit: 1
ttlSecondsAfterFinished: 1800
template:
metadata:
name: job-test
spec:
containers:
- name: cudasample
image: tre-ghcr-proxy.nsh.loc:5000/<github_user>/cuda-sample:nbody-cuda11.7.1
args: ["-benchmark", "-numbodies=512000", "-fp64", "-fullscreen"]
resources:
requests:
cpu: 2
memory: '1Gi'
limits:
cpu: 2
memory: '4Gi'
nvidia.com/gpu: 1
restartPolicy: Never
nodeSelector:
nvidia.com/gpu.product: NVIDIA-A100-SXM4-40GB
Running multiple pods with K8s jobs
Wrapping a pod within a job provides additional functionality on top of accessing the queuing system.
Firstly, the restartPolicy within a job enables the self-healing mechanism within K8s so that if a node dies with the job's pod on it, then the job will find a new node to automatically restart the pod.
Jobs also allow users to define multiple pods that can run in parallel or series, and will continue to spawn pods until a specific number of pods successfully terminate.
See below for an example K8s job that requires three pods to successfully complete the example CUDA code before the job itself ends.
apiVersion: batch/v1
kind: Job
metadata:
generateName: jobtest-
labels:
kueue.x-k8s.io/queue-name: <tre-project-namespace>-user-queue
spec:
completions: 1
backoffLimit: 1
ttlSecondsAfterFinished: 1800
parallelism: 1
template:
metadata:
name: job-test
spec:
containers:
- name: cudasample
image: tre-ghcr-proxy.nsh.loc:5000/<github_user>/cuda-sample:nbody-cuda11.7.1
args: ["-benchmark", "-numbodies=512000", "-fp64", "-fullscreen"]
resources:
requests:
cpu: 2
memory: '1Gi'
limits:
cpu: 2
memory: '4Gi'
nvidia.com/gpu: 1
restartPolicy: Never
Change the default kubectl namespace in the project kubeconfig file
Passing the -n <tre-project-namespace> flag every time you want to interact with the cluster can be cumbersome.
You can alter the kubeconfig on your VM to send commands to your project namespace by default. Pleaase follow the below instructions to achieve that:
-
Open the command line on your SHS VM with access to the SHS GPU Cluster.
-
Open the kubeconfig file.
vi ~/.kube/config -
Add the namespace line with your project's kubernetes namespace to the "shs-gpu-cluster" context entry in your copy of the config file.
*** MORE CONFIG *** contexts: - name: "shs-gpu-cluster" context: user: "shs-gpu-cluster" namespace: "<tre-project-namespace>" # INSERT LINE cluster: "shs-gpu-cluster" *** MORE CONFIG *** -
Check kubectl connects to the Cluster. If this does not work contact your dedicated helpdesk.
kubectl get pods