---
# System prepended metadata

title: '[應用][node label] NodeFeatureDiscovery (NFD)'
tags: [k8s, node, K8s / app, label, NFD, NodeFeatureDiscovery, app, Kubernetes]

---

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