深刻理解 Kubernetes 資源限制:CPU

在關於 Kubernetes 資源限制的系列文章的第一篇文章中,我討論瞭如何使用 ResourceRequirements 對象來設置 Pod 中容器的內存資源限制,以及如何經過容器運行時和 linux control group(cgroup)來實現這些限制。我還談到了 Requests 和 Limits 之間的區別,其中 Requests 用於在調度時通知調度器 Pod 須要多少資源才能調度,而 Limits 用來告訴 Linux 內核何時你的進程能夠爲了清理空間而被殺死。在這篇文章中,我會繼續仔細分析 CPU 資源限制。想要理解這篇文章所說的內容,不必定要先閱讀上一篇文章,但我建議那些工程師和集羣管理員最好仍是先閱讀完第一篇,以便全面掌控你的集羣。html

1. CPU 限制


正如我在上一篇文章中提到的,CPU 資源限制比內存資源限制更復雜,緣由將在下文詳述。幸運的是 CPU 資源限制和內存資源限制同樣都是由 cgroup 控制的,上文中提到的思路和工具在這裏一樣適用,咱們只須要關注他們的不一樣點就好了。首先,讓咱們將 CPU 資源限制添加到以前示例中的 yaml:node

resources:
  requests:
    memory: 50Mi
    cpu: 50m
  limits:
    memory: 100Mi
    cpu: 100m

Copylinux

單位後綴 m 表示千分之一核,也就是說 1 Core = 1000m。所以該資源對象指定容器進程須要 50/1000 核(5%)才能被調度,而且容許最多使用 100/1000 核(10%)。一樣,2000m 表示兩個完整的 CPU 核心,你也能夠寫成 2 或者 2.0。爲了瞭解 Docker 和 cgroup 如何使用這些值來控制容器,咱們首先建立一個只配置了 CPU requests 的 Pod:算法

$ kubectl run limit-test --image=busybox --requests "cpu=50m" --command -- /bin/sh -c "while true; do sleep 2; done"

deployment.apps "limit-test" created

Copydocker

經過 kubectl 命令咱們能夠驗證這個 Pod 配置了 50m 的 CPU requests:json

$ kubectl get pods limit-test-5b4c495556-p2xkr -o=jsonpath='{.spec.containers[0].resources}'

map[requests:map[cpu:50m]]

Copyapi

咱們還能夠看到 Docker 爲容器配置了相同的資源限制:app

$ docker ps | grep busy | cut -d' ' -f1
f2321226620e

$ docker inspect f2321226620e --format '{{.HostConfig.CpuShares}}'
51

Copyide

這裏顯示的爲何是 51,而不是 50?這是由於 Linux cgroup 和 Docker 都將 CPU 核心數分紅了 1024 個時間片(shares),而 Kubernetes 將它分紅了 1000 個 shares。工具

shares 用來設置 CPU 的相對值,而且是針對全部的 CPU(內核),默認值是 1024,假如系統中有兩個 cgroup,分別是 A 和 B,A 的 shares 值是 1024,B 的 shares 值是 512,那麼 A 將得到 1024/(1204+512)=66% 的 CPU 資源,而 B 將得到 33% 的 CPU 資源。shares 有兩個特色:

 

  • 若是 A 不忙,沒有使用到 66% 的 CPU 時間,那麼剩餘的 CPU 時間將會被系統分配給 B,即 B 的 CPU 使用率能夠超過 33%。
  • 若是添加了一個新的 cgroup C,且它的 shares 值是 1024,那麼 A 的限額變成了 1024/(1204+512+1024)=40%,B 的變成了 20%。

  • 從上面兩個特色能夠看出:

     

     

  • 在閒的時候,shares 基本上不起做用,只有在 CPU 忙的時候起做用,這是一個優勢。
  • 因爲 shares 是一個絕對值,須要和其它 cgroup 的值進行比較才能獲得本身的相對限額,而在一個部署不少容器的機器上,cgroup 的數量是變化的,因此這個限額也是變化的,本身設置了一個高的值,但別人可能設置了一個更高的值,因此這個功能無法精確的控制 CPU 使用率。

 

