[應用][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 %}