---
# System prepended metadata

title: RKE2 啟用 Topology Manager 綁定 cpu 功能
tags: [work]

---

# RKE2 啟用 Topology Manager 綁定 cpu 功能

Topology Manager 是屬於 kubelet 中的元件之一，並提供了一個介面(interface) Hint Providers。這個介面會負責去發送與接收 topology 相關的資訊，讓 Topology Manager 再搭配 kubelet 中定義的 topology-manager-policy 去算出最佳的結果。

簡單來說 Topology Manager 就是定義一個策略，讓 Kubelet 判斷要不要盡量把 container 分配在同一個 NUMA node 上執行。

1. none

預設的 topology-manager-policy，若設成 none 則與原本沒設定一樣。

2. best-effort

會盡可能地將開出來的 Container 集中在同一個 NUMA node，但若是沒辦法通通集中在一起，也會允許將 Container 開在不同的 NUMA node 上。

3. restricted

Container 都必須被集中在同一個 NUMA node，除非被要求的資源超過了一個 NUMA node 所能提供的上限，所以還是有部分會跨 NUMA node，仍然允許 pod 啟動。

我們舉例來說：假設目前擁有著兩個 NUMA node 的情況下，一個 node 有著 6 顆 CPU。
- requests and limits CPU → 6 core
Container 會被限制只能在一台 NUMA node 上執行
- requests and limits CPU → 7~12 core
Container 會被限制只能在兩台 NUMA node 上執行



4. single-numa-node

最嚴格的設定，Pod 裡面所有的 Container 都必須集中在同樣的 NUMA node，若是沒辦法達成，Pod 就沒辦法被建立。


## 在 PVE 上開啟 NUMA 功能
* 注意 vm 需要 2 個 socket 