與配置內存資源限制時 Docker 配置容器進程的內存 cgroup 的方式相同,設置 CPU 資源限制時 Docker 會配置容器進程的 cpu,cpuacct cgroup:

$ ps ax | grep /bin/sh
   60554 ?      Ss     0:00 /bin/sh -c while true; do sleep 2; done

$ sudo cat /proc/60554/cgroup
...
4:cpu,cpuacct:/kubepods/burstable/pode12b33b1-db07-11e8-b1e1-42010a800070/3be263e7a8372b12d2f8f8f9b4251f110b79c2a3bb9e6857b2f1473e640e8e75

$ ls -l /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pode12b33b1-db07-11e8-b1e1-42010a800070/3be263e7a8372b12d2f8f8f9b4251f110b79c2a3bb9e6857b2f1473e640e8e75
total 0
drwxr-xr-x 2 root root 0 Oct 28 23:19 .
drwxr-xr-x 4 root root 0 Oct 28 23:19 ..
...
-rw-r--r-- 1 root root 0 Oct 28 23:19 cpu.shares

Copy

Docker 容器的 HostConfig.CpuShares 屬性映射到 cgroup 的 cpu.shares 屬性,能夠驗證一下:

$ sudo cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/podb5c03ddf-db10-11e8-b1e1-42010a800070/64b5f1b636dafe6635ddd321c5b36854a8add51931c7117025a694281fb11444/cpu.shares

51

Copy

你可能會很驚訝,設置了 CPU requests 居然會把值傳播到 cgroup,而在上一篇文章中咱們設置內存 requests 時並無將值傳播到 cgroup。這是由於內存的 soft limit 內核特性對 Kubernetes 不起做用,而設置了 cpu.shares 卻對 Kubernetes 頗有用。後面我會詳細討論爲何會這樣。如今讓咱們先看看設置 CPU limits 時會發生什麼:

$ kubectl run limit-test --image=busybox --requests "cpu=50m" --limits "cpu=100m" --command -- /bin/sh -c "while true; do
sleep 2; done"

deployment.apps "limit-test" created

Copy

再一次使用 kubectl 驗證咱們的資源配置:

$ kubectl get pods limit-test-5b4fb64549-qpd4n -o=jsonpath='{.spec.containers[0].resources}'

map[limits:map[cpu:100m] requests:map[cpu:50m]]

Copy

查看對應的 Docker 容器的配置:

$ docker ps | grep busy | cut -d' ' -f1
f2321226620e
$ docker inspect 472abbce32a5 --format '{{.HostConfig.CpuShares}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'
51 10000 100000

Copy

能夠明顯看出,CPU requests 對應於 Docker 容器的 HostConfig.CpuShares 屬性。而 CPU limits 就不太明顯了,它由兩個屬性控制:HostConfig.CpuPeriod 和 HostConfig.CpuQuota。Docker 容器中的這兩個屬性又會映射到進程的 cpu,couacct cgroup 的另外兩個屬性:cpu.cfs_period_us 和 cpu.cfs_quota_us。咱們來看一下:

$ sudo cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod2f1b50b6-db13-11e8-b1e1-42010a800070/f0845c65c3073e0b7b0b95ce0c1eb27f69d12b1fe2382b50096c4b59e78cdf71/cpu.cfs_period_us
100000

$ sudo cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod2f1b50b6-db13-11e8-b1e1-42010a800070/f0845c65c3073e0b7b0b95ce0c1eb27f69d12b1fe2382b50096c4b59e78cdf71/cpu.cfs_quota_us
10000

Copy

