Kubernetes中資源限制的一些筆記整理

寫在前面
今天和小伙伴們分享K8s中pod資源限制的一些筆記
博文內存涉及pod中通過request和limits實現資源的申請和限制
理解不足小伙伴幫忙指正,生活加油 ^_^
我們的痛苦來源于“夸父追日”一般的對“更好”的追求,也來自于自己的自卑與狂妄。--------duoduokk

學習之前,我們要準備下實驗環(huán)境,新建一個新的命名空間,并且切換過去,提前拉一下實驗鏡像

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$mkdir resources
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cd resources/
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  create ns  resources
namespace/resources created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl config set-context  $(kubectl config current-context) --namespace=resources
Context "kubernetes-admin@kubernetes" modified.
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cd ..
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible node -m shell -a "docker pull hub.c.163.com/library/centos"
為什么要資源限制
一般在本地做實驗,或者Demo的時候,不會對pod的資源進行限制,只有在生產環(huán)境會嚴格配置pod的資源限制。

當不對pod的資源做限制時,K8s的調度器會認為當前pod所需要的資源很少,并且可以調度到任意節(jié)點上,但是這樣有一個很嚴重的弊端,如果不做資源限制,比如拿內存來講,如果pod中的容器存在內存不回收的情況,那么會無休止的使用宿主節(jié)點的內存資源,從而會引發(fā)宿主機的OOM,甚至會觸發(fā)宿主機內核OOM Killer(內存殺手),來保證宿主機不掛掉。

看一個Demo,創(chuàng)建一個沒有限制資源的pod

┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod-demo
  name: pod-demo
spec:
  containers:
  - image: hub.c.163.com/library/centos
    imagePullPolicy: IfNotPresent
    name: pod-demo
    command: ['sh','-c','sleep 500000']
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
pod創(chuàng)建成功后,我們可以看到調度到了vms83.liruilongs.github.io

┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods -o wide
No resources found in resources namespace.
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$vim  pod-demo.yaml
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  apply  -f pod-demo.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP             NODE                         NOMINATED NODE   READINESS GATES
pod-demo   1/1     Running   0          42s   10.244.70.50   vms83.liruilongs.github.io   <none>           <none>
這里我們在pod里安裝一個內存分配工具bigmem,用于模擬pod中容器進程內存不回收的情況。

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl  cp ./bigmem-7.0-1.r29766.x86_64.rpm pod-demo:/root/
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl  exec -it pod-demo  -- bin/bash
[root@pod-demo /]# cd root/
[root@pod-demo ~]# ls
anaconda-ks.cfg  bigmem-7.0-1.r29766.x86_64.rpm  original-ks.cfg
[root@pod-demo ~]# rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:bigmem-7.0-1.r29766              ################################# [100%]
[root@pod-demo ~]# bigmem 1000M
Attempting to allocate 1000 Mebibytes of resident memory...
Press <Enter> to exit^C
[root@pod-demo ~]# bigmem 2000M
Attempting to allocate 2000 Mebibytes of resident memory...
Killed
通過上下內存信息可以發(fā)現,當分配1000M內存時,宿主機用戶使用內存增加了1000M,可用內存為117M,當申請內存為2000M時,超出宿主機可用內存, bigmem 2000M命令所在進程直接被kill了。

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible vms83.liruilongs.github.io -m shell  -a "free -h"
vms83.liruilongs.github.io | CHANGED | rc=0 >>
              total        used        free      shared  buff/cache   available
Mem:           4.4G        2.5G        583M        216M        1.4G        1.4G
Swap:            0B          0B          0B
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$ansible vms83.liruilongs.github.io -m shell  -a "free -h"
vms83.liruilongs.github.io | CHANGED | rc=0 >>
              total        used        free      shared  buff/cache   available
