深刻理解Kubernetes資源限制:CPU

上一篇關於Kubernetes資源限制的文章咱們討論瞭如何經過ResourceRequirements設置Pod中容器內存限制,以及容器運行時是如何利用Linux Cgroups實現這些限制的。也分析了requests是用來通知調度器Pod所需資源需求和limits是在宿主機遇到內存壓力時幫助內核限制資源兩者的區別。docker

在本文中,我會繼續深刻探討CPU時間的requests和limits。你是否閱讀過第一篇文章並不會影響本文的學習,可是我建議你兩篇文章都讀一讀,從而獲得工程師或者集羣管理員視角的集羣控制全景。 json

1.PNG

CPU時間

正如我在第一篇文章中指出,限制CPU時間要比限制內存限制更加複雜,好消息是限制CPU也是根據咱們前面所瞭解到的cgroups機制控制的,與限制內存的原理是通用的,咱們只須要關注一些細節便可。咱們從向前文的例子裏添加CPU時間限制開始: api

resources:  app

    requests:    學習

        memory: 50Mijsonp

        cpu: 50mui

    limits:    google

        memory: 100Mispa

        cpu: 100m插件

單位後綴m表示「千分之一個核心」,因此這個資源對象定義了容器進程須要50/1000的核心(5%),而且最多使用100/1000的核心(10%)。相似的,2000m表示2顆完整的核心,固然也能夠用2或者2.0來表示。讓咱們建立一個只擁有CPU requests的Pod,而後看看Docker是如何配置cgroups的: 

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

deployment.apps "limit-test" created

咱們可以看到Kubernetes已經配置了50m的CPU requests: 

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

[cpu:50m]]

咱們也能夠看到Docker配置了一樣的limits:  

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

f2321226620e

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

51

爲何是51而不是50?CPU cgroup和Docker都把一個核心劃分爲1024份,而Kubernetes則劃分爲1000份。那麼Docker如何把它應用到容器進程上?設置內存限制會讓Docker來配置進程的memory cgroup,一樣設置CPU限制會讓它配置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

Docker的HostConfig.CpuShares容器屬性映射到了cgroup的cpu.shares上,因此讓咱們看看: 

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

51 

你可能會驚奇地發現設置一個CPU請求會把這個值發送到cgroup去,而上篇文章中設置內存卻並不是如此。下面這行內核對內存軟限制的行爲對Kubernetes來講沒什麼用處,而設置了cpu.shares則是有用的。我等會會對此作出解釋。那麼當咱們設置cpu限制時發生了什麼?讓咱們一塊兒找找看: 

$ 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

如今咱們回過頭來看看Kubernetes Pod資源對象的限制: 

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

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

在Docker容器配置裏: 

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

f2321226620e

$ docker inspect 472a**e32a5 --format '{{.HostConfig.CpuShares}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}'

51 10000 100000

正如咱們所見,CPU請求存放在HostConfig.CpuShares屬性裏。CPU限制,儘管不是那麼明顯,它由HostConfig.CpuPeriod和HostConfig.CpuQuota兩個值表示,這些Docker容器配置映射爲進程的cpu, cpuacct 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

如咱們所料這兩個配置會一樣配置到Docker容器配置裏。可是這些值是怎麼從Pod的100m CPU限制裏轉換過來,而且是怎麼實現的呢?原來CPU requests和CPU limits是由兩套不一樣的cgroup分別進行控制的。Requests使用CPU分片系統,是兩者中出現較早的一個。Cpu分片是將每一個核心劃分爲1024份,而且保證每一個進程會接收到必定比例的CPU分片。若是隻有1024片而這兩個進程都設置cpu.shares爲512,那麼這兩個進程會各自獲得一半的CPU時間。CPU分片系統並不能指定上界,也就是說若是一個進程沒有使用它的這一份,其它進程是可使用的。

