# Kubernetes CPU throttling ## 問題背景 在 Kubernetes 環境中,Cgroup 是如何限制 container 的 CPU 使用,以及如何觸發 CPU throttling。 ## K8s 如何套用 CPU 資源的限制? 當 Kubelet 啟動 Pod 中的 container 時,會將該 container 在 Kubernetes 中設定的 CPU 與記憶體的 requests(請求)與 limits(限制) 傳遞給底層的 container runtime 執行(如 Containerd、CRI-O 等)。 在 Linux 系統中,container runtime 通常會設定在 kernel 中的 cgroups ,以應用並強制執行定義的資源限制。 Kubelet(containerd 會去執行 cgroup) 會使用 Linux Kernel CFS(Completely Fair Scheduler,完全公平調度) 來限制 Workload 的 CPU time 的使用率,要點如下: * CPU 使用量的計量週期為 100ms。 * CPU limit 決定每計量週期(100ms)內 container 可以使用的 CPU 時間的上限 * 本週期若 container 的 CPU 時間用量達到上限,CPU throttling (限流) 開始,container 只能在下個週期繼續執行。 * 換算如下: - limit 限制使用 1 顆 CPU = 每 100ms 可用 100ms 的 CPU 時間 - limit 限制使用 0.2 顆 CPU = 每 100ms 可用 20ms 的 CPU 時間 - limit 限制使用 2.5 顆 CPU = 每 100ms 可用 250ms 的 CPU 時間 * 若 container 內的應用程式為多執行緒(multi-threaded),在多核心情況下會將所有執行緒的 CPU 使用時間累加計算,整體仍受 CFS quota 所限制。 > cgroups 提供了資源限制與隔離的框架,而 CFS 則在此框架下,實施具體的 CPU 時間分配與調度。 所以當 Workload 的 CPU 達到限制時,他是限制 CPU 的使用時間,會造成應用延時,讓應用執行所需要的時間變得更長了。 ### 以實際舉例,假設應用需要 150m,但 limit 設定 100m,會發生什麼事? * 換算成 CFS 週期 - limit = 100m = 每 100ms 週期內只能用 10ms。 - 需求 = 150m = 每 100ms 週期內需要 15ms。 ``` 週期 1(0ms ~ 100ms) ├─ 0ms : 開始執行 ├─ 10ms : 配額用完 → kernel 強制暫停 ├─ 10ms ~ 100ms : 容器被凍結,什麼都不能做(等待 90ms) └─ 100ms : 新週期開始,解凍 週期 2(100ms ~ 200ms) ├─ 100ms : 繼續執行(還差 5ms) ├─ 105ms : 任務完成 ✓ └─ 剩餘配額 5ms 閒置 ``` 結果:本來 15ms 就能跑完,實際花了 105ms,慢了 7 倍。  ## 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,最低優先權,最容易被驅逐。  ## 在 Cgroup v1 架構驗證 * 檢查 Cgroup,出現 `tmpfs` 代表是 v1 架構 ``` $ stat -fc %T /sys/fs/cgroup/ tmpfs ``` * 產生一個 Guaranteed 等級的 Pod ``` 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: 100m memory: 512Mi requests: cpu: 100m memory: 512Mi nodeName: rke2-w2 # 更改自己環境的節點名稱 ``` * 檢查 container uid ``` $ kubectl get pod NAME READY STATUS RESTARTS AGE test-57bdb449bf-h6fcz 1/1 Running 0 8m22s $ kubectl describe pod test-57bdb449bf-h6fcz | grep QoS QoS Class: Guaranteed $ kubectl get pod test-57bdb449bf-h6fcz -o jsonpath="{.metadata.uid}" 5bac8518-d4bd-4088-b72d-46c993a9b2cd ``` * 在產生 pod 的節點進入 `/sys/fs/cgroup/cpu/kubepods.slice/` 目錄,如果是創建 Guaranteed 等級 的 Pod,那麼就是會在這裡產生檔案。 ``` $ cd /sys/fs/cgroup/cpu/kubepods.slice/ # kubepods-pod5bac8518_d4bd_4088_b72d_46c993a9b2cd.slice 是我們剛剛創建的 pod # 後面的 id 是 pod 產生的 uid,可以透過 kubectl get po -oyaml 找到對應的 pod $ ls -l total 0 -rw-r--r-- 1 root root 0 May 23 08:34 cgroup.clone_children -rw-r--r-- 1 root root 0 May 23 08:34 cgroup.procs -rw-r--r-- 1 root root 0 May 23 09:07 cpu.cfs_burst_us -rw-r--r-- 1 root root 0 May 23 08:34 cpu.cfs_period_us -rw-r--r-- 1 root root 0 May 23 08:34 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 May 23 09:07 cpu.idle -rw-r--r-- 1 root root 0 May 23 08:34 cpu.shares -r--r--r-- 1 root root 0 May 23 08:34 cpu.stat -r--r--r-- 1 root root 0 May 23 08:34 cpuacct.stat -rw-r--r-- 1 root root 0 May 23 08:34 cpuacct.usage -r--r--r-- 1 root root 0 May 23 08:34 cpuacct.usage_all -r--r--r-- 1 root root 0 May 23 08:34 cpuacct.usage_percpu -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_percpu_sys -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_percpu_user -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_sys -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_user drwxr-xr-x 11 root root 0 May 23 08:34 kubepods-besteffort.slice drwxr-xr-x 6 root root 0 May 23 08:34 kubepods-burstable.slice drwxr-xr-x 4 root root 0 May 23 08:49 kubepods-pod5bac8518_d4bd_4088_b72d_46c993a9b2cd.slice -rw-r--r-- 1 root root 0 May 23 09:07 notify_on_release -rw-r--r-- 1 root root 0 May 23 09:07 tasks ``` ``` $ cd kubepods-pod5bac8518_d4bd_4088_b72d_46c993a9b2cd.slice $ ls -l total 0 -rw-r--r-- 1 root root 0 May 23 08:49 cgroup.clone_children -rw-r--r-- 1 root root 0 May 23 08:49 cgroup.procs -rw-r--r-- 1 root root 0 May 23 09:07 cpu.cfs_burst_us -rw-r--r-- 1 root root 0 May 23 08:49 cpu.cfs_period_us -rw-r--r-- 1 root root 0 May 23 08:49 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 May 23 09:07 cpu.idle -rw-r--r-- 1 root root 0 May 23 08:49 cpu.shares -r--r--r-- 1 root root 0 May 23 08:49 cpu.stat -r--r--r-- 1 root root 0 May 23 08:49 cpuacct.stat -rw-r--r-- 1 root root 0 May 23 08:49 cpuacct.usage -r--r--r-- 1 root root 0 May 23 08:49 cpuacct.usage_all -r--r--r-- 1 root root 0 May 23 08:49 cpuacct.usage_percpu -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_percpu_sys -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_percpu_user -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_sys -r--r--r-- 1 root root 0 May 23 09:07 cpuacct.usage_user drwxr-xr-x 2 root root 0 May 23 08:49 cri-containerd-d6fbe13e17995b57615f45fc0c8386b651ecb51aa17a60c754eacf5330963518.scope drwxr-xr-x 2 root root 0 May 23 08:49 cri-containerd-f3a4beafc13f8c2b832d47110a5da9b6c0da6336c6a44992c1bff6d059402e99.scope -rw-r--r-- 1 root root 0 May 23 09:07 notify_on_release -rw-r--r-- 1 root root 0 May 23 09:07 tasks ``` * 透過 crictl 檢查對應的 container `cgroupsPath` 是在哪裡。 ``` $ crictl ps -a | grep container-0 d6fbe13e17995 a830707172e80 18 minutes ago Running container-0 0 f3a4beafc13f8 test-57bdb449bf-h6fcz default $ crictl inspect d6fbe13e17995 | grep cgroupsPath "cgroupsPath": "kubepods-pod5bac8518_d4bd_4088_b72d_46c993a9b2cd.slice:cri-containerd:d6fbe13e17995b57615f45fc0c8386b651ecb51aa17a60c754eacf5330963518", $ cd cri-containerd-d6fbe13e17995b57615f45fc0c8386b651ecb51aa17a60c754eacf5330963518.scope ``` * `cpu.cfs_period_us` : 定義重新分配 CPU 資源的時間週期長度。預設是 100000(即 100 毫秒)。 * `cpu.cfs_quota_us` : 在一個週期內,這個 cgroup 最多可以使用的 CPU 時間。如果用完會被 throttled。 * `nr_throttled` : cgroup 中被限制的次數。 * `throttled_time` : 表示總共被節流(Throttled)的時間奈秒(ns)。 ``` $ ls -l total 0 -rw-r--r-- 1 root root 0 May 22 13:53 cgroup.clone_children -rw-r--r-- 1 root root 0 May 22 13:53 cgroup.procs -rw-r--r-- 1 root root 0 May 22 13:55 cpu.cfs_burst_us -rw-r--r-- 1 root root 0 May 22 13:53 cpu.cfs_period_us -rw-r--r-- 1 root root 0 May 22 13:53 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 May 22 13:55 cpu.idle -rw-r--r-- 1 root root 0 May 22 13:53 cpu.shares -r--r--r-- 1 root root 0 May 22 13:53 cpu.stat -r--r--r-- 1 root root 0 May 22 13:53 cpuacct.stat -rw-r--r-- 1 root root 0 May 22 13:53 cpuacct.usage -r--r--r-- 1 root root 0 May 22 13:53 cpuacct.usage_all -r--r--r-- 1 root root 0 May 22 13:53 cpuacct.usage_percpu -r--r--r-- 1 root root 0 May 22 13:55 cpuacct.usage_percpu_sys -r--r--r-- 1 root root 0 May 22 13:55 cpuacct.usage_percpu_user -r--r--r-- 1 root root 0 May 22 13:55 cpuacct.usage_sys -r--r--r-- 1 root root 0 May 22 13:55 cpuacct.usage_user -rw-r--r-- 1 root root 0 May 22 13:55 notify_on_release -rw-r--r-- 1 root root 0 May 22 13:55 tasks # 預設的 cpu 週期都是 100 ms $ cat cpu.cfs_period_us 100000 # 10000 就是代表限制(limit) 100m cpu $ cat cpu.cfs_quota_us 10000 # 419816941 ns = 419.816941 ms $ cat cpu.stat nr_periods 7 nr_throttled 3 throttled_time 419816941 ``` * 進到 test pod 去做 cpu 壓力測試 ``` $ kubectl get pod NAME READY STATUS RESTARTS AGE test-57bdb449bf-8kxvj 1/1 Running 0 3m31s $ kubectl exec test-57bdb449bf-8kxvj -- timeout 240 yes >/dev/null & ``` * 可以看到 `nr_throttled` 被限制次數變多了。 ``` $ cat cpu.stat nr_periods 255 nr_throttled 221 throttled_time 42323283399 ``` * 使用 PromQL 表達式查詢,可以看到 pod 有被節流,只要大於 0 就是有發生 throttled, 1 表示幾乎每次都被 throttled。 ``` sum(increase(container_cpu_cfs_throttled_periods_total{container!=""}[5m])) by (container, pod, namespace) / sum(increase(container_cpu_cfs_periods_total[5m])) by (container, pod, namespace) ```  ## 在 Cgroup v2 架構驗證 * 檢查 Cgroup,出現 `cgroup2fs` 代表是 v2 架構 ``` $ stat -fc %T /sys/fs/cgroup/ cgroup2fs ``` * 產生一個 Guaranteed 等級的 Pod ``` 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: 100m memory: 512Mi requests: cpu: 100m memory: 512Mi nodeName: rke2-m1 # 更改自己環境的節點名稱 ``` * 檢查 container uid ``` $ kubectl get pod NAME READY STATUS RESTARTS AGE test-f84769dfd-ngpj6 1/1 Running 0 59s $ kubectl describe pod test-f84769dfd-ngpj6 | grep QoS QoS Class: Guaranteed $ kubectl get pod test-f84769dfd-ngpj6 -o jsonpath="{.metadata.uid}" 3bd1d53b-35e4-4ee4-a73d-24a3645354bd ``` * 在產生 pod 的節點進入 `/sys/fs/cgroup/kubepods.slice/` 目錄,如果是創建 Guaranteed 等級 的 Pod,那麼就是會在這裡產生檔案。 ``` $ cd /sys/fs/cgroup/kubepods.slice/ # kubepods-pod3bd1d53b_35e4_4ee4_a73d_24a3645354bd.slice 是我們剛剛創建的 pod $ ls -l /sys/fs/cgroup/kubepods.slice/ total 0 -r--r--r-- 1 root root 0 May 22 11:24 cgroup.controllers -r--r--r-- 1 root root 0 May 22 11:24 cgroup.events -rw-r--r-- 1 root root 0 May 22 11:27 cgroup.freeze --w------- 1 root root 0 May 22 14:30 cgroup.kill -rw-r--r-- 1 root root 0 May 22 14:30 cgroup.max.depth -rw-r--r-- 1 root root 0 May 22 14:30 cgroup.max.descendants -rw-r--r-- 1 root root 0 May 22 11:24 cgroup.procs -r--r--r-- 1 root root 0 May 22 14:30 cgroup.stat -rw-r--r-- 1 root root 0 May 22 11:25 cgroup.subtree_control -rw-r--r-- 1 root root 0 May 22 14:30 cgroup.threads -rw-r--r-- 1 root root 0 May 22 11:24 cgroup.type -rw-r--r-- 1 root root 0 May 22 11:24 cpu.idle -rw-r--r-- 1 root root 0 May 22 11:24 cpu.max -rw-r--r-- 1 root root 0 May 22 14:30 cpu.max.burst -r--r--r-- 1 root root 0 May 22 11:24 cpu.stat -r--r--r-- 1 root root 0 May 22 14:30 cpu.stat.local -rw-r--r-- 1 root root 0 May 22 11:24 cpu.weight -rw-r--r-- 1 root root 0 May 22 14:30 cpu.weight.nice -rw-r--r-- 1 root root 0 May 22 11:24 cpuset.cpus -r--r--r-- 1 root root 0 May 22 11:24 cpuset.cpus.effective -rw-r--r-- 1 root root 0 May 22 14:30 cpuset.cpus.partition -rw-r--r-- 1 root root 0 May 22 11:24 cpuset.mems -r--r--r-- 1 root root 0 May 22 14:30 cpuset.mems.effective -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.1GB.current -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.1GB.events -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.1GB.events.local -rw-r--r-- 1 root root 0 May 22 11:24 hugetlb.1GB.max -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.1GB.numa_stat -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.1GB.rsvd.current -rw-r--r-- 1 root root 0 May 22 11:24 hugetlb.1GB.rsvd.max -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.2MB.current -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.2MB.events -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.2MB.events.local -rw-r--r-- 1 root root 0 May 22 11:24 hugetlb.2MB.max -r--r--r-- 1 root root 0 May 22 14:30 hugetlb.2MB.numa_stat -r--r--r-- 1 root root 0 May 22 11:25 hugetlb.2MB.rsvd.current -rw-r--r-- 1 root root 0 May 22 11:24 hugetlb.2MB.rsvd.max -rw-r--r-- 1 root root 0 May 22 11:24 io.bfq.weight -rw-r--r-- 1 root root 0 May 22 14:30 io.latency -rw-r--r-- 1 root root 0 May 22 14:30 io.max -r--r--r-- 1 root root 0 May 22 11:24 io.stat -rw-r--r-- 1 root root 0 May 22 11:24 io.weight drwxr-xr-x 16 root root 0 May 22 13:58 kubepods-besteffort.slice drwxr-xr-x 11 root root 0 May 22 11:27 kubepods-burstable.slice drwxr-xr-x 4 root root 0 May 23 09:10 kubepods-pod3bd1d53b_35e4_4ee4_a73d_24a3645354bd.slice drwxr-xr-x 4 root root 0 May 23 08:34 kubepods-pod51251f5d_8fc7_424e_9756_90d49dcc2c35.slice ...... ``` ``` $ cd kubepods-pod3bd1d53b_35e4_4ee4_a73d_24a3645354bd.slice $ ls -l total 0 -r--r--r-- 1 root root 0 May 23 09:10 cgroup.controllers -r--r--r-- 1 root root 0 May 23 09:10 cgroup.events -rw-r--r-- 1 root root 0 May 23 09:11 cgroup.freeze --w------- 1 root root 0 May 23 09:11 cgroup.kill -rw-r--r-- 1 root root 0 May 23 09:11 cgroup.max.depth -rw-r--r-- 1 root root 0 May 23 09:11 cgroup.max.descendants -rw-r--r-- 1 root root 0 May 23 09:10 cgroup.procs -r--r--r-- 1 root root 0 May 23 09:11 cgroup.stat -rw-r--r-- 1 root root 0 May 23 09:10 cgroup.subtree_control -rw-r--r-- 1 root root 0 May 23 09:11 cgroup.threads -rw-r--r-- 1 root root 0 May 23 09:10 cgroup.type -rw-r--r-- 1 root root 0 May 23 09:10 cpu.idle -rw-r--r-- 1 root root 0 May 23 09:10 cpu.max -rw-r--r-- 1 root root 0 May 23 09:11 cpu.max.burst -r--r--r-- 1 root root 0 May 23 09:10 cpu.stat -r--r--r-- 1 root root 0 May 23 09:11 cpu.stat.local -rw-r--r-- 1 root root 0 May 23 09:10 cpu.weight -rw-r--r-- 1 root root 0 May 23 09:11 cpu.weight.nice -rw-r--r-- 1 root root 0 May 23 09:10 cpuset.cpus -r--r--r-- 1 root root 0 May 23 09:10 cpuset.cpus.effective -rw-r--r-- 1 root root 0 May 23 09:11 cpuset.cpus.partition -rw-r--r-- 1 root root 0 May 23 09:10 cpuset.mems -r--r--r-- 1 root root 0 May 23 09:11 cpuset.mems.effective drwxr-xr-x 2 root root 0 May 23 09:10 cri-containerd-32dc3b88a5f2175809059f08bb2fd9e155fe6ef5a3266030094a4873435afc8f.scope drwxr-xr-x 2 root root 0 May 23 09:10 cri-containerd-e266625d0bfec4cd12819866d2e929cf8b4540670342ca3da77a32e336c5f2cc.scope ...... ``` * 透過 crictl 檢查對應的 container `cgroupsPath` 是在哪裡。 ``` $ crictl ps -a | grep container-0 e266625d0bfec a830707172e80 About a minute ago Running container-0 0 32dc3b88a5f21 test-f84769dfd-ngpj6 default $ crictl inspect e266625d0bfec | grep cgroupsPath "cgroupsPath": "kubepods-pod3bd1d53b_35e4_4ee4_a73d_24a3645354bd.slice:cri-containerd:e266625d0bfec4cd12819866d2e929cf8b4540670342ca3da77a32e336c5f2cc", $ cd cri-containerd-e266625d0bfec4cd12819866d2e929cf8b4540670342ca3da77a32e336c5f2cc.scope ``` * 環境是 cgroup v2 的話,則根據 cpu.max 的設定的值來限制 CPU,他的格式就是 `cfs_quota_us cfs_period_us`。 * `usage_usec` : 該 cgroup 總共使用的 CPU 時間(單位:微秒),包括使用者與核心空間的時間。 * `user_usec` : 使用者空間(user space)所使用的 CPU 時間(微秒),例如應用程式本身的運算。 * `system_usec` : 核心空間(kernel space)所使用的 CPU 時間(微秒),例如系統呼叫、網路 I/O。 * `nr_periods` : cpu.max 配額控制的週期總數。每個週期表示一次限速監控的單位(預設是 100ms)。 * `nr_throttled` : 有多少個週期因為超出 cpu.max 而被限速(throttled)。越高代表資源常常不夠用。 * `throttled_usec` : 累積被限速的 CPU 時間(微秒)。 ``` # 第一個值 10000 就是代表限制(limit) 100m cpu # 第二個值 100000 就是預設的 cpu 週期,都是 100 ms $ cat cpu.max 10000 100000 $ cat cpu.stat usage_usec 42485 user_usec 15935 system_usec 26550 core_sched.force_idle_usec 0 nr_periods 7 nr_throttled 4 throttled_usec 627048 nr_bursts 0 burst_usec 0 ``` * 進到 test pod 去做 cpu 壓力測試。 ``` $ kubectl get po NAME READY STATUS RESTARTS AGE test-746865dbb-c8tsf 1/1 Running 0 22m $ kubectl exec test-746865dbb-c8tsf -- timeout 240 yes >/dev/null & ``` * 可以看到 `nr_throttled` 和 `throttled_usec` 都變高,代表目前這個 pod 被 throttled ``` $ cat cpu.stat usage_usec 1211910 user_usec 84596 system_usec 1127314 core_sched.force_idle_usec 0 nr_periods 125 nr_throttled 119 throttled_usec 16374085 nr_bursts 0 burst_usec 0 ``` * 使用 PromQL 表達式查詢,可以看到 pod 有被節流,只要大於 0 就是有發生 throttled, 1 表示幾乎每次都被 throttled。 ``` sum(increase(container_cpu_cfs_throttled_periods_total{container!=""}[5m])) by (container, pod, namespace) / sum(increase(container_cpu_cfs_periods_total[5m])) by (container, pod, namespace) ```  ## K8s 是否有方式可以避免 CPU throttling? ### 答案是有的,分為以下兩種 1. 提高 CPU Limit 的用量 * Kubernetes 中 container 資源限制(Resources)可以分為: - cpu request:保證 container 最低可以使用的 CPU。 - cpu limit: container 最多可以使用的 CPU。 如果 container 的 cpu limit 設定得太低,而應用實際需要更多資源,當超過限制時,Kubernetes 的 CFS(Completely Fair Scheduler)就會對其進行「Throttling」(節流),導致應用程式效能下降或不穩定。 2. 檢視應用程式的 Thread 用量是否不正確 * 應用程式若開啟過多執行緒(threads),尤其是在多執行緒(multi-threaded)或非同步架構中,可能會造成: - 多個執行緒同時爭用 CPU,導致短時間內 CPU 使用急升。 - 每個執行緒都被排程,但在受限的 CPU limit 下無法順利執行 → 出現 CFS throttling。 ## 優化 CPU 使用效率 另外一個影響 Container CPU 效能的因素就是 CPU 的使用效率。 在 Kubernetes 中,Container 所使用的 CPU 資源是共享的。雖然我們可以透過 CPU request 為 Container 保留資源,但實際上這些 request 所對應的 vCPU 並不是固定綁定在某幾個實體 core 上。因此,在同一個 Container 中執行的多個 thread,可能會在不同的時間區段內被調度到不同的 CPU core。 舉例來說,假設應用是使用 java 開發,那麼他一定會有多執行緒的設計(multi-threaded),假設使用 4 條 thread 同時處理工作。在執行期間,這些 thread 被排程在哪些 CPU 上,其實是變動的,如下圖所示:  ### 什麼是 NUMA(Non-Uniform Memory Access) 在透過 Topology Manager 優化 K8s 系統中的 container CPU 使用效率前,我們先認識什麼是 NUMA。 NUMA 它是一種讓多顆 CPU 能更有效率地存取記憶體的設計方式,常見於多核心、高階伺服器或工作站上。  在執行應用時,系統為了分散使用 CPU 資源,可能會將應用分別執行在不同的 NUMA node 上,在應用執行過程中,只要有 thread 跨 NUMA node 兩者要互相讀寫記憶體時,就會經過 Interconnect,而透過 Interconnect 都是會增加整個應用所執行的時間。 想像你在辦公室裡有兩個團隊(代表 2 顆 CPU),每個團隊旁邊都有一個書櫃(代表 RAM)。每個團隊成員通常都會從「自己旁邊的書櫃」拿資料(速度很快),但有時也會需要去「另一邊的書櫃」找資料(花比較久)。 * 這就是 NUMA 架構的核心概念: - 每個 CPU 有自己「比較快的記憶體」可以用(稱為 local memory)。 - 但它也能用其他 CPU 的記憶體,只是速度會比較慢(稱為 remote memory)。 * 在 NUMA 架構下,本地記憶體存取 local memory access 的速度會比非本地記憶體存取 non-local memory access 快上許多。 上圖中的 Node1 中的 Core 要存取 Node1 中的記憶體,就比透過 Interconnect 存取 Node2 的記憶體快上許多。為了達到資源的最有效利用,就必須要盡可能地把資源集中在同一台 NUMA Node 上。 如果看到 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 ``` ### RKE2 啟用 Topology Manager 綁定 cpu 功能可以[參考](https://hackmd.io/URf7odnWStepSLUxWhSScQ) ## 參考 https://hackmd.io/@QI-AN/S1Z2FYUyxx https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu#sect-cfs https://www.hwchiu.com/docs/2023/container-vm#how-to-avoid https://kubernetes.io/docs/tasks/administer-cluster/topology-manager/
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.