如我所說,這些值與容器配置中指定的值相同。可是這兩個屬性的值是如何從咱們在 Pod 中設置的 100m cpu limits 得出的呢,他們是如何實現該 limits 的呢?這是由於 cpu requests 和 cpu limits 是使用兩個獨立的控制系統來實現的。Requests 使用的是 cpu shares 系統,cpu shares 將每一個 CPU 核心劃分爲 1024 個時間片,並保證每一個進程將得到固定比例份額的時間片。若是總共有 1024 個時間片,而且兩個進程中的每個都將 cpu.shares 設置爲 512,那麼它們將分別得到大約一半的 CPU 可用時間。但 cpu shares 系統沒法精確控制 CPU 使用率的上限,若是一個進程沒有設置 shares,則另外一個進程可用自由使用 CPU 資源。

大約在 2010 年左右,谷歌團隊和其餘一部分人注意到了這個問題。爲了解決這個問題,後來在 linux 內核中增長了第二個功能更強大的控制系統:CPU 帶寬控制組。帶寬控制組定義了一個 週期,一般爲 1/10 秒(即 100000 微秒)。還定義了一個 配額,表示容許進程在設置的週期長度內所能使用的 CPU 時間數,兩個文件配合起來設置CPU的使用上限。兩個文件的單位都是微秒(us),cfs_period_us 的取值範圍爲 1 毫秒(ms)到 1 秒(s),cfs_quota_us 的取值大於 1ms 便可,若是 cfs_quota_us 的值爲 -1(默認值),表示不受 CPU 時間的限制。

下面是幾個例子:

# 1.限制只能使用1個CPU(每250ms能使用250ms的CPU時間)
$ echo 250000 > cpu.cfs_quota_us /* quota = 250ms */
$ echo 250000 > cpu.cfs_period_us /* period = 250ms */

# 2.限制使用2個CPU(內核)(每500ms能使用1000ms的CPU時間,即便用兩個內核)
$ echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */
$ echo 500000 > cpu.cfs_period_us /* period = 500ms */

# 3.限制使用1個CPU的20%(每50ms能使用10ms的CPU時間,即便用一個CPU核心的20%)
$ echo 10000 > cpu.cfs_quota_us /* quota = 10ms */
$ echo 50000 > cpu.cfs_period_us /* period = 50ms */

Copy

在本例中咱們將 Pod 的 cpu limits 設置爲 100m,這表示 100/1000 個 CPU 核心,即 100000 微秒的 CPU 時間週期中的 10000。因此該 limits 翻譯到 cpu,cpuacct cgroup 中被設置爲 cpu.cfs_period_us=100000 和 cpu.cfs_quota_us=10000。順便說一下,其中的 cfs 表明 Completely Fair Scheduler(絕對公平調度),這是 Linux 系統中默認的 CPU 調度算法。還有一個實時調度算法,它也有本身相應的配額值。

如今讓咱們來總結一下:

  • 在 Kubernetes 中設置的 cpu requests 最終會被 cgroup 設置爲 cpu.shares 屬性的值, cpu limits 會被帶寬控制組設置爲 cpu.cfs_period_us 和 cpu.cfs_quota_us 屬性的值。與內存同樣,cpu requests 主要用於在調度時通知調度器節點上至少須要多少個 cpu shares 才能夠被調度。
  • 與 內存 requests 不一樣,設置了 cpu requests 會在 cgroup 中設置一個屬性,以確保內核會將該數量的 shares 分配給進程。
  • cpu limits 與 內存 limits 也有所不一樣。若是容器進程使用的內存資源超過了內存使用限制,那麼該進程將會成爲 oom-killing 的候選者。可是容器進程基本上永遠不能超過設置的 CPU 配額,因此容器永遠不會由於嘗試使用比分配的更多的 CPU 時間而被驅逐。系統會在調度程序中強制進行 CPU 資源限制,以確保進程不會超過這個限制。

