Kubernetes中資源限制的一些筆記整理
寫在前面
今天和小伙伴們分享K8s中pod資源限制的一些筆記
博文內(nèi)存涉及pod中通過request和limits實現(xiàn)資源的申請和限制
理解不足小伙伴幫忙指正,生活加油 ^_^
我們的痛苦來源于“夸父追日”一般的對“更好”的追求,也來自于自己的自卑與狂妄。--------duoduokk
學(xué)習(xí)之前,我們要準(zhǔn)備下實驗環(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的資源進(jìn)行限制,只有在生產(chǎn)環(huán)境會嚴(yán)格配置pod的資源限制。
當(dāng)不對pod的資源做限制時,K8s的調(diào)度器會認(rèn)為當(dāng)前pod所需要的資源很少,并且可以調(diào)度到任意節(jié)點上,但是這樣有一個很嚴(yán)重的弊端,如果不做資源限制,比如拿內(nèi)存來講,如果pod中的容器存在內(nèi)存不回收的情況,那么會無休止的使用宿主節(jié)點的內(nèi)存資源,從而會引發(fā)宿主機的OOM,甚至?xí)|發(fā)宿主機內(nèi)核OOM Killer(內(nèi)存殺手),來保證宿主機不掛掉。
看一個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)建成功后,我們可以看到調(diào)度到了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里安裝一個內(nèi)存分配工具bigmem,用于模擬pod中容器進(jìn)程內(nèi)存不回收的情況。
┌──[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
通過上下內(nèi)存信息可以發(fā)現(xiàn),當(dāng)分配1000M內(nèi)存時,宿主機用戶使用內(nèi)存增加了1000M,可用內(nèi)存為117M,當(dāng)申請內(nèi)存為2000M時,超出宿主機可用內(nèi)存, bigmem 2000M命令所在進(jìn)程直接被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ā)現(xiàn)bigmem 所在進(jìn)程造成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做資源限制,他會認(rèn)為宿主機的資源全是自己的,會無休止的使用,直到宿主機內(nèi)存不足被OOM killer 直接殺了,為什么會被宿主機殺掉,容器的本質(zhì)即運行在宿主機上的一個進(jìn)程組。
通過 top 命令監(jiān)控 node資源,可以發(fā)現(xiàn)由于 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>
必須對資源進(jìn)行限制,雖然上面的OOM情況下,殺掉了bigmem進(jìn)程,但是實際上 OOM Killer 殺進(jìn)程是不確定的,確定OOM殺手應(yīng)該殺死哪個進(jìn)程,內(nèi)核會為每個進(jìn)程保持一個運行不良評分,分?jǐn)?shù)越高,進(jìn)程越有可能被OOM殺手殺死。許多因素被用來計算這個分?jǐn)?shù)。
所以當(dāng)前節(jié)點上的任何一個Pod 進(jìn)程都有可以能被殺掉。但有些Pod可能擔(dān)負(fù)著很重要的職責(zé),比其他Pod更重要,比如與數(shù)據(jù)存儲相關(guān)的、與登錄相關(guān)的、與查詢余額相關(guān)的,即使系統(tǒng)資源嚴(yán)重不足,也需要保障這些Pod的存活。
所以Kubernetes中該提供了一些保障機制:
通過資源限額來確保不同的Pod只能占用指定的資源
允許集群的資源被超額分配,以提高集群的資源利用率。
為Pod劃分等級,確保不同等級的Pod有不同的服務(wù)質(zhì)量(QoS),資源不足時,低等級的Pod會被清理,以確保高等級的Pod穩(wěn)定運行。
今天和小伙伴分享的主要第一種方式
Kubernetes集群里的節(jié)點提供的資源主要是計算資源,計算資源是可計量的能被申請、分配和使用的基礎(chǔ)資源,這使之區(qū)別于API資源(API Resources,例如Pod和Services等)。當(dāng)前Kubernetes集群中的計算資源主要包括CPU、GPU及Memory,絕大多數(shù)常規(guī)應(yīng)用是用不到GPU的,因此這里重點介紹CPU與Memory的資源管理問題。
我們知道,一個程序所使用的CPU與Memory是一個動態(tài)的量,確切地說,是一個范圍,跟它的負(fù)載密切相關(guān):負(fù)載增加時,CPU和Memory的使用量也會增加。因此最準(zhǔn)確的說法是,某個進(jìn)程的CPU使用量為0.1個CPU~1個CPU,內(nèi)存占用則為500MB~1GB。對應(yīng)到Kubernetes的Pod容器上,就是下面這4個參數(shù):
spec.container[].resources.requests.cpu
spec.container[].resources.limits.cpu
spec.container[].resources.requests.memory
spec.container[].resources.limits.memory
Request&&limits
在配置Pod時可以通過參數(shù)Request為其中的每個容器指定所需使用的CPU與Memory量,類似于一種申請,Kubernetes會根據(jù)Request的值去查找有足夠資源的Node來調(diào)度此Pod,如果沒有,則調(diào)度失敗。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: {}
當(dāng)前pod所調(diào)度的所有node最大只有4.4G內(nèi)存,所以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對應(yīng)資源量的上限,即最多允許使用這個上限的資源量。
由于CPU資源是可壓縮的,進(jìn)程無論如何也不可能突破上限,因此設(shè)置起來比較容易。對于Memory這種不可壓縮資源來說,它的Limit設(shè)置就是一個問題了,如果設(shè)置得小了,當(dāng)進(jìn)程在業(yè)務(wù)繁忙期試圖請求超過Limit限制的Memory時,此進(jìn)程就會被Kubernetes殺掉。因此,Memory的Request與Limit的值需要結(jié)合進(jìn)程的實際需求謹(jǐn)慎設(shè)置。
┌──[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ā)現(xiàn),我們設(shè)置了limits 的值,所以由cgroup實現(xiàn)資源隔離來處理內(nèi)存不夠,可以看到頂層的控制組為kubepods.slice,內(nèi)存不夠的時候通過Cgroup 來kill掉進(jìn)程,而不是通過宿主機內(nèi)核的 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
如果不設(shè)置CPU或Memory的Limit值,會怎樣呢?(不考慮上面的問題)
在這種情況下,該Pod的資源使用量有一個彈性范圍,我們不用絞盡腦汁去思考這兩個Limit的合理值,但問題也來了,考慮下面的例子:
Pod A的Memory Request被設(shè)置為1GB,NodeA當(dāng)時空閑的Memory為1.2GB,符合PodA的需求,因此PodA被調(diào)度到NodeA上。運行3天后,PodA的訪問請求大增,內(nèi)存需要增加到1.5GB,此時NodeA的剩余內(nèi)存只有200MB,由于PodA新增的內(nèi)存已經(jīng)超出系統(tǒng)資源,所以在這種情況下,PodA就會被Kubernetes殺掉。
沒有設(shè)置Limit的Pod,或者只設(shè)置了CPULimit或者M(jìn)emory Limit兩者之一的Pod,表面看都是很有彈性的,但實際上,相對于4個參數(shù)都被設(shè)置的Pod,是處于一種相對不穩(wěn)定的狀態(tài)的。
CPU和內(nèi)存這兩種計算資源的特點進(jìn)行說明。
CPU CPU的Requests和Limits是通過CPU數(shù)(cpus)來度量的。CPU的資源值是絕對值,而不是相對值,比如0.1CPU在單核或多核機器上是一樣的,都嚴(yán)格等于0.1CPU core。
Memory 內(nèi)存的Requests和Limits計量單位是字節(jié)數(shù)。使用整數(shù)或者定點整數(shù)加上國際單位制(International System of Units)來表示內(nèi)存值。國際單位制包括十進(jìn)制的E、P、T、G、M、K、m,或二進(jìn)制的Ei、Pi、Ti、Gi、Mi、Ki。KiB與MiB是以二進(jìn)制表示的字節(jié)單位,常見的KB與MB則是以十進(jìn)制表示的字節(jié)單位,Kubernetes的計算資源單位是大小寫敏感的
官方文檔的一些描述
如果你沒有指定內(nèi)存限制
如果你沒有為一個容器指定內(nèi)存限制,則自動遵循以下情況之一:
容器可無限制地使用內(nèi)存。容器可以使用其所在節(jié)點所有的可用內(nèi)存, 進(jìn)而可能導(dǎo)致該節(jié)點調(diào)用 OOM Killer。此外,如果發(fā)生 OOM Kill,沒有資源限制的容器將被殺掉的可行性更大。
運行的容器所在命名空間有默認(rèn)的內(nèi)存限制,那么該容器會被自動分配默認(rèn)限制。集群管理員可用使用 LimitRange 來指定默認(rèn)的內(nèi)存限制。
內(nèi)存請求和限制的目的
通過為集群中運行的容器配置內(nèi)存請求和限制,你可以有效利用集群節(jié)點上可用的內(nèi)存資源。通過將 Pod 的內(nèi)存請求保持在較低水平,你可以更好地安排 Pod 調(diào)度。通過讓內(nèi)存限制大于內(nèi)存請求,你可以完成兩件事:
Pod 可以進(jìn)行一些突發(fā)活動,從而更好的利用可用內(nèi)存。
Pod 在突發(fā)活動期間,可使用的內(nèi)存被限制為合理的數(shù)量。
博文參考
《Kubernetes權(quán)威指南 第4版》
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-memory-resource/
作者:山河已無恙
歡迎關(guān)注微信公眾號 :山河已無恙