![image](https://hackmd.io/_uploads/By2XtAYbee.png)


* 如果看到 NUMA node(s): 2，就代表你有兩個 NUMA 節點
```
$ lscpu | grep "NUMA node(s)"
NUMA node(s):                         2
```
* 查看每個 NUMA node 對應的 CPU 和記憶體資訊
```
$ numactl --hardware
available: 2 nodes (0-1)            # 機器有 2 個 NUMA node，編號為 0 和 1
node 0 cpus: 0 1 2 3 4 5 6 7        # Node 0 管理 CPU 0~7
node 0 size: 5874 MB
node 0 free: 4855 MB
node 1 cpus: 8 9 10 11 12 13 14 15  # Node 1 管理 CPU 8~15
node 1 size: 5735 MB
node 1 free: 4461 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10
```
## 實作透過 rancher 創建 rke2 叢集


kubelet 設定以下參數:
* 啟用 feature-gates 功能 : 我們要開啟 `TopologyManagerPolicyOptions` 、`CPUManagerPolicyOptions` 的功能。
* `topology-manager-policy=single-numa-node` : 讓 Pod 所用的所有 CPU 和記憶體資源，都來自於同一個 NUMA node。
* `cpu-manager-policy=static` : 會讓設定了 CPU 資源 limits 的 Pod 獲得固定綁定的實體 CPU core，而要讓 Topology Manager 能夠正確分配 Container 到 NUMA 上，必須要把 Pod 設定成 Guaranteed。
* `kube-reserved=cpu=1,memory=2048Mi` : 保留 1 顆 CPU core 與 2048Mi 記憶體給 Kubernetes 系統的背景程序（如 kubelet、container runtime 時等）預留資源。
* `system-reserved=cpu=1,memory=2048Mi` : 保留 1 顆 CPU core 與 2048Mi 記憶體給作業系統本身使用。
* `topology-manager-scope=pod` : 將 pod 視為一個整體，並將整個 pod（所有 container）都指派給單一 NUMA node。

```
feature-gates=TopologyManagerPolicyOptions=true,CPUManagerPolicyOptions=true
topology-manager-policy=single-numa-node
cpu-manager-policy=static
kube-reserved=cpu=1,memory=2048Mi
system-reserved=cpu=1,memory=2048Mi
topology-manager-scope=pod
```
> kubelet 設定開啟 `feature-gates=TopologyManagerPolicyOptions=true` 這個功能在 1.32 預設是已經啟用了。
> `CPUManagerPolicyOptions=true` 這個功能在 1.33 預設是已經啟用了。


![image](https://hackmd.io/_uploads/HkmCocGMgx.png)




* 檢查 rke2 叢集狀態
```
$ kubectl get no
NAME      STATUS   ROLES                              AGE    VERSION
rke2-m1   Ready    control-plane,etcd,master,worker   179m   v1.32.4+rke2r1
rke2-w1   Ready    worker                             172m   v1.32.4+rke2r1
rke2-w2   Ready    worker                             102m   v1.32.4+rke2r1
```
* 檢查 kubelet 參數是否套用
```
$ kubectl get --raw "/api/v1/nodes/rke2-m1/proxy/configz" | jq .
{
  "kubeletconfig": {
    "cpuManagerPolicy": "static",
    "topologyManagerPolicy": "single-numa-node",
    "featureGates": {
      "CPUManagerPolicyOptions": true,
      "TopologyManagerPolicyOptions": true
    },
    "systemReserved": {
      "cpu": "1",
      "memory": "2048Mi"
    },
    "kubeReserved": {
      "cpu": "1",
      "memory": "2048Mi"
    },
......
```
## Quality of Service
* 在 Kubernetes 中，Pod 的 Quality of Service (QoS) 分為三種等級：Guaranteed（保證）、Burstable（突發）和 BestEffort（盡力而為）。這些等級是根據 Pod 中 container 所設定的 CPU 和 Memory 資源來自動判斷的，會影響資源爭用時的優先級與調度行為。
  - Guaranteed : 所有 container 都設定了 requests = limits（CPU & Memory），擁有最高優先權，在節點壓力大時最不容易被驅逐。
  - Burstable : 至少有一個 container 設定了 requests，但 requests 、 limits 的值不完全相同，中等優先權，在節點壓力下，有可能被驅逐，但優先於 BestEffort。
  - BestEffort : 沒有設定任何資源 requests 或 limits，最低優先權，最容易被驅逐。
## 驗證 container 是否都跑在同一個 NUMA Node 上執行


### Cgroup v1 環境
* 檢查 Cgroup，出現 tmpfs 代表是 v1 架構
```
$ stat -fc %T /sys/fs/cgroup/
tmpfs
```

* 產生一個 Guaranteed 等級的 Pod，需要指定 1 core 的 cpu 使用。
```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - image: nginx
          imagePullPolicy: IfNotPresent
          name: container-0
          resources:
            limits:
              cpu: 1
              memory: 512Mi
            requests:
              cpu: 1
              memory: 512Mi
      nodeName: rke2-w2           # 更改自己環境的節點名稱
```
* 找到 container 對應的 uid，並且 QoS Class 是 Guaranteed。
```
$ kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
test-84b5cbb8db-r4frd   1/1     Running   0          3m

$ kubectl describe pod test-84b5cbb8db-r4frd | grep QoS
QoS Class:                   Guaranteed

$ kubectl get pod test-84b5cbb8db-r4frd -o jsonpath="{.metadata.uid}"
d23f2473-c831-4c2d-a948-96ca09abf068
```
* `kubepods-podd23f2473_c831_4c2d_a948_96ca09abf068.slice` 是剛剛創建的 pod，可以根據 container 的 uid 確認。
```
# v1 架構是以下目錄
$ cd /sys/fs/cgroup/cpuset/kubepods.slice

$ ls -l
total 0
-rw-r--r-- 1 root root 0 May 20 16:57 cgroup.clone_children
-rw-r--r-- 1 root root 0 May 20 16:57 cgroup.procs
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.cpu_exclusive
-rw-r--r-- 1 root root 0 May 20 16:45 cpuset.cpus
-r--r--r-- 1 root root 0 May 20 16:57 cpuset.effective_cpus
-r--r--r-- 1 root root 0 May 20 16:57 cpuset.effective_mems
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.mem_exclusive
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.mem_hardwall
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.memory_migrate
-r--r--r-- 1 root root 0 May 20 16:57 cpuset.memory_pressure
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.memory_spread_page
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.memory_spread_slab
-rw-r--r-- 1 root root 0 May 20 16:45 cpuset.mems
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.sched_load_balance
-rw-r--r-- 1 root root 0 May 20 16:57 cpuset.sched_relax_domain_level
drwxr-xr-x 3 root root 0 May 20 16:45 kubepods-besteffort.slice
drwxr-xr-x 4 root root 0 May 20 16:45 kubepods-burstable.slice
drwxr-xr-x 4 root root 0 May 20 16:47 kubepods-podd23f2473_c831_4c2d_a948_96ca09abf068.slice
-rw-r--r-- 1 root root 0 May 20 16:57 notify_on_release
-rw-r--r-- 1 root root 0 May 20 16:57 tasks
```
* 透過 crictl 檢查對應的 container `cgroupsPath` 是在哪裡，並查看 `cpuset.cpus` 是在哪個 core 上執行。
```
$ crictl ps -a | grep container-0
e792f799393ef       a830707172e80       20 seconds ago      Running             container-0                     0                   1bb236e4bcd45       test-7f8fbb578f-9fld4                 default

$ crictl inspect e792f799393ef | grep cgroupsPath
        "cgroupsPath": "kubepods-podd23f2473_c831_4c2d_a948_96ca09abf068.slice:cri-containerd:168972ba3c4a808cfcf4c028b2690940a158e728347b6da47897b99928dbb2d1",

# 進入 cgroupsPath 的目錄
$ cd kubepods-podd23f2473_c831_4c2d_a948_96ca09abf068.slice/cri-containerd-168972ba3c4a808cfcf4c028b2690940a158e728347b6da47897b99928dbb2d1.scope/


$ ls -l
total 0
-rw-r--r-- 1 root root 0 May 20 18:21 cgroup.clone_children
-rw-r--r-- 1 root root 0 May 20 18:16 cgroup.procs
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.cpu_exclusive
-rw-r--r-- 1 root root 0 May 20 18:16 cpuset.cpus
-r--r--r-- 1 root root 0 May 20 18:21 cpuset.effective_cpus
-r--r--r-- 1 root root 0 May 20 18:21 cpuset.effective_mems
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.mem_exclusive
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.mem_hardwall
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.memory_migrate
-r--r--r-- 1 root root 0 May 20 18:21 cpuset.memory_pressure
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.memory_spread_page
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.memory_spread_slab
-rw-r--r-- 1 root root 0 May 20 18:16 cpuset.mems
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.sched_load_balance
-rw-r--r-- 1 root root 0 May 20 18:21 cpuset.sched_relax_domain_level
-rw-r--r-- 1 root root 0 May 20 18:21 notify_on_release
-rw-r--r-- 1 root root 0 May 20 18:21 tasks
```
* 查看 `cpuset.cpus` 是 `2` ，代表 container 被限制只能使用 CPU core 2，就代表他綁定在 NUMA Node 0 上執行。
* 如果沒有設定 Topology Manager 綁定 cpu 的功能，那麼在 `cpuset.cpus` 看到的會是一個區間，就有可能跨 NUMA Node 執行。
```
$ cat cpuset.cpus
2

$ numactl --hardware
available: 2 nodes (0-1)            # 機器有 2 個 NUMA node，編號為 0 和 1
node 0 cpus: 0 1 2 3 4 5 6 7        # Node 0 管理 CPU 0~7
node 0 size: 5874 MB
node 0 free: 4855 MB
node 1 cpus: 8 9 10 11 12 13 14 15  # Node 1 管理 CPU 8~15
node 1 size: 5735 MB
node 1 free: 4461 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10
```

### Cgroup v2 環境

* 檢查 Cgroup，出現 cgroup2fs 代表是 v2 架構
```
$ stat -fc %T /sys/fs/cgroup/
cgroup2fs
```
* 產生一個 Guaranteed 等級的 Pod，需要指定 1 core 的 cpu 使用。
```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - image: nginx
          imagePullPolicy: IfNotPresent
          name: container-0
          resources:
            limits:
              cpu: 1
              memory: 512Mi
            requests:
              cpu: 1
              memory: 512Mi
      nodeName: rke2-m1           # 更改自己環境的節點名稱
```
* 找到 container 對應的 uid，並且 QoS Class 是 Guaranteed。
```
$ kubectl get pod
NAME                    READY   STATUS    RESTARTS   AGE
test-84b5cbb8db-v5jlj   1/1     Running   0          13s

$ kubectl describe pod test-84b5cbb8db-v5jlj | grep QoS
QoS Class:                   Guaranteed

$ kubectl get pod test-84b5cbb8db-v5jlj -o jsonpath="{.metadata.uid}"
5b35383e-e6ab-4e25-bdb5-f44c8beef4c7
```


* `kubepods-pod5b35383e_e6ab_4e25_bdb5_f44c8beef4c7.slice` 是剛剛創建的 pod，可以根據 container 的 uid 確認。
```
# v2 架構是以下目錄
$ cd /sys/fs/cgroup/kubepods.slice/

$ ls -l
total 0
......
drwxr-xr-x 12 root root 0 May 20 15:35 kubepods-besteffort.slice
drwxr-xr-x 11 root root 0 May 20 15:32 kubepods-burstable.slice
drwxr-xr-x  4 root root 0 May 20 18:15 kubepods-pod51251f5d_8fc7_424e_9756_90d49dcc2c35.slice
drwxr-xr-x  4 root root 0 May 20 18:45 kubepods-pod5b35383e_e6ab_4e25_bdb5_f44c8beef4c7.slice
```
* 透過 crictl 檢查對應的 container `cgroupsPath` 是在哪裡，並查看 `cpuset.cpus` 是在哪個 core 上執行。
```
$ crictl ps -a | grep container-0
a85108cfc1c7c       a830707172e80       2 hours ago         Running             container-0                     0                   23feaee82f382       test-84b5cbb8db-v5jlj                                   default

$ crictl inspect a85108cfc1c7c | grep cgroupsPath
        "cgroupsPath": "kubepods-pod5b35383e_e6ab_4e25_bdb5_f44c8beef4c7.slice:cri-containerd:a85108cfc1c7cdf4a082cb8fffd6427f5e56b6812e4f216fd64d2e86097ee1fe",

# 進入 cgroupsPath 的目錄
$ cd kubepods-pod5b35383e_e6ab_4e25_bdb5_f44c8beef4c7.slice/cri-containerd-a85108cfc1c7cdf4a082cb8fffd6427f5e56b6812e4f216fd64d2e86097ee1fe.scope/
```
* 查看 `cpuset.cpus` 是 `2` ，代表 container 被限制只能使用 CPU core 2，就代表他綁定在 NUMA Node 0 上執行。
```
$ cat cpuset.cpus
2

$ numactl --hardware
available: 2 nodes (0-1)            # 機器有 2 個 NUMA node，編號為 0 和 1
node 0 cpus: 0 1 2 3 4 5 6 7        # Node 0 管理 CPU 0~7
node 0 size: 5874 MB
node 0 free: 4855 MB
node 1 cpus: 8 9 10 11 12 13 14 15  # Node 1 管理 CPU 8~15
node 1 size: 5735 MB
node 1 free: 4461 MB
node distances:
node   0   1
  0:  10  20
  1:  20  10
```
## 參考

https://medium.com/gemini-open-cloud/kubernetes%E6%90%AD%E9%85%8Dnuma%E5%B8%B6%E4%BD%A0%E9%A3%9B-e71193bba996#b13c

https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/

https://kubernetes.io/docs/tasks/administer-cluster/topology-manager/#topology-manager-policy-options