Mem:           4.4G        3.5G        117M        216M        857M        456M
Swap:            0B          0B          0B
查看宿主機日志 /var/log/messages,可以發(fā)現bigmem 所在進程造成OOM。被OOM killer 殺掉了。

┌──[root@vms83.liruilongs.github.io]-[~]
└─$cat /var/log/messages | grep -i memory
Aug 10 20:37:27 vms83 kernel: [<ffffffff81186bd6>] out_of_memory+0x4b6/0x4f0
Aug 10 20:37:27 vms83 kernel: Out of memory: Kill process 25143 (bigmem) score 1347 or sacrifice child
如果不對pod做資源限制,他會認為宿主機的資源全是自己的,會無休止的使用,直到宿主機內存不足被OOM killer 直接殺了,為什么會被宿主機殺掉,容器的本質即運行在宿主機上的一個進程組。

通過 top 命令監(jiān)控 node資源,可以發(fā)現由于 OOM的問題, 可能會造成的節(jié)點短暫性死機,無法采集同步節(jié)點信息。

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl top nodes
NAME                         CPU(cores)   CPU%        MEMORY(bytes)   MEMORY%
vms81.liruilongs.github.io   189m         9%          1816Mi          58%
vms82.liruilongs.github.io   112m         3%          819Mi           17%
vms83.liruilongs.github.io   <unknown>    <unknown>   <unknown>       <unknown>
必須對資源進行限制,雖然上面的OOM情況下,殺掉了bigmem進程,但是實際上 OOM Killer 殺進程是不確定的,確定OOM殺手應該殺死哪個進程,內核會為每個進程保持一個運行不良評分,分數越高,進程越有可能被OOM殺手殺死。許多因素被用來計算這個分數。

所以當前節(jié)點上的任何一個Pod 進程都有可以能被殺掉。但有些Pod可能擔負著很重要的職責,比其他Pod更重要,比如與數據存儲相關的、與登錄相關的、與查詢余額相關的,即使系統(tǒng)資源嚴重不足,也需要保障這些Pod的存活。

所以Kubernetes中該提供了一些保障機制:

通過資源限額來確保不同的Pod只能占用指定的資源
允許集群的資源被超額分配,以提高集群的資源利用率。
為Pod劃分等級,確保不同等級的Pod有不同的服務質量(QoS),資源不足時,低等級的Pod會被清理,以確保高等級的Pod穩(wěn)定運行。
今天和小伙伴分享的主要第一種方式

Kubernetes集群里的節(jié)點提供的資源主要是計算資源,計算資源是可計量的能被申請、分配和使用的基礎資源,這使之區(qū)別于API資源(API Resources,例如Pod和Services等)。當前Kubernetes集群中的計算資源主要包括CPU、GPU及Memory,絕大多數常規(guī)應用是用不到GPU的,因此這里重點介紹CPU與Memory的資源管理問題。

我們知道,一個程序所使用的CPU與Memory是一個動態(tài)的量,確切地說,是一個范圍,跟它的負載密切相關:負載增加時,CPU和Memory的使用量也會增加。因此最準確的說法是,某個進程的CPU使用量為0.1個CPU~1個CPU,內存占用則為500MB~1GB。對應到Kubernetes的Pod容器上,就是下面這4個參數:

spec.container[].resources.requests.cpu
spec.container[].resources.limits.cpu
spec.container[].resources.requests.memory
spec.container[].resources.limits.memory
Request&&limits
在配置Pod時可以通過參數Request為其中的每個容器指定所需使用的CPU與Memory量,類似于一種申請,Kubernetes會根據Request的值去查找有足夠資源的Node來調度此Pod,如果沒有,則調度失敗。pod會一直處于pending

┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo-momory.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod-demo
  name: pod-demo
spec:
  containers:
  - image: hub.c.163.com/library/centos
    imagePullPolicy: IfNotPresent
    name: pod-demo
    command: ['sh','-c','sleep 500000']
    resources:
      requests:
        memory: "5000Mi"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