若是你沒有在容器中設置這些屬性,或將他們設置爲不許確的值,會發生什麼呢?與內存同樣,若是隻設置了 limits 而沒有設置 requests,Kubernetes 會將 CPU 的 requests 設置爲 與 limits 的值同樣。若是你對你的工做負載所須要的 CPU 時間瞭如指掌,那再好不過了。若是隻設置了 CPU requests 卻沒有設置 CPU limits 會怎麼樣呢?這種狀況下,Kubernetes 會確保該 Pod 被調度到合適的節點,而且該節點的內核會確保節點上的可用 cpu shares 大於 Pod 請求的 cpu shares,可是你的進程不會被阻止使用超過所請求的 CPU 數量。既不設置 requests 也不設置 limits 是最糟糕的狀況:調度程序不知道容器須要什麼,而且進程對 cpu shares 的使用是無限制的,這可能會對 node 產生一些負面影響。

最後我還想告訴大家的是:爲每一個 pod 都手動配置這些參數是挺麻煩的事情,kubernetes 提供了 LimitRange 資源,可讓咱們配置某個 namespace 默認的 request 和 limit 值。

2. 默認限制


經過上文的討論你們已經知道了忽略資源限制會對 Pod 產生負面影響,所以你可能會想,若是可以配置某個 namespace 默認的 request 和 limit 值就行了,這樣每次建立新 Pod 都會默認加上這些限制。Kubernetes 容許咱們經過 LimitRange 資源對每一個命名空間設置資源限制。要建立默認的資源限制,須要在對應的命名空間中建立一個 LimitRange 資源。下面是一個例子:

apiVersion: v1
kind: LimitRange
metadata:
  name: default-limit
spec:
  limits:
  - default:
      memory: 100Mi
      cpu: 100m
    defaultRequest:
      memory: 50Mi
      cpu: 50m
  - max:
      memory: 512Mi
      cpu: 500m
  - min:
      memory: 50Mi
      cpu: 50m
    type: Container

Copy

這裏的幾個字段可能會讓大家有些困惑,我拆開來給大家分析一下。

  • limits 字段下面的 default 字段表示每一個 Pod 的默認的 limits 配置,因此任何沒有分配資源的 limits 的 Pod 都會被自動分配 100Mi limits 的內存和 100m limits 的 CPU。
  • defaultRequest 字段表示每一個 Pod 的默認 requests 配置,因此任何沒有分配資源的 requests 的 Pod 都會被自動分配 50Mi requests 的內存和 50m requests 的 CPU。
  • max 和 min 字段比較特殊,若是設置了這兩個字段,那麼只要這個命名空間中的 Pod 設置的 limits 和 requests超過了這個上限和下限,就不會容許這個 Pod 被建立。我暫時尚未發現這兩個字段的用途,若是你知道,歡迎在留言告訴我。

LimitRange 中設定的默認值最後由 Kubernetes 中的准入控制器 LimitRanger 插件來實現。准入控制器由一系列插件組成,它會在 API 接收對象以後建立 Pod 以前對 Pod 的 Spec 字段進行修改。對於 LimitRanger 插件來講,它會檢查每一個 Pod 是否設置了 limits 和 requests,若是沒有設置,就給它配置 LimitRange 中設定的默認值。經過檢查 Pod 中的 annotations 註釋,你能夠看到 LimitRanger 插件已經在你的 Pod 中設置了默認值。例如:

apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubernetes.io/limit-ranger: 'LimitRanger plugin set: cpu request for container
      limit-test'
  name: limit-test-859d78bc65-g6657
  namespace: default
spec:
  containers:
  - args:
    - /bin/sh
    - -c
    - while true; do sleep 2; done
    image: busybox
    imagePullPolicy: Always
    name: limit-test
    resources:
      requests:
        cpu: 100m

Copy

以上就是我對 Kubernetes 資源限制的所有看法,但願能對你有所幫助。若是你想了解更多關於 Kubernetes 中資源的 limits 和 requests、以及 linux cgroup 和內存管理的更多詳細信息,能夠查看我在文末提供的參考連接。

3. 參考資料


相關文章
相關標籤/搜索