深入理解Kubernetes資源限制:記憶體
寫在前面
當我開始大範圍使用Kubernetes的時候,我開始考慮一個我做實驗時沒有遇到的問題:當叢集裡的節點沒有足夠資源的時候
Pod會卡在Pending狀態。你是沒有辦法給節點增加CPU或者記憶體的,那麼你該怎麼做才能將這個Pod從這個節點拿走?
最簡單的辦法是新增另一個節點,我承認我總是這麼幹。最終這個策略無法發揮出Kubernetes最重要的一個能力:即它最佳化
計算資源使用的能力。這些場景裡面實際的問題並不是節點太小,而是我們沒有仔細為Pod計算過資源限制。
資源限制是我們可以向Kubernetes提供的諸多配置之一,它意味著兩點:工作負載執行需要哪些資源;最多允許消費多少
資源。第一點對於排程器而言十分重要,因為它要以此選擇合適的節點。第二點對於Kubelet非常重要,每個節點上的守護
程式Kubelet負責Pod的執行健康狀態。大多數本文的讀者可能對資源限制有一定的瞭解,實際上這裡面有很多有趣的細節。
在這個系列的兩篇文章中我會先仔細分析記憶體資源限制,然後第二篇文章中分析CPU資源限制。
資源限制
資源限制是透過每個容器containerSpec的resources欄位進行設定的,它是v1版本的ResourceRequirements型別的API對
象。每個指定了"limits"和"requests"的物件都可以控制對應的資源。目前只有CPU和記憶體兩種資源。第三種資源型別,持久
化儲存仍然是beta版本,我會在以後的部落格裡進行分析。大多數情況下,deployment、statefulset、daemonset的定義裡
都包含了podSpec和多個containerSpec。這裡有個完整的v1資源物件的yaml格式配置:
這個物件可以這麼理解:這個容器通常情況下,需要5%的CPU時間和50MiB的記憶體(requests),同時最多允許它使用10%
的CPU時間和100MiB的記憶體(limits)。我會對requests和limits的區別做進一步講解,但是一般來說,在排程的時候
requests比較重要,在執行時limits比較重要。儘管資源限制配置在每個容器上,你可以認為Pod的資源限制就是它裡面容器
的資源限制之和,我們可以從系統的視角觀察到這種關係。
記憶體限制
通常情況下分析記憶體要比分析CPU簡單一些,所以我從這裡開始著手。我的一個目標是給大家展示記憶體在系統中是如何實現
的,也就是Kubernetes對容器執行時(docker/containerd)所做的工作,容器執行時對Linux核心所做的工作。從分析內
存資源限制開始也為後面分析CPU打好了基礎。首先,讓我們回顧一下前面的例子:
單位字尾Mi表示的是MiB,所以這個資源物件定義了這個容器需要50MiB並且最多能使用100MiB的記憶體。當然還有其他單
位可以進行表示。為了瞭解如何用這些值是來控制容器程式,我們首先建立一個沒有配置記憶體限制的Pod:
$ kubectl run limit-test --image=busybox --command -- /bin/sh -c "while true; do sleep 2; done"
deployment.apps "limit-test" created
用Kubectl命令我們可以驗證這個Pod是沒有資源限制的:
$ kubectl get pods limit-test-7cff9996fc-zpjps -o=jsonpath='{.spec.containers[0].resources}'
map[]
Kubernetes最酷的一點是你可以跳到系統以外的角度來觀察每個構成部分,所以我們登入到執行Pod的節點,看看Docker是
如何執行這個容器的:
$ docker ps | grep busy | cut -d' ' -f1
5c3af3101afb
$ docker inspect 5c3af3101afb -f "{{.HostConfig.Memory}}"
這個容器的.HostConfig.Memory域對應了docker run時的--memory引數,0值表示未設定。Docker會對這個值做什麼?
為了控制容器程式能夠訪問的記憶體數量,Docker配置了一組control group,或者叫cgroup。Cgroup在2008年1月時合併
到Linux 2.6.24版本的核心。它是一個很重要的話題。我們說cgroup是容器的一組用來控制核心如何執行程式的相關屬性集
合。針對記憶體、CPU和各種裝置都有對應的cgroup。Cgroup是具有層級的,這意味著每個cgroup擁有一個它可以繼承屬
性的父親,往上一直直到系統啟動時建立的root cgroup。
Cgroup可以透過/proc和/sys偽檔案系統輕鬆檢視到,所以檢查容器如何配置記憶體的cgroup就很簡單了。在容器的
Pid namespace裡,根程式的pid為1,但是namespace以外它呈現的是系統級pid,我們可以用來查詢它的cgroups:
$ ps ax | grep /bin/sh
9513 ? Ss 0:00 /bin/sh -c while true; do sleep 2; done
$ sudo cat /proc/9513/cgroup
...
6:memory:/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec1361a97c3511db37a4
bae932d41b22264e5b97611748f8b662312574
我列出了記憶體cgroup,這正是我們所關注的。你在路徑裡可以看到前面提到的cgroup層級。一些比較重要的點是:首先
這個路徑是以kubepods開始的cgroup,所以我們的程式繼承了這個group的每個屬性,還有burstable的屬性
(Kubernetes將Pod設定為burstable QoS類別)和一組用於審計的Pod表示。最後一段路徑是我們程式實際使用的cgroup。
我們可以把它追加到/sys/fs/cgroups/memory後面檢視更多資訊:
$ ls -l /sys/fs/cgroup/memory/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec1361a
97c3511db37a4bae932d41b22264e5b97611748f8b662312574
...
-rw-r--r-- 1 root root 0 Oct 27 19:53 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Oct 27 19:53 memory.soft_limit_in_bytes
再一次,我只列出了我們所關心的記錄。我們暫時不關注memory.soft_limit_in_bytes,而將重點轉移到memory.limit_in_
bytes屬性,它設定了記憶體限制。它等價於Docker命令中的--memory引數,也就是Kubernetes裡的記憶體資源限制。我們
看看:
$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/podfbc202d3-da21-11e8-ab5e-42010a80014b/0a1b22ec
1361a97c3511db37a4bae932d41b22264e5b97611748f8b662312574/memory.limit_in_bytes
9223372036854771712
這是沒有設定資源限制時我的節點上顯示的情況。這裡有對它的一個簡單的解釋(
questions/420906/what-is-the-value-for-the-cgroups-limit-in-bytes-if-the-memory-is-not-restricte)。所以我們看
到如果沒有在Kubernetes裡設定記憶體限制的話,會導致Docker設定HostConfig.Memory值為0,並進一步導致容器程式
被放置在預設值為"no limit"的memory.limit_in_bytes記憶體cgroup下。我們現在建立使用100MiB記憶體限制的Pod:
$ kubectl run limit-test --image=busybox --limits "memory=100Mi" --command -- /bin/sh -c "while true; do
sleep 2; done"
deployment.apps "limit-test" created
我們再一次使用kubectl驗證我們的資源配置:
$ kubectl get pods limit-test-5f5c7dc87d-8qtdx -o=jsonpath='{.spec.containers[0].resources}'
map[limits:map[memory:100Mi] requests:map[memory:100Mi]]
你會注意到除了我們設定的limits外,Pod還增加了requests。當你設定limits而沒有設定requests時,Kubernetes預設
讓requests等於limits。如果你從排程器的角度看這是非常有意義的。我會在下面進一步討論requests。當這個Pod啟動後
我們可以看到Docker如何配置的容器以及這個程式的記憶體cgroup:
$ docker ps | grep busy | cut -d' ' -f1
8fec6c7b6119
$ docker inspect 8fec6c7b6119 --format '{{.HostConfig.Memory}}'
104857600
$ ps ax | grep /bin/sh
29532 ? Ss 0:00 /bin/sh -c while true; do sleep 2; done
$ sudo cat /proc/29532/cgroup
...
6:memory:/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7b61190e74cd9f88286181dd
5fa3bbf9cf33c947574eb61462bc254d11
$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7b6
1190e74cd9f88286181dd5fa3bbf9cf33c947574eb61462bc254d11/memory.limit_in_bytes
104857600
正如你所見,Docker基於我們的containerSpec正確地設定了這個程式的記憶體cgroup。但是這對於執行時意味著什麼?
Linux記憶體管理是一個複雜的話題,Kubernetes工程師需要知道的是:當一個宿主機遇到了記憶體資源壓力時,核心可能會
有選擇性地殺死程式。如果一個使用了多於限制記憶體的程式會有更高几率被殺死。因為Kubernetes的任務是儘可能多地
向這些節點上安排Pod,這會導致節點記憶體壓力異常。如果你的容器使用了過多記憶體,那麼它很可能會被oom-killed。
如果Docker收到了核心的通知,Kubernetes會找到這個容器並依據設定嘗試重啟這個Pod。
所以Kubernetes預設建立的記憶體requests是什麼?擁有一個100MiB的記憶體請求會影響到cgroup?可能它設定了我們之前
看到的memory.soft_limit_in_bytes?讓我們看看:
$ sudo cat /sys/fs/cgroup/memory/kubepods/burstable/pod88f89108-daf7-11e8-b1e1-42010a800070/8fec6c7
b61190e74cd9f88286181dd5fa3bbf9cf33c947574eb61462bc254d11/memory.soft_limit_in_bytes
9223372036854771712
你可以看到軟限制仍然被設定為預設值“no limit”。即使Docker支援透過引數--memory-reservation進行設定,但
Kubernetes並不支援這個引數。這是否意味著為你的容器指定記憶體requests並不重要?不,不是的。requests要比limits
更重要。limits告訴Linux核心什麼時候你的程式可以為了清理空間而被殺死。requests幫助Kubernetes排程找到合適的節
點執行Pod。如果不設定它們,或者設定得非常低,那麼可能會有不好的影響。
例如,假設你沒有配置記憶體requests來執行Pod,而配置了一個較高的limits。正如我們所知道的Kubernetes預設會把
requests的值指向limits,如果沒有合適的資源的節點的話,Pod可能會排程失敗,即使它實際需要的資源並沒有那麼多。
另一方面,如果你執行了一個配置了較低requests值的Pod,你其實是在鼓勵核心oom-kill掉它。為什麼?假設你的Pod
通常使用100MiB記憶體,你卻只為它配置了50MiB記憶體requests。如果你有一個擁有75MiB記憶體空間的節點,那麼這個Pod
會被排程到這個節點。當Pod記憶體消耗擴大到100MiB時,會讓這個節點壓力變大,這個時候核心可能會選擇殺掉你的程式。
所以我們要正確配置Pod的記憶體requests和limits。
相關服務請訪問: https://support.huaweicloud.com/cce/index.html?utm_content=cce_helpcenter_2019
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69908804/viewspace-2637865/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 深入理解Kubernetes資源限制:CPU
- 深入理解golang:記憶體分配原理Golang記憶體
- 深入理解JVM之記憶體管理JVM記憶體
- 如何使用 Docker 來限制 CPU、記憶體和 IO等資源?Docker記憶體
- 深入理解Java的堆記憶體和執行緒記憶體Java記憶體執行緒
- 深入理解JVM之記憶體區域與記憶體溢位JVM記憶體溢位
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型
- 深入理解Java記憶體模型(五)——鎖Java記憶體模型
- 深入理解JVM(一)JVM記憶體模型JVM記憶體模型
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- 深入理解jvm記憶體模型以及gc原理JVM記憶體模型GC
- 深入理解 JVM 之 JVM 記憶體結構JVM記憶體
- 深入理解Java記憶體模型(二)——重排序Java記憶體模型排序
- 深入理解Java記憶體模型(七)——總結Java記憶體模型
- 深入理解JVM(1)之--JVM記憶體模型JVM記憶體模型
- 深入理解javascript系列(二):記憶體空間JavaScript記憶體
- 深入理解Java記憶體模型(一)——基礎Java記憶體模型
- 深入理解Java記憶體模型(六)——finalJava記憶體模型
- 深入理解GO語言之記憶體詳解Go記憶體
- 深入理解Java中的記憶體洩漏Java記憶體
- JavaScript 程式中記憶體洩漏深入理解JavaScript記憶體
- 資源供給:記憶體和虛擬記憶體記憶體
- 資源記憶體佔用記憶體
- Kubernetes資源請求與限制
- 如何設定Kubernetes資源限制
- Netweaver工作程式的記憶體限制 VS CloudFoundry應用的記憶體限制記憶體Cloud
- 深入理解JVM(③)學習Java的記憶體模型JVMJava記憶體模型
- 深入理解JVM-記憶體模型(jmm)和GCJVM記憶體模型GC
- 【Linux】深入理解Linux中記憶體管理Linux記憶體
- 深入理解 Java String#intern() 記憶體模型Java記憶體模型
- 深入理解JVM虛擬機器-JVM記憶體區域與記憶體溢位JVM虛擬機記憶體溢位
- 記憶體--通俗理解記憶體
- Kubernetes筆記(四):詳解Namespace與資源限制ResourceQuota,LimitRange筆記namespaceMIT
- K8S(18)容器環境下資源限制與jvm記憶體回收K8SJVM記憶體
- 資源供給:再談記憶體和虛擬記憶體記憶體
- 用例項帶你深入理解Java記憶體模型Java記憶體模型
- Node記憶體限制和垃圾回收記憶體
- Node記憶體限制與垃圾回收記憶體