當前pod所調度的所有node最大只有4.4G內存,所以pod會一直處于pending

┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  apply  -f pod-demo-momory.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  get pods
NAME       READY   STATUS    RESTARTS   AGE
pod-demo   0/1     Pending   0          6s
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$
limits對應資源量的上限,即最多允許使用這個上限的資源量。

由于CPU資源是可壓縮的,進程無論如何也不可能突破上限,因此設置起來比較容易。對于Memory這種不可壓縮資源來說,它的Limit設置就是一個問題了,如果設置得小了,當進程在業(yè)務繁忙期試圖請求超過Limit限制的Memory時,此進程就會被Kubernetes殺掉。因此,Memory的Request與Limit的值需要結合進程的實際需求謹慎設置。






┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$cat pod-demo-momory.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: pod-demo
  name: pod-demo
spec:
  containers:
  - image: hub.c.163.com/library/centos
    imagePullPolicy: IfNotPresent
    name: pod-demo
    command: ['sh','-c','sleep 500000']
    resources:
      requests:
        memory: "2000Mi"
      limits:
        memory: "3000Mi"
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$vim pod-demo-momory.yaml
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  apply  -f pod-demo-momory.yaml
pod/pod-demo created
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$kubectl  get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP             NODE                         NOMINATED NODE   READINESS GATES
pod-demo   1/1     Running   0          12s   10.244.70.55   vms83.liruilongs.github.io   <none>           <none>
┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources]
└─$
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl  cp ./bigmem-7.0-1.r29766.x86_64.rpm pod-demo:/root/
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl  exec -it pod-demo -- bin/bash
[root@pod-demo /]# cd /root/
[root@pod-demo ~]# rpm -ivh bigmem-7.0-1.r29766.x86_64.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:bigmem-7.0-1.r29766              ################################# [100%]
[root@pod-demo ~]# bi
bigmem  bind
[root@pod-demo ~]# free -h
              total        used        free      shared  buff/cache   available
Mem:           4.4G        548M        3.1G        216M        818M        3.4G
Swap:            0B          0B          0B
[root@pod-demo ~]# bigmem  3000M
Attempting to allocate 3000 Mebibytes of resident memory...
Killed
[root@pod-demo ~]# bigmem  2000M
Attempting to allocate 2000 Mebibytes of resident memory...
Press <Enter> to exit^C
[root@pod-demo ~]# bigmem  20000M
Attempting to allocate 20000 Mebibytes of resident memory...
Killed
[root@pod-demo ~]# exit
exit
command terminated with exit code 137
可以發(fā)現,我們設置了limits 的值,所以由cgroup實現資源隔離來處理內存不夠,可以看到頂層的控制組為kubepods.slice,內存不夠的時候通過Cgroup 來kill掉進程,而不是通過宿主機內核的 OOM Killer 來殺

