1. 程式人生 > >pod資源限制和QoS探索

pod資源限制和QoS探索

## 簡述 預設情況下,k8s不會對pod的資源使用進行限制,也就是說,pod可以無限使用主機的資源,例如CPU、記憶體等。為了保障k8s整體環境執行的穩定性,一般情況下,建議是對pod的資源使用進行限制,將其限制在一個範圍內,防止起過度使用主機資源造成節點負載過大,導致其上面執行的其他應用受到影響。 ## 前提 - 已有的k8s環境(這裡安裝的是k8s-v1.18.12版本) - k8s叢集安裝了metrics-server服務(這裡藉助rancher搭建k8s叢集,預設安裝了該服務) ## 何為CGroup 參考Wiki,**cgroups**其名稱源自**控制組群**(英語:control groups)的簡寫,是Linux核心的一個功能,用來限制、控制與分離一個程序組的資源(如CPU、記憶體、磁碟輸入輸出等)。 也就是說,通過設定CGroup,可以達到對資源的控制,例如限制記憶體、CPU的使用。在k8s中,對pod的資源限制就是通過CGroup這個技術來實現的。 ## 記憶體限制 ### pod示例 首先建立一個pod,並設定記憶體限制 ```yaml resources: limits: memory: "200Mi" requests: memory: "100Mi" ``` > 記憶體單位:E、P、T、G、M、K、Ei、Pi、Ti、Gi、Mi、Ki > > 其中M = 1000 x 1000,Mi = 1024 x 1024 > > limit必須要≥request 完整yaml參考 ```yaml apiVersion: v1 kind: Pod metadata: name: stress-memory namespace: default spec: containers: - name: stress image: polinux/stress imagePullPolicy: IfNotPresent command: ["stress"] args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"] resources: limits: memory: "200Mi" requests: memory: "100Mi" nodeName: node01 ## 這裡指定排程到某個節點上,方便接下來的操作 ``` 這裡設定了記憶體的limit為"200Mi",記憶體的request為"100Mi",並通過`stress`,指定分配150M的記憶體空間 接著我們執行該yaml ```bash kubectl create -f stress-memory.yaml ``` 等到pod執行起來後,我們看一下pod的詳細資訊 ```bash kubectl get pod stress-memory -oyaml ``` 輸出結果如下 ```yaml ... ... name: stress-memory namespace: default ... ... uid: b84cf8e8-03b3-4365-b5b5-5d9f99969705 ... ... resources: limits: memory: 200Mi requests: memory: 100Mi ... ... status: ... ... qosClass: Burstable ... ... ``` 從上述輸出中可以獲取如下兩個資訊: - pod的uid為`b84cf8e8-03b3-4365-b5b5-5d9f99969705` - pod的QoSClass為`Burstable` 這裡**Burstable**對應的就是pod在CGroup中的路徑,我們進入到CGroup的memory目錄下,並進入到`kubepods/Burstable`目錄中 ```bash cd /sys/fs/cgroup/memory/kubepods/burstable/ ``` ```bash # ls ... podb84cf8e8-03b3-4365-b5b5-5d9f99969705 ... ``` 通過執行`ls`命令,能看到其中一個目錄為`podb84cf8e8-03b3-4365-b5b5-5d9f99969705`,正好對應上面我們獲得pod的uid,也就是說,對pod的資源限制,就是在這個CGroup目錄下實現的 我們看一下這個目錄下有什麼檔案 ```bash cd podb84cf8e8-03b3-4365-b5b5-5d9f99969705/ ls -1 ``` ```bash 8c9adca52f2bfd9e1862f6abaac5b435bdaf233925b7d640edb28e36da0b9b39 ca20137f7d5a42910e22425cca06443c16246c90ee9caa27814442e34a832b21 cgroup.clone_children cgroup.event_control cgroup.procs memory.failcnt memory.force_empty memory.kmem.failcnt memory.kmem.limit_in_bytes memory.kmem.max_usage_in_bytes memory.kmem.slabinfo memory.kmem.tcp.failcnt memory.kmem.tcp.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.kmem.usage_in_bytes memory.limit_in_bytes memory.max_usage_in_bytes memory.memsw.failcnt memory.memsw.limit_in_bytes memory.memsw.max_usage_in_bytes memory.memsw.usage_in_bytes memory.move_charge_at_immigrate memory.numa_stat memory.oom_control memory.pressure_level memory.soft_limit_in_bytes memory.stat memory.swappiness memory.usage_in_bytes memory.use_hierarchy notify_on_release tasks ``` 相關檔案作用如下: | 檔案 | 作用 | | ------------------------------- | ------------------------------------------------------------ | | cgroup.event_control | 用於event_fd()介面 | | cgroup.procs | 展示process列表 | | memory.failcnt | 記憶體使用達到限制的次數 | | memory.force_empty | 強制觸發當前CGroup中的記憶體回收 | | memory.limit_in_bytes | 記憶體限制的最大值 | | memory.max_usage_in_bytes | 記錄該CGroup中歷史最大記憶體使用量 | | memory.move_charge_at_immigrate | 設定/顯示當前CGroup的程序移動到另一個CGroup時,當前已佔用的記憶體是否遷移到新的CGroup中,預設為0,即不移動 | | memory.numa_stat | 顯示numa相關的記憶體資訊 | | memory.oom_control | 設定/顯示oom相關資訊,其中oom_kill_disable為0,則超過記憶體會被kill;oom_kill_disable為1則停止程序,直至額外的記憶體被釋放,當程序被暫停時,under_oom返回1 | | memory.pressure_level | 設定記憶體壓力的通知事件,配合cgroup.event_control一起使用 | | memory.soft_limit_in_bytes | 設定/顯示記憶體的軟限制,預設不限制 | | memory.stat | 顯示當前CGroup中記憶體使用情況 | | memory.swappiness | 設定/顯示vmscan的swappiness引數(參考sysctl的vm.swappiness) | | memory.usage_in_bytes | 顯示當前記憶體使用情況 | | memory.use_hierarchy | 如果該值為0,將會統計到root cgroup裡;如果值為1,則統計到它的父cgroup裡面 | | notify_on_release | 是否在cgroup中最後一個任務退出時通知執行release agent,預設情況下是0,表示不執行。(release_agent在CGroup最頂層的目錄) | | tasks | 控制的程序組(這裡看不到對應程序,需要進入到子group中檢視) | > 不涉及核心記憶體(memory.kmem.\*)和swap分割槽記憶體(memory.memsw.\*),這裡就不詳細介紹 主要關注這幾個檔案 1. `8c9adca52f2bfd9e1862f6abaac5b435bdaf233925b7d640edb28e36da0b9b39`和`ca20137f7d5a42910e22425cca06443c16246c90ee9caa27814442e34a832b21`:分別對應的是pod執行的主容器ID和pause容器ID 2. `memory.usage_in_bytes`已使用的記憶體,例如我這裡檢視的結果是160817152,也就是153MB左右 ```bash # cat memory.usage_in_bytes 160382976 ``` 使用kubect top命令檢視使用情況 ```bash # kubectl top pod NAME CPU(cores) MEMORY(bytes) stress-memory 13m 151Mi ``` 3. `memory.limit_in_bytes`:記憶體限制的最大值,等於我們設定的記憶體limit的值 ```bash # cat memory.limit_in_bytes 160587776 ``` 4. `memory.max_usage_in_bytes`:歷史記憶體最大使用量,再檢視一下該CGroup下記憶體歷史最大使用量,正好200M ```bash # cat memory.max_usage_in_bytes 209715200 ``` ### 建立一個記憶體使用超出limit的pod 這時候我們將記憶體使用設定到250M,超出200M的限制 ```yaml apiVersion: v1 kind: Pod metadata: name: stress-memory2 namespace: default spec: containers: - name: stress image: polinux/stress imagePullPolicy: IfNotPresent command: ["stress"] args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"] ## 修改為250M,也就是分配250M記憶體 resources: limits: memory: "200Mi" requests: memory: "100Mi" nodeName: node01 ``` 執行`kubectl create`命令,執行這個pod ``` kubectl create -f pod-memory-2.yaml ``` 檢視pod狀態 ```yaml # kubectl get pod NAME READY STATUS RESTARTS AGE stress-memory 1/1 Running 1 10m6s stress-memory2 0/1 OOMKilled 2 26s ``` 此時會發現,pod在不斷重啟,並且**stress-memory2**這個pod的狀態為OOMKilled,這個是怎麼回事呢,我們可以進到pod對應的CGroup下檢視記憶體使用情況,我們繼續看一下目前pod的狀態 ```bash kubectl get pod stress-memory2 -oyaml ``` ```yaml ... ... status: ... ... lastState: terminated: containerID: docker://a7d686a3b56aa03b66fd4fed07217693d8e41d75529c02bae34769dca6f01f9e exitCode: 1 finishedAt: "2021-01-18T14:13:21Z" reason: OOMKilled startedAt: "2021-01-18T14:13:21Z" ``` 可以看到pod退出的原因是OOMKilled,什麼是OOMKilled呢?簡單來說,就是當程序申請的記憶體超出了已有的記憶體資源,那麼為了保證主機的穩定執行,就會基於程序oom_score的值,有選擇性的殺死某個程序。也就是說,在這個例子中,pod申請的記憶體為250Mi,超過了限制的200Mi,那麼就會從該程序所在的CGroup中,殺死對應的程序,具體我們可以看一下該CGroup中記憶體情況: 通過`kubectl get pod stress-memory2 -oyaml`命令,獲取pod uid,進入到對應的CGroup ```bash cd /sys/fs/cgroup/memory/kubepods/burstable/pod92c2a4c2-3b5c-4a9a-8a00-5d59575e96e7/ ``` 首先看一下記憶體限制是多少 ```bash # cat memory.limit_in_bytes 209715200 ``` 再檢視一下記憶體使用量,只有1M左右,這是因為此時pod狀態不是執行狀態 ```bash # cat memory.usage_in_bytes 1093632 ``` 再檢視一下該CGroup下記憶體歷史最大使用量,正好200M ```bash # cat memory.max_usage_in_bytes 209715200 ``` 此時我們再看看記憶體使用量達到限制值的次數 ```bash # cat memory.failcnt 531 ``` 從以上資訊可以得知,記憶體不斷申請超出記憶體限制的值,導致程序被kill,最終導致pod退出 ### 只設置request 設定request=100M,不設定limit,並設定pod使用記憶體150M ```yaml apiVersion: v1 kind: Pod metadata: name: stress-memory4 namespace: default spec: containers: - name: stress image: polinux/stress imagePullPolicy: IfNotPresent command: ["stress"] args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"] resources: requests: memory: "100Mi" nodeName: node01 ``` 執行`kubectl create`命令,執行這個pod ``` kubectl create -f pod-memory-4.yaml ``` 檢視pod狀態 ```yaml # kubectl get pod NAME READY STATUS RESTARTS AGE stress-memory3 1/1 Running 0 79s ``` 檢視pod記憶體使用 ```bash # kubectl top pod NAME CPU(cores) MEMORY(bytes) stress-memory3 51m 150Mi ``` 可以發現,pod記憶體使用時150Mi,說明只設置記憶體request,並不會對其限制其記憶體的使用 > **注意:**如果只設置limit,則request=limit ### 記憶體資源限制的目的 - 如果沒有指定記憶體限制,則容器可以無限制的使用主機記憶體資源,當使用完主機的所有可用資源後,就會導致該節點呼叫OOMkilled,此時沒有設定記憶體限制的pod對應的程序的score會更大,所以被kill的可能性更大。 - 為叢集中的pod設定記憶體請求和限制,可以有效的利用叢集上的記憶體資源,防止應用突發高峰期的時候記憶體猛漲影響其他應用的穩定執行 ## CPU限制 ### pod示例 首先建立一個pod,並設定CPU限制 ```yaml resources: limits: cpu: "1" requests: cpu: "0.5" ``` > CPU單位:小數值是可以使用。一個請求 0.5 CPU 的容器保證會獲得請求 1 個 CPU 的容器的 CPU 的一半。 可以使用字尾 `m` 表示毫。例如 `100m` CPU、100 milliCPU 和 0.1 CPU 都相同。 精度不能超過 1m。 完整yaml參考 ```yaml apiVersion: v1 kind: Pod metadata: name: stress-cpu spec: containers: - name: stress-cpu image: vish/stress resources: limits: cpu: "0.9" requests: cpu: "0.5" args: - -cpus - "2" nodeName: node01 ## 這裡指定排程到某個節點上,方便接下來的操作 ``` 這裡設定了CPU的limit為"1",CPU的request為"0.5",並通過stress指定分配2個程序去跑滿2個CPU 接著我們執行該yaml ```bash kubectl create -f stress-cpu.yaml ``` 等到pod執行起來後,我們看一下pod的詳細資訊 ```bash kubectl get pod stress-cpu -oyaml ``` 輸出結果如下 ```yaml ... ... name: stress-cpu namespace: default ... ... uid: 10929272-932f-4b4d-85c8-046a9f0e39d8 ... ... resources: limits: cpu: 900m requests: cpu: 500m ... ... status: ... ... qosClass: Burstable ... ... ``` 結合之前的記憶體相關的實驗,從輸出的結果中可以得知pod uid為**10929272-932f-4b4d-85c8-046a9f0e39d8**,對應的CGroup路徑為`kubepods/Burstable` 在CGroup中可以看到cpu、cpuset、cpuacct三種跟CPU相關的CGroup,其中`cpu`用於對cpu使用率的劃分;`cpuset`用於設定cpu的親和性等,主要用於numa架構的os;`cpuacct`記錄了cpu的部分資訊。通過定義可以得知,k8s CPU限制在`cpu`這個目錄中,我們進入到對應的pod的CGroup空間下,檢視CGroup相關檔案是如何工作的 ```bash # cd /sys/fs/cgroup/cpu/kubepods/burstable/pod10929272-932f-4b4d-85c8-046a9f0e39d8/ ls -1 ``` ```bash cgroup.clone_children cgroup.procs cpuacct.stat cpuacct.usage cpuacct.usage_percpu cpu.cfs_period_us cpu.cfs_quota_us cpu.rt_period_us cpu.rt_runtime_us cpu.shares cpu.stat d5b407752d32b7cd8937eb6a221b6f013522d00bb237134017ae5d8324ce9e30 eba8c20349137130cdc0af0e9db2a086f6ea5d6f37ad118394b838adfc1325bd notify_on_release tasks ``` 相關檔案作用如下: | 檔案 | 作用 | | ----------------------- | ------------------------------------------------------------ | | `cgroup.clone_children` | 子cgroup是否會繼承父cgroup的配置,預設是0 | | `cgroup.procs` | 樹中當前節點的cgroup中的程序組ID,現在我們在根節點,這個檔案中是會有現在系統中所有程序組ID | | `cpu.cfs_period_us` | 統計CPU使用時間的週期,單位是微秒(us)。
例如如果設定該CGroup中的程序訪問CPU的限制為每1秒訪問單個CPU0.2秒,則需要設定`cpu.cfs_period_us=200000`,`cpu.cfs_quota_us=1000000`
| | `cpu.cfs_quota_us` | 週期內允許佔用的CPU時間,即CGroup中的程序能使用cpu的最大時間,該值是硬限(-1為不限制)
一旦cgroup中的任務用完了配額指定的所有時間,它們就會在該時間段指定的剩餘時間中受到限制,並且直到下一個時間段才允許執行。`cpu.cfs_quota_us`引數的上限為1秒,下限為1000微秒 | | `cpu.rt_period_us` | 僅適用於實時排程任務,CPU使用時間的週期,單位是微秒(us),預設是1s(1000000us) | | `cpu.rt_runtime_us` | 僅適用於實時排程任務,此引數指定cgroup中的任務可以訪問CPU資源的最長連續時間 | | `cpu.shares` | `cpu.shares`是軟限制,理解為CPU的相對權重。
例如,A、B、C三個程序的權重為100、200、300,那麼在CPU滿載的情況下,A程序最多使用1/6 CPU時間片;而如果CPU不是滿載的情況,則各個程序允許使用超出相對權重的大小的CPU時間 | | `cpu.stat` | CPU時間統計資訊,其中:
`nr_periods`—已經過去的週期間隔數(在cpu.cfs_period_us中指定)
`nr_throttled` — cgroup中的任務被限制的次數(即,由於它們已經用盡了配額所指定的所有可用時間,因此不允許執行)
`throttled_time` — cgroup中的任務已被限制的總持續時間(以納秒為單位) | > 不涉及到cpuacct,這裡就不詳細介紹 在CPU的CGroup中,可以使用兩個排程程式來排程對CPU資源的訪問: - `CFS`:完全公平排程程式,比例共享排程程式,根據任務或分配給cgroup的優先順序/權重,在任務組(cgroup)之間按比例劃分CPU時間。 - `RT`:實時排程程式,一種任務排程程式,它提供一種方法來指定實時任務可以使用的CPU時間。(不做討論) 這裡主要討論k8s是如何設定CFS排程演算法去限制程序對CPU使用,主要關注這幾個檔案 1. `cpu.cfs_period_us`和`cpu.cfs_quota_us`,由這兩個檔案組成CPU硬限制,在這個例子中,我們設定CPUlimit的值為900m,所以`cfs_quota_us/cfs_period_us`等於`90000/100000`,也就是0.9個CPU ```bash # cat cpu.cfs_period_us 100000 # cat cpu.cfs_quota_us 90000 ``` 2. `cpu.shares`,CPU軟限制,在這個例子中我們設定了CPU request為500m,可以看出,CPU request對應了`cpu.shares`(此時軟限制不會起到作用) ```bash # cat cpu.shares 512 ``` 此時檢視podCPU使用情況,可以看到確實已經被限制住了 ```bash # kubectl top pod NAME CPU(cores) MEMORY(bytes) stress-cpu 902m 0Mi ``` ### request之CPU軟限制 在前面講記憶體限制的時候說到,如果只設置request,則limit沒有限制。 如果CPU只設置request,此時limit也是沒有限制。但是不同於記憶體,CPU request的值會設定到`cpu.shares`中,也就是說,只設置了request的pod,會有一個CPU軟限制。 此時正常情況下,當節點CPU資源充足的時候,設定了request的pod,還是可以正常的請求超出request值的CPU資源,可是當節點可分配的CPU資源不足時,那麼CPU軟限制就會起到作用,限制pod對CPU的訪問。 下面我們測試一下CPU軟限制是如何生效的 檢視節點可分配的CPU大小 ```bash kubectl describe node node01 ``` ```bash ... ... Allocatable: cpu: 4 ephemeral-storage: 57971659066 hugepages-2Mi: 0 memory: 8071996Ki pods: 110 ... ... Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 380m (9%) 10m (0%) memory 100Mi (1%) 190Mi (2%) ephemeral-storage 0 (0%) 0 (0%) ``` 目前該節點可用CPU數量為4000m,已使用了380m,也就是剩餘可用CPU為3620m 我們先建立一個CPU,設定request為0.5的pod,並通過stress指定分配2個程序去跑滿2個CPU ```yaml kind: Pod metadata: name: stress-cpu-1 spec: containers: - name: stress-cpu image: vish/stress resources: requests: cpu: "0.5" args: - -cpus - "2" ``` 執行`kubectl create`命令,執行這個pod ``` kubectl create -f stress-cpu-1.yaml ``` 檢視pod狀態 ```yaml # kubectl get pod NAME READY STATUS RESTARTS AGE stress-cpu 1/1 Running 0 5s ``` 檢視podCPU使用情況 ```bash # kubectl top pod NAME CPU(cores) MEMORY(bytes) stress-cpu 2001m 0Mi ``` 也可以使用top命令實時檢視程序CPU使用情況 ```bash # top PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 30285 root 20 0 5824 3692 3120 R 200.0 0.0 56:18.95 stress ``` 此時我們可以看到,我們程序的CPU使用率達到了2001m,超過了request設定的值 這時候我們再啟動一個pod,設定request為2.5的pod,並通過stress指定分配2個程序去跑滿3個CPU ```yaml apiVersion: v1 kind: Pod metadata: name: stress-cpu-2 spec: containers: - name: stress-cpu image: vish/stress resources: requests: cpu: "2.5" args: - -cpus - "3" ``` 執行`kubectl create`命令,執行這個pod ``` kubectl create -f stress-cpu-2.yaml ``` 檢視pod狀態 ```yaml # kubectl get pod NAME READY STATUS RESTARTS AGE stress-cpu 1/1 Running 0 15m stress-cpu-2 1/1 Running 0 8s ``` 檢視podCPU使用情況,此時由於pod佔用了大量的CPU資源,執行`kubectl`會卡住無法執行,可以通過`top`命令檢視CPU使用情況 ```bash # top PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 20732 root 20 0 5568 3688 3120 R 300.0 0.0 6:04.80 stress 30285 root 20 0 5824 3692 3120 R 96.2 0.0 63:57.08 stress ``` 我們再來回顧一下,首先這臺主機可用CPU資源3620m,總的CPU資源為4個CPU(4000m),其中380m的CPU資源已分配給其他pod使用,但是並沒有滿負載,所以總的資源我們可以按照4個CPU來算。 這裡可以看到PID為30285的程序是之前我們建立的pod **stress-cpu**,當時設定的request是0.5(500m),並指定分配2個程序去跑滿2個CPU(2000m),也就是軟限制在資源充足的情況下,使用率為200%(2000m),超過了CPUrequest軟限制。 後面我們又建立了一個request為2.5(2500m),並指定分配3個程序去跑滿3個CPU(3000m)的pod **stress-cpu-2**,此時CPU總的CPU使用需求為2+3=5CPU(5000m),但是CPU的總資源只有4CPU(4000m),此時CPU軟限制就會開始發揮作用。 我們檢視一下對應的`cpu.shares`,**stress-cpu** pod 的`cpu.shares`的值為512,**stress-cpu-2** pod 的`cpu.shares`的值為2560。 `cpu.shares`是軟限制,理解為CPU的相對權重。根據之前表格中的演算法,那麼當CPU滿負載的情況下(此時假設只有這兩個pod正在滿負載的執行): - **stress-cpu**可以使用`4 x 512/(512+2560)≈0.666`(666m CPU) - **stress-cpu-2**可以使用`4 x 2560/(512+2560)≈3.333`(3333m CPU) 由這個公式可以得知,在軟限制的前提下,**stress-cpu-2**可以跑滿3333m CPU,但是我們只分配了3個程序去跑滿CPU,也就是說,實際上**stress-cpu-2**可以跑到3000mCPU ,正好符合我們使用top看到的資料,佔用了300%(3000m),還剩餘333m左右的CPU資源未使用,而這部分的資源可以被其他程序使用。那麼可以思考一下,**stress-cpu**可以使用多少CPU資源? 其實從上面top命令的結果就可以知道,**stress-cpu**使用了近1000mCPU的資源,這是因為,當CPU滿負載時,會相應的分配666mCPU的資源,此時**stress-cpu-2**並沒有完全使用完,還剩餘333mCPU未被使用,那麼由於軟限制的作用下,實際上是可以使用這部分未被使用的資源,也就是說,**stress-cpu**可以使用`666m + 333m = 999m`(CPU),也就是符合使用top命令看到的96%CPU使用率。 ### CPU資源限制的目的 - 如果沒有指定CPU限制,則容器可以無限制的使用主機CPU資源。為叢集中的pod設定CPU請求和限制,可以有效的利用叢集上的CPU資源,防止應用突發高峰期的時候CPU猛漲影響其他應用的穩定執行 ## QoS 前面我們講了如何通過設定資源的request和limit來限制pod對主機資源的使用,在前面幾個例子中,我們看到,當我們設定了資源配額時,檢視pod yaml可以看到`qosClass`的值為 Burstable,這是因為在k8s中,會為pod設定不同的QoS型別,以保證pod的資源可用,其中QoS有三個型別: - `Guaranteed`:Pod中的所有容器(包括init容器)都必須設定記憶體和CPU的請求(limit)和限制(request),且請求和限制的值要相等。(如果只設置limit,則預設request=limit); - `Burstable`:Pod中至少有一個容器設定了記憶體和CPU請求,且不符合Guaranteed QoS型別的標準; - `BestEffort`:Pod中所有的容器都沒有設定任何記憶體和CPU的限制和請求。 即會根據對應的請求和限制來設定QoS等級,接下來我們分別建立對應的QoS等級來感受一下 ### Guaranteed 定義:Pod中的所有容器(包括init容器)都必須設定記憶體和CPU的請求(limit)和限制(request),且請求和限制的值要相等。其中如果只設置limit,則預設request=limit 舉個例子:建立一個包含記憶體和CPU請求和限制的pod,其中記憶體的請求和限制都為300Mi,CPU的請求和限制都為500m ```yaml apiVersion: v1 kind: Pod metadata: name: pod-guaranteed spec: containers: - name: pod-guaranteed image: zerchin/network resources: limits: memory: "300Mi" cpu: "500m" requests: memory: "300Mi" cpu: "500m" ``` 建立pod ```bash kubectl create -f pod-guaranteed.yaml ``` 檢視pod 詳細資訊 ```bash kubectl get pod pod-guaranteed -oyaml ``` 輸出結果如下,可以看到pod的qosClass為**Guaranteed** ```yaml ... ... name: pod-guaranteed uid: 494e608c-d63e-41a9-925c-3ac7acf7b465 ... ... limits: cpu: 500m memory: 300Mi requests: cpu: 500m memory: 300Mi ... ... status: qosClass: Guaranteed ``` 根據前面的經驗,這裡**Guaranteed**對應的就是pod在CGroup中的路徑,對應的CGroup路徑如下: - CPU:`/sys/fs/cgroup/memory/kubepods/pod494e608c-d63e-41a9-925c-3ac7acf7b465` - 記憶體:`/sys/fs/cgroup/cpu/kubepods/pod494e608c-d63e-41a9-925c-3ac7acf7b465` ### Burstable 定義:Pod中至少有一個容器設定了記憶體和CPU請求,且不符合Guaranteed QoS型別的標準; 回顧一下我們前面我們在瞭解記憶體和CPU限制的時候,建立的pod都是**Burstable** Qos型別,包括: - 單一設定記憶體或CPU的request - 同時設定了記憶體或CPU的request和limit,且request≠limit - 同時設定了記憶體或CPU的request和limit,且request=limit 上述這些都是pod中只含有單個容器,還有一種情況就是單個pod包含多個容器,如果一個容器指定了資源請求,另一個容器沒有指定任何請求和限制,則也是屬於**Burstable** Qos型別 舉個例子:建立一個多容器的pod,其中一個容器設定了記憶體和CPU的請求和限制且值相等,另一個容器不限制資源 ```yaml apiVersion: v1 kind: Pod metadata: name: pod-burstable spec: containers: - name: pod-burstable-1 image: zerchin/network resources: limits: memory: "300Mi" cpu: "500m" requests: memory: "300Mi" cpu: "500m" - name: pod-burstable-2 image: busybox:1.28 args: ["sh", "-c", "sleep 3600"] ``` 建立pod ```bash kubectl create -f pod-burstable.yaml ``` 檢視pod 詳細資訊 ```bash kubectl get pod pod-burstable -oyaml ``` 輸出結果如下,可以看到pod的qosClass為**Guaranteed** ```yaml ... ... name: pod-burstable uid: 7d858afc-f88a-454e-85dc-81670a0ddb8b ... ... status: qosClass: Burstable ... ... ``` ### BestEffort 定義:pod中所有的容器都沒有設定任何記憶體和CPU的限制和請求。 這個很好理解,我們建立個pod試試 ```yaml apiVersion: v1 kind: Pod metadata: name: pod-besteffort spec: containers: - name: pod-besteffort image: zerchin/network ``` 建立pod ```bash kubectl create -f pod-besteffort.yaml ``` 檢視pod 詳細資訊 ```bash kubectl get pod pod-besteffort -oyaml ``` 輸出結果如下,可以看到pod的qosClass為**BestEffort** ```yaml ... ... name: pod-besteffort uid: 1316dbf7-01ed-415b-b901-2be9d650163c ... ... status: qosClass: BestEffort ``` 那麼,在CGroup中對應的路徑為`kubepods/besteffort/pod1316dbf7-01ed-415b-b901-2be9d650163c` ### QoS優先順序 當叢集資源被耗盡時,容器會被殺死,此時會根據QoS優先順序對pod進行處理,即優先順序高的會盡量被保護,而優先順序低的會優先被殺死 三種Qos型別優先順序(由高到低):Guaranteed > Burstable > BestEffort 其中CPU屬於可壓縮資源,而記憶體屬於不可壓縮資源,這裡我們主要討論一下當記憶體耗盡時,是如何根據QoS優先順序處理pod - `BestEffort`:`BestEffort`型別的pod沒有設定資源限制,此類pod被認為是最低優先順序,當系統記憶體不足時,這些pod會首先被殺死 - `Burstable `:`Burstable `型別的pod設定有部分資源的請求和限制,此類pod的優先順序高於`BestEffort`型別的pod,當系統記憶體不足且系統中不存在`BestEffort`型別的pod時才會被殺死 - `Guaranteed `:`Guaranteed`型別的pod同時設定了CPU和記憶體的資源限制和請求,此類pod的優先順序最高,只有在系統記憶體不足且系統系統中不存在`BestEffort`和`Burstable `的pod時才會被殺死 ### OOM score 在前面講記憶體限制時提到過,就是當程序申請的記憶體超出了已有的記憶體資源,那麼為了保證主機的穩定執行,就會基於程序`oom_score`的值,有選擇性的殺死某個程序,這個過程就是OOMKilled。 這裡我們先了解一下什麼是`oom_score`: 當系統記憶體資源被耗盡時,就需要釋放部分記憶體保證主機的執行。而記憶體是被程序所佔用的,所以釋放記憶體實際上就是要殺死程序。那麼系統是如何選擇程序進行殺死的呢?答案就是基於`oom_score`的值選擇可以殺死的程序。oom_score的值在0-1000範圍,其中`oom_score`的值越高,則被殺死的可能性越大。 `oom_score`的值還會受到`oom_score_adj`影響,最後的得分會加上`oom_score_adj`的值,也就是說,可以通過設定`oom_score_adj`的大小從而影響最終`oom_score`的大小。 其中正常情況下,`oom_score`是該程序消耗的記憶體百分比的10倍,通過`oom_score_adj`進行調整,例如如果某個程序使用了100%的記憶體,則得分為1000;如果使用0%的記憶體,則得分為0(其他例如root使用者啟動的程序會減去30這裡不討論) 那麼我們來看看不同的QoS型別的score值是如何設定的 - `BestEffort`:由於它的優先順序最低,為了保證`BestEffort`型別的pod最先被殺死,所以設定`oom_score_adj`為1000,那麼`BestEffort`型別pod的`oom_score`值為1000 - `Guaranteed`:由於它的優先順序最高,為了保證`Guaranteed`型別的pod的不被殺死,所以設定`oom_score_adj`為-998,那麼`Guaranteed`型別pod的`oom_score`值為0或者1 - `Burstable`:這裡要分幾種情況討論 - 如果總記憶體請求 > 可用記憶體的99.8%,則設定`oom_score_adj`的值為2,否則將`oom_score_adj`設定為`1000 - 10 x 記憶體請求的百分比`,這樣可以確保`Burstable`型別的pod的`oom_score` > 1。 - 如果記憶體請求為0,則設定`oom_score_adj`的值為999。所以,如果`Burstable`型別的pod和`Guaranteed`型別的發生衝突時,保證`Burstable`型別的pod被殺死。 - 如果`Burstable`型別的pod使用的記憶體少於請求的記憶體,則其`oom_score`<1000,因此,如果`BestEffort`型別的pod與使用少於請求記憶體的`Burstable`型別的pod發生衝突時,則`BestEffort`型別的pod將被殺死 - 如果`Burstable`型別的pod使用的記憶體大於記憶體請求時,`oom_score`=1000,否則`oom_score`<1000 - 如果一個使用的記憶體多於請求記憶體的`Burstable`型別的pod,與另一個使用的記憶體少於請求記憶體的`Burstable`型別的pod發生衝突時,則前者將被殺死 - 如果`Burstable`型別的pod與多個程序發生衝突,則OOM分數的計算公式是一種啟發式的,它不能保證“請求和限制”的保證 infra 容器(pause)或init 容器,`oom_score_adj`為-998 預設kubelet和docker的`oom_score_adj`為-999 基於上述oom_score,就能保證當系統資源耗盡時,首先被殺死的是`BestEffort`型別的pod,其次是`Burstable`型別的pod,最後才是`Guaranteed`類