在2010年左右Google和一些公司注意到了這個可能存在的問題(https://ai.google/research/pubs/pub36669)。進而合併了一個更增強大的秒級響應的系統:CPU帶寬控制。帶寬控制系統定義了一個一般是1/10秒的週期,或者100000微秒,以及一個表示週期裏一個進程可使用的最大分片數配額。在這個例子裏,咱們爲咱們的Pod申請了100mCPU,它等價於100/1000的核心,或者10000/100000毫秒的CPU時間。因此咱們的CPU requests被翻譯爲設置這個進程的cpu,cpuacct的配置爲cpu.cfs_period_us=100000而且cpu.cfs_quota_us=10000。cfs表示徹底公平調度,它是Linux默認的CPU調度器。同時還有一個響應quota值的實時調度器 。

咱們爲Kubernetes設置CPU requests其實是設置了cpu.shares cgroup屬性,設置CPU limits配置了另外一個子系統的cpu.cfs_period_us和cpu.cfs_quota_us屬性。就像內存requests對調度器的意義同樣,CPU requests會讓調度器選擇至少擁有那麼多可用CPU分片的節點。不一樣於內存requests,設置CPU requests也會給cgroup設置相應的屬性,幫助內核實際給進程分配同樣數量的CPU核心分片。Limits的處理也與內存不同。超出內存limits會讓你的容器進程成爲oom-kill的選項,可是你的進程基本上不可能超出設置的cpu配額,而且永遠不會由於試着使用更多CPU而被驅逐。系統在調度器那裏增強了配額的使用,因此進程在到達limits後只會被限流。 

若是你並未爲你的容器設置這些屬性,或者給他們設置了不許確的值會怎麼樣?做爲內存,若是你設置了limits但並未指定requests,Kubernetes會默認讓request指向limit。若是你對你的應用須要多少CPU時間很清楚的話這沒問題。那麼若是設置requests而不設置limits呢?在這個場景裏Kubernetes仍然能夠精確地調度你的Pod,內核也會保證它能獲得須要的最少資源配額。可是不會限制你的進程只能使用requested數量的資源,它可能會偷取別的進程的分片。不設置requests和limits是最壞的狀況,調度器不知道容器須要多少資源,進程的CPU分片也是無限的,這也許會對節點帶來不利的影響。這引出了我想要說的最後一件事情:爲每一個namespace設置默認的的資源限制。 

默認限制

在瞭解到不爲Pod配置資源限制會有一些負面效應後,你可能會想到給它們設置默認值,因此每一個提交到集羣的Pod都會有一個默認設置。Kubernetes容許咱們這麼作:基於Namespace,使用v1版本的LimitRange API對象實現。你能夠經過在你想限制的Namespace裏建立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

這裏的命名可能會有些迷惑,讓咱們把它拆分開看看。limits下的default鍵表明了每種資源的默認limits。在這個場景裏,指定Namespace裏的任何沒有配置內存限制的Pod都會被設置一個默認100Mi的limits,任何沒有CPU限制的Pod會被設置一個默認100m的limits。defaultRequest鍵表示資源requests。若是建立了一個Pod沒有指定內存requests的Pod,它會被自動分配默認50Mi的內存,以及若是沒有指定CPU requests的話,會被默認分配默認50m的CPU。max和min鍵則有些不一樣:基本上若是一個Pod的requests或limits超過了這兩種規定的上下界,這個Pod就沒法提交經過建立。我目前尚未找到這種用法的場景,可是你可能會用到,因此若是有的話請你留言告訴咱們你用它解決了什麼問題。

默認的LimitRange設置經過LimitRange插件應用到Pod上,這個插件存在於Kubernetes Admission Controller裏。Admission Controller是可能會在對象被API接收以後,實際建立以前修改它定義的插件集合。在LimitRange場景裏,它會檢查每一個Pod,若是它沒有指明requests和limits,而且Namespace設置裏設置了默認值,它就會把這個默認值應用到Pod上。你會發現LimitRanger經過檢查Pod metadata的annotations裏來設置默認值。

相關文章
相關標籤/搜索