┌──[root@vms83.liruilongs.github.io]-[~]
└─$cat /var/log/messages | grep -i memory
Aug 10 21:08:06 vms83 kernel: [<ffffffff81186c24>] pagefault_out_of_memory+0x14/0x90
Aug 10 21:08:06 vms83 kernel: memory: usage 3072000kB, limit 3072000kB, failcnt 2760
Aug 10 21:08:06 vms83 kernel: memory+swap: usage 3072000kB, limit 9007199254740988kB, failcnt 0
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice: cache:0KB rss:0KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-bf3af784f917c54a50d0cb422d8f2624be3ba65a904e126e89081817d457c4d4.scope: cache:0KB rss:40KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:40KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-80f5aff910f2d6e0203fb6fc871cec9bf1d358d7e06ab9d4a381e46bac311465.scope: cache:0KB rss:3071960KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:3071912KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:08:06 vms83 kernel: Memory cgroup out of memory: Kill process 47482 (bigmem) score 1561 or sacrifice child
Aug 10 21:09:11 vms83 kernel: [<ffffffff81186c24>] pagefault_out_of_memory+0x14/0x90
Aug 10 21:09:11 vms83 kernel: memory: usage 3072000kB, limit 3072000kB, failcnt 2770
Aug 10 21:09:11 vms83 kernel: memory+swap: usage 3072000kB, limit 9007199254740988kB, failcnt 0
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice: cache:0KB rss:0KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-bf3af784f917c54a50d0cb422d8f2624be3ba65a904e126e89081817d457c4d4.scope: cache:0KB rss:40KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:40KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod804e1f6c_d5c0_4f8a_aa27_bcd66393087c.slice/docker-80f5aff910f2d6e0203fb6fc871cec9bf1d358d7e06ab9d4a381e46bac311465.scope: cache:0KB rss:3071960KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:3071948KB inactive_file:0KB active_file:0KB unevictable:0KB
Aug 10 21:09:11 vms83 kernel: Memory cgroup out of memory: Kill process 48483 (bigmem) score 1561 or sacrifice child
如果不設置CPU或Memory的Limit值,會怎樣呢?(不考慮上面的問題)

在這種情況下,該Pod的資源使用量有一個彈性范圍,我們不用絞盡腦汁去思考這兩個Limit的合理值,但問題也來了,考慮下面的例子:

Pod A的Memory Request被設置為1GB,NodeA當時空閑的Memory為1.2GB,符合PodA的需求,因此PodA被調度到NodeA上。運行3天后,PodA的訪問請求大增,內存需要增加到1.5GB,此時NodeA的剩余內存只有200MB,由于PodA新增的內存已經超出系統(tǒng)資源,所以在這種情況下,PodA就會被Kubernetes殺掉。

沒有設置Limit的Pod,或者只設置了CPULimit或者Memory Limit兩者之一的Pod,表面看都是很有彈性的,但實際上,相對于4個參數都被設置的Pod,是處于一種相對不穩(wěn)定的狀態(tài)的。

CPU和內存這兩種計算資源的特點進行說明。

CPU CPU的Requests和Limits是通過CPU數(cpus)來度量的。CPU的資源值是絕對值,而不是相對值,比如0.1CPU在單核或多核機器上是一樣的,都嚴格等于0.1CPU core。

Memory 內存的Requests和Limits計量單位是字節(jié)數。使用整數或者定點整數加上國際單位制(International System of Units)來表示內存值。國際單位制包括十進制的E、P、T、G、M、K、m,或二進制的Ei、Pi、Ti、Gi、Mi、Ki。KiB與MiB是以二進制表示的字節(jié)單位,常見的KB與MB則是以十進制表示的字節(jié)單位,Kubernetes的計算資源單位是大小寫敏感的

官方文檔的一些描述
如果你沒有指定內存限制
如果你沒有為一個容器指定內存限制,則自動遵循以下情況之一:

容器可無限制地使用內存。容器可以使用其所在節(jié)點所有的可用內存, 進而可能導致該節(jié)點調用 OOM Killer。此外,如果發(fā)生 OOM Kill,沒有資源限制的容器將被殺掉的可行性更大。

運行的容器所在命名空間有默認的內存限制,那么該容器會被自動分配默認限制。集群管理員可用使用 LimitRange 來指定默認的內存限制。

內存請求和限制的目的
通過為集群中運行的容器配置內存請求和限制,你可以有效利用集群節(jié)點上可用的內存資源。通過將 Pod 的內存請求保持在較低水平,你可以更好地安排 Pod 調度。通過讓內存限制大于內存請求,你可以完成兩件事:

Pod 可以進行一些突發(fā)活動,從而更好的利用可用內存。
Pod 在突發(fā)活動期間,可使用的內存被限制為合理的數量。
博文參考
《Kubernetes權威指南 第4版》
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-memory-resource/

作者:山河已無恙


歡迎關注微信公眾號 :山河已無恙