|
1 | 1 | #!/usr/bin/env bash |
2 | 2 | # Set up single-node K3s with NVIDIA GPU scheduling on a Lambda Cloud instance. |
3 | | -# Lambda Stack preinstalls the NVIDIA driver and Container Toolkit, so this |
4 | | -# script only installs K3s, points containerd at the NVIDIA runtime, registers |
5 | | -# the RuntimeClass, and deploys the device plugin. Run once on a fresh instance. |
| 3 | +# Lambda Stack preinstalls the NVIDIA driver, so we keep it (driver.enabled=false) |
| 4 | +# and let the NVIDIA GPU Operator manage the container toolkit and configure |
| 5 | +# K3s's containerd at its non-standard paths. This is more robust than hand- |
| 6 | +# editing the containerd template, which is version-sensitive on K3s. Run once. |
6 | 7 | set -euo pipefail |
7 | 8 |
|
| 9 | +export KUBECONFIG=/etc/rancher/k3s/k3s.yaml |
| 10 | + |
8 | 11 | echo "==> [1/4] Installing K3s (single node)" |
9 | 12 | curl -sfL https://get.k3s.io | sh - |
10 | | -# Make kubectl usable without sudo for the rest of the script. |
11 | | -export KUBECONFIG=/etc/rancher/k3s/k3s.yaml |
12 | 13 | sudo chmod 644 /etc/rancher/k3s/k3s.yaml |
13 | | -kubectl wait --for=condition=Ready node --all --timeout=120s |
14 | | -echo " K3s ready." |
| 14 | +# Wait for the node to register AND become Ready (the API may not be up the |
| 15 | +# instant the installer returns, so retry instead of failing fast). |
| 16 | +for i in $(seq 1 30); do |
| 17 | + if kubectl get nodes 2>/dev/null | grep -q ' Ready '; then |
| 18 | + echo " K3s node Ready." |
| 19 | + break |
| 20 | + fi |
| 21 | + echo " [$i] waiting for K3s node..." |
| 22 | + sleep 4 |
| 23 | +done |
15 | 24 |
|
16 | | -echo "==> [2/4] Configuring K3s containerd for the NVIDIA runtime" |
17 | | -# K3s ships its own containerd. Recent K3s autodetects the NVIDIA runtime when |
18 | | -# the Container Toolkit is present, generating an "nvidia" runtime in its |
19 | | -# containerd config. Restart K3s so detection runs now that the toolkit is here. |
20 | | -sudo systemctl restart k3s |
21 | | -sleep 10 |
22 | | -kubectl wait --for=condition=Ready node --all --timeout=120s |
23 | | -# Verify the nvidia runtime was registered in K3s containerd config. |
24 | | -if sudo grep -q 'nvidia' /var/lib/rancher/k3s/agent/etc/containerd/config.toml*; then |
25 | | - echo " NVIDIA runtime detected in K3s containerd config." |
26 | | -else |
27 | | - echo " WARNING: nvidia runtime not found in containerd config; GPU pods may fail." |
| 25 | +echo "==> [2/4] Installing Helm" |
| 26 | +if ! command -v helm >/dev/null 2>&1; then |
| 27 | + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash |
28 | 28 | fi |
| 29 | +helm version |
29 | 30 |
|
30 | | -echo "==> [3/4] Registering the nvidia RuntimeClass" |
31 | | -kubectl apply -f - <<'RTC' |
32 | | -apiVersion: node.k8s.io/v1 |
33 | | -kind: RuntimeClass |
34 | | -metadata: |
35 | | - name: nvidia |
36 | | -handler: nvidia |
37 | | -RTC |
38 | | -echo " RuntimeClass 'nvidia' applied." |
39 | | - |
40 | | -echo "==> [4/4] Deploying the NVIDIA device plugin" |
41 | | -kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.1/deployments/static/nvidia-device-plugin.yml |
42 | | -kubectl -n kube-system rollout status daemonset/nvidia-device-plugin-daemonset --timeout=120s |
| 31 | +echo "==> [3/4] Installing the NVIDIA GPU Operator (driver from Lambda Stack)" |
| 32 | +helm repo add nvidia https://helm.ngc.nvidia.com/nvidia |
| 33 | +helm repo update |
| 34 | +# driver.enabled=false -> keep Lambda's preinstalled driver. |
| 35 | +# The toolkit envs point the operator at K3s's non-standard containerd paths; |
| 36 | +# without these the runtime is written to the wrong place and never registers. |
| 37 | +helm install --wait gpu-operator nvidia/gpu-operator \ |
| 38 | + -n gpu-operator --create-namespace \ |
| 39 | + --set driver.enabled=false \ |
| 40 | + --set toolkit.env[0].name=CONTAINERD_CONFIG \ |
| 41 | + --set toolkit.env[0].value=/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl \ |
| 42 | + --set toolkit.env[1].name=CONTAINERD_SOCKET \ |
| 43 | + --set toolkit.env[1].value=/run/k3s/containerd/containerd.sock \ |
| 44 | + --set toolkit.env[2].name=CONTAINERD_RUNTIME_CLASS \ |
| 45 | + --set toolkit.env[2].value=nvidia \ |
| 46 | + --set toolkit.env[3].name=CONTAINERD_SET_AS_DEFAULT \ |
| 47 | + --set-string toolkit.env[3].value=true |
| 48 | +echo " GPU Operator installed." |
43 | 49 |
|
44 | | -echo "==> Verifying GPU is advertised to the scheduler" |
45 | | -for i in $(seq 1 12); do |
| 50 | +echo "==> [4/4] Verifying GPU is advertised to the scheduler" |
| 51 | +# The operator's toolkit pod reconfigures containerd and restarts it, so the |
| 52 | +# node may briefly go NotReady; allow generous time for nvidia.com/gpu to appear. |
| 53 | +for i in $(seq 1 36); do |
46 | 54 | gpu=$(kubectl get nodes -o jsonpath='{.items[0].status.allocatable.nvidia\.com/gpu}' 2>/dev/null || true) |
47 | 55 | if [ -n "${gpu:-}" ] && [ "$gpu" != "0" ]; then |
48 | 56 | echo " Node advertises nvidia.com/gpu=${gpu}. Cluster is GPU-ready." |
49 | 57 | exit 0 |
50 | 58 | fi |
51 | | - echo " [$i] waiting for GPU to be advertised..." |
52 | | - sleep 5 |
| 59 | + echo " [$i] waiting for nvidia.com/gpu (operator configuring containerd)..." |
| 60 | + sleep 10 |
53 | 61 | done |
54 | | -echo " ERROR: no GPU advertised after 60s. Check device plugin logs:" |
55 | | -echo " kubectl -n kube-system logs daemonset/nvidia-device-plugin-daemonset" |
| 62 | +echo " ERROR: no GPU advertised after ~6min. Inspect the operator:" |
| 63 | +echo " kubectl -n gpu-operator get pods" |
| 64 | +echo " kubectl -n gpu-operator logs -l app=nvidia-container-toolkit-daemonset" |
56 | 65 | exit 1 |
0 commit comments