[應用][node label] NodeFeatureDiscovery (NFD) === ###### tags: `K8s / app` ###### tags: `Kubernetes`, `k8s`, `app`, `node`, `label`, `NodeFeatureDiscovery`, `NFD` <br> [TOC] <br> ## 官方文件 > https://kubernetes-sigs.github.io/node-feature-discovery/master/deployment/helm.html - To install the latest stable version ```bash= export NFD_NS=node-feature-discovery helm repo add nfd https://kubernetes-sigs.github.io/node-feature-discovery/charts helm repo update helm install nfd/node-feature-discovery \ --namespace $NFD_NS --create-namespace --generate-name ``` <br> ## label ### `feature.node.kubernetes.io/pci-10de.present: "true"` - ### gpt-o4-mini-high (2025/05/23) 這個 Label 其實是由 **Node Feature Discovery (NFD)** 所貼,用來標示這台節點有 **NVIDIA**(PCI vendor ID `0x10de`)的裝置可供 Kubernetes 排程: - `feature.node.kubernetes.io/pci-10de.present=true` - **feature.node.kubernetes.io**:表示這是 NFD 所貼的硬體特性標籤。 - **pci-10de**:`10de` 是 NVIDIA 在 PCI 規範中的廠商編號(vendor ID)。 - **present=true**:表示此節點「偵測到」符合這個條件(有 NVIDIA GPU)並貼上此標籤。 當你部署了 GPU Operator 或 Soperator 並想要在 SlurmCluster 中排程 GPU 工作時,就會透過這個 Label 來篩選出具備 NVIDIA GPU 的節點。 :contentReference[oaicite:0]{index=0} 若要檢視或驗證,你可以執行: ```bash $ kubectl get nodes -l feature.node.kubernetes.io/pci-10de.present=true NAME STATUS ROLES AGE VERSION mymini Ready control-plane 22h v1.32.0 ``` 若能列出節點,代表 NFD 已成功偵測並標籤該節點。 <br> ## GPT-o4-mini-high (2025/05/21) 安裝 NFD 之後,它會自動掃描節點硬體,並根據你設定的規則打上 Labe ```bash= # 先加入 Helm repo helm repo add nfd https://kubernetes-sigs.github.io/node-feature-discovery/charts helm repo update # 安裝 NFD master + worker helm install nfd nfd/node-feature-discovery \ --namespace nfd-system --create-namespace \ --set nfd.master.enabled=true \ --set nfd.worker.enabled=true \ --set nfd.config.customLabels[0].featureName="nvidia.com/gpu.present" \ --set nfd.config.customLabels[0].labelKey="accelerator" \ --set nfd.config.customLabels[0].labelValue="nvidia" ``` 這段設定會讓 NFD 在偵測到 GPU 存在時,自動加上 accelerator=nvidia。 <br> ## NVIDIA GPU Feature Discovery DaemonSet (GFD) - `nvidia.com/gpu.*` label 來自於 NVIDIA GPU Feature Discovery DaemonSet (GFD) - [About the NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html) <br> ## 打上標籤 - ### 錯誤範例 ``` $ kubectl label node/mymini "nvidia.com/gpu.product=NVIDIA GeForce GTX 1080 Ti" ``` `error: invalid label value: "nvidia.com/gpu.product=NVIDIA GeForce GTX 1080 Ti": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')` - ### 正規化作法 ### 解決 Kubernetes Label 值不合法錯誤 這個錯誤訊息來自於 **Kubernetes Label 的命名規則限制**,你不能直接將 `NVIDIA GeForce GTX 1080 Ti` 當成 label 值,因為它含有空格。 --- ### ✅ 解決方法:請用合法格式轉換後再標籤 - #### 🚫 錯誤範例(含空格、不合法字元): ``` NVIDIA GeForce GTX 1080 Ti ``` - #### ✅ 正確轉換範例(推薦轉為小寫、改成 `-` 或 `_`): ``` nvidia-geforce-gtx-1080-ti ``` --- ### ✅ 正確用法: ```bash kubectl label node mymini nvidia.com/gpu.product=nvidia-geforce-gtx-1080-ti ``` 這樣就符合 Kubernetes label 的正規表達式: ``` (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])? ``` --- ### 🔁 建議:自動轉換 GPU 型號為合法 label 值(bash 範例) ```bash GPU_NAME="NVIDIA GeForce GTX 1080 Ti" LABEL_VALUE=$(echo "$GPU_NAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g') kubectl label node mymini "nvidia.com/gpu.product=${LABEL_VALUE}" ``` 輸出結果: ```bash nvidia.com/gpu.product=nvidia-geforce-gtx-1080-ti ``` <br> ## 在 Ubuntu 上要如何手動查詢 label 值? ### `nvidia.com/gpu.machine` - ### 使用 `dmidecode` ``` # 讀取產品名稱 sudo dmidecode -s system-product-name # 讀取製造商 sudo dmidecode -s system-manufacturer ``` - 結果範例 ``` ESC4000 G3 Series ASUSTeK COMPUTER INC. ``` - `nvidia.com/gpu.machine: ESC4000-G3-Series` - ### 直接讀取 DMI 檔案介面 ```bash # 不需 sudo cat /sys/devices/virtual/dmi/id/product_name # 產品名稱 cat /sys/devices/virtual/dmi/id/sys_vendor # 製造商 ``` - 結果範例 ``` ESC4000 G3 Series ASUSTeK COMPUTER INC. ``` - ### 使用 `lshw` ```bash sudo lshw -class system | grep -E 'product|vendor' ``` - 結果範例 ``` product: ESC4000 G3 Series (All) vendor: ASUSTeK COMPUTER INC. product: PnP device PNP0b00 product: PnP device PNP0c02 product: PnP device PNP0c02 ``` 以上方法均可在 Ubuntu 上快速取得 `nvidia.com/gpu.machine` 所需的機型名稱(如 ESC4000A-E10),您只要將取得的字串填入對應的 Kubernetes label 即可。 <br> ### `nvidia.com/gpu.compute.major` & `nvidia.com/gpu.compute.minor` > - `nvidia.com/gpu.compute.major: "6"` > - `nvidia.com/gpu.compute.minor: "1"` - 這代表的是CUDA專屬的GPU架構版本,像 Pascal 架構(例如 1080 Ti)就是 6.1。要查詢這些數據,可使用像 `nvidia-smi --query-gpu=compute_cap` 的命令,或者使用 CUDA 工具包的deviceQuery範例。也可以查官方的NVIDIA計算能力表格來獲取這些資訊。 - 要查詢顯示計算能力(如 6.1),可以使用 CUDA 工具包中的 deviceQuery 範例或 PyCUDA、Numba 等 Python 庫來取得。您也可以使用 `nvidia-smi --query-gpu=compute_cap` 來查詢(如果支援)。計算能力數值是由GPU的架構版本所決定,像是 Pascal(GTX 1080 Ti)就是 6.1。這些資料也可以參考 NVIDIA 的官方文檔。 <br> <hr> <br> ## 討論 ### 如何根據 GPU 型號與記憶體決定是否套用 ModelFlavor CR - ### Question ``` apiVersion: example.com/v1 kind: ModelFlavor metadata: name: deepseek-r1-distill-qwen-1.5b-vllm-a40-1gpu-32k spec: resourceProfile: a40-1gpu ``` - 目標:對多個 ModelFlavor cr 進行套用 - 前提:要判斷 K8s 沒有該 gpu 類型,沒有就不能套用;有資源才能套用 - ### gpt-o4-mini-high (2025/05/08) 在 Kubernetes 裡,其實預設只有「nvidia.com/gpu」這個 extended resource,代表可調度的「GPU 卡數量」,並不會把 GPU 型號或 VRAM 容量 expose 出來。如果你要在套用 ModelFlavor CR 之前,先檢查叢集裡到底有沒有符合你需求的 GPU 型號/記憶體,就必須額外做一層「偵測+標籤」或「外部查詢再下發」機制。 下面提供三種常見做法,供你選擇或組合: --- ### 方法一.利用 NodeFeatureDiscovery (NFD) 自動標籤 1. **部署 NFD** 安裝 [Node Feature Discovery](https://github.com/kubernetes-sigs/node-feature-discovery) 並啟用 GPU-labeler plugin。它會針對所有 Node 執行 `nvidia-smi`,自動在 Node metadata 上貼上像這樣的 label: ``` feature.node.kubernetes.io/gpu.vendor.nvidia.com=Tesla feature.node.kubernetes.io/gpu.model.nvidia.com=Tesla-A40 feature.node.kubernetes.io/gpu.memory.nvidia.com=48GiB ``` 2. **查詢** - GPU 型號: ``` kubectl get nodes -l feature.node.kubernetes.io/gpu.model.nvidia.com=Tesla-A40 ``` - VRAM(示範大於等於 40GiB 的節點): ``` # 假設 NFD 標籤為 feature.node.kubernetes.io/gpu.memory.nvidia.com=48GiB kubectl get nodes \ -l feature.node.kubernetes.io/gpu.model.nvidia.com=Tesla-A40 \ -l feature.node.kubernetes.io/gpu.memory.nvidia.com=48GiB ``` 3. **條件套用 CR** 在 CI/CD 腳本裡: ``` if kubectl get nodes \ -l feature.node.kubernetes.io/gpu.model.nvidia.com=Tesla-A40 \ -l feature.node.kubernetes.io/gpu.memory.nvidia.com=48GiB \ --no-headers \ | grep -q .; then kubectl apply -f my-modelflavor.yaml else echo "No Tesla-A40 ≥48GiB node found; skip ModelFlavor" fi ``` --- ### 方法二.手動標籤 Node + LabelSelector 如果不想用 NFD,你可以自己建一個 DaemonSet,跑 `nvidia-smi` 然後用 `kubectl label node …`,範例如下: 1. **DaemonSet 偵測** ```yaml= apiVersion: apps/v1 kind: DaemonSet metadata: name: gpu-info-labeler spec: selector: matchLabels: { name: gpu-info-labeler } template: metadata: labels: { name: gpu-info-labeler } spec: tolerations: - operator: Exists containers: - name: detect image: nvidia/cuda:12.1.1-base securityContext: privileged: true command: ["/bin/sh","-c"] args: - | MEM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits) MODEL=$(nvidia-smi --query-gpu=name --format=csv,noheader) # 把節點貼標籤 kubectl label node $(hostname) gpu.model=$MODEL gpu.mem=${MEM}MiB --overwrite sleep 3600 ``` 2. **查詢** ``` # 找出有 A40 且記憶體 ≥ 40000MiB 的節點 kubectl get nodes \ -l gpu.model=Tesla-A40 \ -l 'gpu.mem in (48000MiB,56000MiB,...)' # 或你自己定義的離散等級 ``` --- ### 方法三.透過 Kubernetes API 用程式檢查 以 Python + kubernetes-client 為例,直接在腳本裡做判斷: ```python= from kubernetes import client, config # 載入 kubeconfig config.load_kube_config() v1 = client.CoreV1Api() # 列出所有 Node nodes = v1.list_node().items suitable = [] for node in nodes: labels = node.metadata.labels or {} # 假設你先把 gpu.model、gpu.mem 當 label 寫到 Node model = labels.get("gpu.model") mem = labels.get("gpu.mem") # e.g. "48000MiB" if model == "Tesla-A40" and int(mem.rstrip("MiB")) >= 40000: suitable.append(node.metadata.name) if not suitable: print("No nodes with Tesla-A40 ≥40GiB found; skip applying.") else: # 呼叫 kubectl apply 或用 client 直接建立 CR print("Found nodes:", suitable) # e.g. os.system("kubectl apply -f my-flavor.yaml") ``` --- ### 小結 - **Kubernetes 預設只會 expose GPU 張數**(`nvidia.com/gpu`),**不含型號/記憶體**。 - **要抓型號/VRAM,就得讓 Node 自己「打標籤」**:最簡單的是用 NodeFeatureDiscovery,或手動 DaemonSet + `kubectl label`。 - 標好之後,就可以用 `-l key=value` 這類 Label Selector 機制,在套用 CR 前先做過濾,確保叢集中真的有支援該 GPU profile 的 Node,再 safely apply。 <br> ### DaemonSet 腳本:自動標記 GPU 型號與記憶體至 Node Labels - ### gpt-o4 (2025/05/12) 以下是一個**自動為每個 Node 加上 GPU 型號與記憶體標籤的 DaemonSet 腳本**,它會使用 `nvidia-smi` 取得資訊,並將不合法的型號名稱轉為 Kubernetes 合法的 label 格式(轉為小寫並改為 `-`)。 --- ```yaml= ### DaemonSet:標記 GPU 型號與記憶體到 Node Labels apiVersion: apps/v1 kind: DaemonSet metadata: name: gpu-labeler namespace: kube-system spec: selector: matchLabels: app: gpu-labeler template: metadata: labels: app: gpu-labeler spec: hostNetwork: true hostPID: true tolerations: - operator: "Exists" # 允許在任意節點上執行(包含 taint node) containers: - name: label-gpu image: nvidia/cuda:12.4.0-base-ubuntu20.04 securityContext: privileged: true # 需要權限進行 kubectl label command: - /bin/bash - -c - | apt update && apt install -y curl jq sleep 5 export NODE_NAME=$(hostname) export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) export KUBE_API=https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT} export CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt if ! command -v nvidia-smi >/dev/null; then echo "nvidia-smi not found, skipping..." exit 0 fi MODEL=$(nvidia-smi --query-gpu=name --format=csv,noheader | head -n1) MEMORY=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -n1) # 合法化 label 格式:小寫 + 非字母數字轉為 "-" MODEL_LABEL=$(echo "$MODEL" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g') MEMORY_LABEL=$(echo "$MEMORY" | sed 's/[^0-9]//g') echo "Labeling node: $NODE_NAME with model=$MODEL_LABEL, memory=$MEMORY_LABEL MiB" curl -s --cacert $CACERT --header "Authorization: Bearer $TOKEN" \ -X PATCH \ -H "Content-Type: application/merge-patch+json" \ --data "{\"metadata\":{\"labels\":{\"nvidia.com/gpu.product\":\"$MODEL_LABEL\",\"nvidia.com/gpu.memory\":\"$MEMORY_LABEL\"}}}" \ $KUBE_API/api/v1/nodes/$NODE_NAME sleep 3600 # Prevent container from restarting too often volumeMounts: - name: kube-api-access mountPath: /var/run/secrets/kubernetes.io/serviceaccount readOnly: true restartPolicy: Always volumes: - name: kube-api-access projected: sources: - serviceAccountToken: path: token expirationSeconds: 3600 - configMap: name: kube-root-ca.crt items: - key: ca.crt path: ca.crt --- apiVersion: v1 kind: ServiceAccount metadata: name: gpu-labeler-sa namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: gpu-labeler-role rules: - apiGroups: [""] resources: ["nodes"] verbs: ["get", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: gpu-labeler-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: gpu-labeler-role subjects: - kind: ServiceAccount name: gpu-labeler-sa namespace: kube-system ``` --- ### ✅ Label 範例結果: 這段 DaemonSet 會標上類似以下格式: ``` nvidia.com/gpu.product: nvidia-geforce-gtx-1080-ti nvidia.com/gpu.memory: "11264" ``` --- ### ⏱️ 自動更新? 目前這腳本在容器內休眠 `3600s`(1 小時),如果你希望定時重標,可將其改為 loop,每隔 N 分鐘重新執行一次偵測與打標籤。 <br> ### 為什麼需要定時重標 label? - ### gpt-o4 (2025/05/12) 實務上,「是否需要定時重標(relabel)」取決於你的 **GPU 硬體資訊是否會在節點生命周期內發生變化**。我們來看看會有哪些狀況可能導致 **Node 上的 GPU 資訊變動**,從而需要重新打標籤。 --- ### ✅ 你**可能需要定時重標**的情況: | 情況 | 是否常見 | 說明 | |------|----------|------| | **節點重啟後標籤遺失** | ❌ 不會發生 | Kubernetes 標籤是儲存在 API server,不會因為節點重啟而消失 | | **GPU hotplug / 新插入 GPU** | ❗少見但可能 | 若使用的是裸機伺服器,且有動態插拔 GPU 的操作(如 A/B 卡切換測試環境) | | **GPU 被更換(送修/升級)** | ✅ 在測試機或動態環境可能發生 | 若節點上 GPU 型號或記憶體發生變化,需更新 label | | **GPU 虛擬化技術(MIG, vGPU)重新分配** | ✅ 可能發生 | 如果有使用 NVIDIA MIG 或 vGPU 模式,重啟或重新分配後,memory 資訊可能不同 | | **你有自動建立/銷毀節點的流程(如 Karpenter, 自動擴展)** | ✅ 需要 | 新建立的 Node 沒有 label,DaemonSet 執行一次後就結束,不會重新打標 | | **Node metadata 被其他人誤改或清除** | ⚠️ 有風險 | 有些維運腳本或工具可能誤清 label,需要定期修正 | --- ### ✅ 如果你**不會動到 GPU 或節點不會改變**,**可以不用定時重標**,DaemonSet 初始化時標一次即可。 --- ### 🔁 若你要應對**上面變動場景**,建議: - 讓 DaemonSet 中的容器**保持運行並每幾分鐘重新偵測一次** - 或者,你可以改用一個 **CronJob**(例如每小時跑一次標籤流程) --- ### 🚀 結論與建議: | 環境 | 建議方式 | |------|----------| | 測試機、GPU 常被更換、開發人員常操作 GPU | ✅ DaemonSet + 定時 relabel | | 穩定上線的生產環境(GPU 不變) | ⛔ 初次標籤後即可,無須定時重標 | | 使用 Karpenter/GKE/AWS Auto Scaling | ✅ 要定時標籤,或觸發時自動執行打標腳本 | <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}