Kubernetes的資源管理與垃圾回收機(jī)制學(xué)習(xí)

匠心獨(dú)運(yùn)維妙維效公眾號
董嗣伯
在 Kubernetes 集群管理中,有一個非常核心的功能,就是為 Pod 選擇一個主機(jī)運(yùn)行。調(diào)度必須滿足一定的條件,其中最基本的是主機(jī)上要有足夠的資源給 Pod 使用。

引言

Kubernetes從創(chuàng)建之初的核心模塊之一就是資源調(diào)度,想要在生產(chǎn)環(huán)境使用好Kubernetes,就必須對它的資源模型以及資源管理非常了解,同時,為了避免不必要的資源浪費(fèi),Kubernetes也提供了基于kubelet的垃圾回收機(jī)制,最大限度地保證整個容器內(nèi)的資源可以被有效利用,并及時清理回收失效的對象資源。

一、Kubernetes的資源和管理

在 Kubernetes 中,有兩個基礎(chǔ)但是非常重要的概念:Node和Pod。Node通常翻譯成節(jié)點(diǎn),是對集群資源的抽象;Pod 是對容器的封裝,是應(yīng)用運(yùn)行的實(shí)體。Node 提供資源,而 Pod 使用資源,這里的資源分為計算資源(CPU、Memory、GPU)、存儲資源(Disk、SSD)和網(wǎng)絡(luò)資源(Network Bandwidth、IP、Ports)等。這些資源提供了應(yīng)用運(yùn)行的基礎(chǔ)環(huán)境,正確理解這些資源以及集群調(diào)度如何使用這些資源,對于大規(guī)模的 Kubernetes 集群來說至關(guān)重要,不僅能保證應(yīng)用的穩(wěn)定性,也可以提高資源的利用率。

通常我們在關(guān)注一個容器化系統(tǒng)的運(yùn)行性能時,首先關(guān)注的就是它可以獲得的計算資源(CPU、內(nèi)存)的資源情況,一般來說,獲得的資源越多、越充分,那么這個系統(tǒng)的工作就會越順暢,其中CPU 分配的是可使用時間,也就是操作系統(tǒng)管理的時間片,每個進(jìn)程在一定的時間片里運(yùn)行自己的任務(wù),而對于內(nèi)存,系統(tǒng)提供的是可供調(diào)配的內(nèi)存大小。

CPU 的使用時間是可壓縮的,換句話說它本身并無狀態(tài),資源申請很快,也能快速正?;厥?;而內(nèi)存大小是不可壓縮的,因為它是有狀態(tài)的(內(nèi)存里面保存的數(shù)據(jù)),申請資源很慢(需要計算和分配內(nèi)存塊的空間),并且回收可能失?。ū徽加玫膬?nèi)存一般不可回收)。

這里把資源分成可壓縮和不可壓縮,是因為在資源不足的時候,它們的表現(xiàn)很不一樣。對于不可壓縮資源,如果資源不足,也就無法繼續(xù)申請資源(內(nèi)存用完就是用完了),并且會導(dǎo)致 Pod 的運(yùn)行產(chǎn)生無法預(yù)測的錯誤(應(yīng)用申請內(nèi)存失敗會導(dǎo)致一系列問題);而對于可壓縮資源,比如 CPU 時間片,即使 Pod 使用的 CPU 資源很多,CPU 使用也可以按照權(quán)重分配給所有 Pod 使用,雖然每個Pod使用的時間片減少,但不會影響程序的邏輯。

在 Kubernetes 集群管理中,有一個非常核心的功能,就是為 Pod 選擇一個主機(jī)運(yùn)行。調(diào)度必須滿足一定的條件,其中最基本的是主機(jī)上要有足夠的資源給 Pod 使用。

圖1 集群管理示意圖

用戶在 Pod 中可以配置要使用的資源總量,Kubernetes 根據(jù)配置的資源數(shù)進(jìn)行調(diào)度和運(yùn)行。目前主要可以配置的資源是 CPU 和 Memory,對應(yīng)的配置字段是:

spec.containers[].resource.limits/request.cpu/memory

需要注意的是,用戶是對每個容器配置Request值,所有容器的資源請求之和就是 Pod 的資源請求總量。

CPU 一般用核數(shù)來標(biāo)識,一核CPU 對應(yīng)物理服務(wù)器的一個超線程核,也就是操作系統(tǒng)/proc/cpuinfo中列出來的核數(shù)。因為對資源進(jìn)行了池化和虛擬化,所以Kubernetes允許配置非整數(shù)個的核數(shù),比如0.5也是合法的,它標(biāo)識應(yīng)用可以使用半個CPU核的計算量。CPU的請求有兩種方式,一種是剛提到的 0.5、1 這種直接用數(shù)字標(biāo)識CPU核心數(shù);另外一種表示是 500m,它等價于 0.5,也就是說 1Core = 1000m。

內(nèi)存比較容易理解,是通過字節(jié)大小指定的。如果直接一個數(shù)字,后面沒有任何單位,表示這么多字節(jié)的內(nèi)存。數(shù)字后面還可以跟著單位,支持的單位有 E、P、T、G、M、K,前者分別是后者的1000倍大小的關(guān)系,此外還支持 Ei、Pi、Ti、Gi、Mi、Ki,其對應(yīng)的倍數(shù)關(guān)系是2^10 = 1024。比如要使用100M 內(nèi)存的話,直接寫成100Mi即可。

但是,節(jié)點(diǎn)上的的資源,真的可以予取予求嗎?

理想情況下,我們希望節(jié)點(diǎn)上所有的資源都可以分配給Pod 使用,但實(shí)際上節(jié)點(diǎn)上除了運(yùn)行Pods之外,還會運(yùn)行其他的很多進(jìn)程:系統(tǒng)相關(guān)的進(jìn)程(比如SSHD、Udev等),以及Kubernetes集群的組件(Kubelet、Docker等)。我們在分配資源的時候,需要給這些進(jìn)程預(yù)留一些資源,剩下的才能給Pod 使用,Kubelet會保證節(jié)點(diǎn)上的資源使用率不會真正到100%,因此Pod的實(shí)際可使用資源會稍微再少一點(diǎn)。主機(jī)上的資源邏輯分配圖如圖2所示。

圖2 主機(jī)上的資源邏輯分配圖

用戶在創(chuàng)建Pod 的時候,可以指定每個容器的Requests和Limits 兩個字段,下面是一個實(shí)例:

Requests是容器請求要使用的資源,Kubernetes會保證Pod能使用到這么多的資源。請求的資源是調(diào)度的依據(jù),只有當(dāng)節(jié)點(diǎn)上的可用資源大于Pod請求的各種資源時,調(diào)度器才會把Pod調(diào)度到該節(jié)點(diǎn)上(如果CPU資源足夠,內(nèi)存資源不足,調(diào)度器也不會選擇該節(jié)點(diǎn))。

需要注意的是,調(diào)度器只關(guān)心節(jié)點(diǎn)上可分配的資源,以及節(jié)點(diǎn)上所有Pods請求的資源,而不關(guān)心節(jié)點(diǎn)資源的實(shí)際使用情況,換句話說,如果節(jié)點(diǎn)上的Pods申請的資源已經(jīng)把節(jié)點(diǎn)上的資源用滿,即使它們的使用率非常低,比如說CPU和內(nèi)存使用率都低于10%,調(diào)度器也不會繼續(xù)調(diào)度Pod上去。

Limits是Pod能使用的資源上限,是實(shí)際配置到內(nèi)核cgroups里面的配置數(shù)據(jù)。對于內(nèi)存來說,會直接轉(zhuǎn)換成dockerrun 命令行的--memory大小,最終會配置到cgroups對應(yīng)任務(wù)的文件中,文件目錄如下:

/sys/fs/cgroup/memory/…/memory.limit_in_bytes

如果Limit沒有配置,則表明沒有資源的上限,只要節(jié)點(diǎn)上有對應(yīng)的資源,Pod就可以使用。

使用Requests和Limits概念,我們能分配更多的Pod,提升整體的資源使用率。但是這個體系有個非常重要的問題需要考慮,那就是怎么去準(zhǔn)確地評估Pod的資源Requests?如果評估過低,會導(dǎo)致應(yīng)用不穩(wěn)定;如果過高,則會導(dǎo)致使用率降低。這個就需要開發(fā)者和管理員共同討論和定義,要結(jié)合生產(chǎn)實(shí)踐進(jìn)行調(diào)節(jié)。

舉個容器化改造后問題排查及優(yōu)化的例子:某行的移動辦公平臺進(jìn)行從傳統(tǒng)服務(wù)遷移到“微服務(wù)+容器化”的改造,開發(fā)測試驗證未出現(xiàn)問題,轉(zhuǎn)入非功能性能試期間后,隨著壓力的不斷增加,并發(fā)量上升,系統(tǒng)開始出現(xiàn)登錄請求緩慢、超時等情況,與之前基于開發(fā)測試的預(yù)估有明顯差距,經(jīng)日志排查后,定位直接原因為接入“移動網(wǎng)關(guān)”服務(wù)內(nèi)存使用率過高,檢查非功能性能試驗環(huán)境的移動網(wǎng)關(guān)服務(wù)資源配置,配置如下:

開發(fā)人員與運(yùn)維人員對非功能性能試驗環(huán)境及應(yīng)用程序的日志進(jìn)行分析后,決定解決問題從兩方面進(jìn)行,一方面開發(fā)對應(yīng)用程序進(jìn)行優(yōu)化,減少開源軟件產(chǎn)生堆外內(nèi)存,另外一方面,在非功能性能試驗環(huán)境對進(jìn)行資源配置緊急擴(kuò)容,擴(kuò)容后配置如下:

經(jīng)過資源擴(kuò)容,系統(tǒng)穩(wěn)定性得到提升,問題出現(xiàn)的概率明顯下降,后經(jīng)應(yīng)用程序調(diào)整后,問題徹底解決。由于此移動網(wǎng)關(guān)是通用的消息轉(zhuǎn)發(fā)網(wǎng)關(guān)服務(wù),根據(jù)此次問題的解決結(jié)果,后續(xù)此服務(wù)的默認(rèn)配置即以此次優(yōu)化后結(jié)果為準(zhǔn)。

二、Kubernetes垃圾對象的回收機(jī)制

Kubernetes系統(tǒng)在長時間運(yùn)行后,Kubernetes Node會下載非常多的鏡像,其中可能存在很多過期的鏡像。同時因為運(yùn)行大量的容器,容器推出后就變成死亡容器,將數(shù)據(jù)殘留在宿主機(jī)上,這樣一來,過期鏡像和死亡容器都會占用大量的硬盤空間。如果磁盤空間被用光,可能會發(fā)生非常糟糕的情況,甚至?xí)?dǎo)致磁盤的損壞。為此kubelete會進(jìn)行垃圾清理工作,即定期清理過期鏡像和死亡容器。因為kubelet需要通過容器來判斷pod的運(yùn)行狀態(tài),如果使用其它方式清除容器有可能影響kubelet的正常工作,所以不推薦使用其它管理工具或手工進(jìn)行容器和鏡像的清理。

那么實(shí)現(xiàn)GC的機(jī)制是怎么樣的呢?各項資源對象(Resource Object)是以怎樣的一種方式進(jìn)行清理的呢?

Kubernetes通過 Garbage Collector組件 和 ownerReference 一起配合實(shí)現(xiàn)了“垃圾回收GC”的功能。

通常在K8s 中,有兩大類GC:

第一類

級聯(lián)(Cascading Deletionstrategy)

Kubernetes在不同的 Resource Objects 中維護(hù)了一定的“從屬關(guān)系”,一般會默認(rèn)在一個Resource Object和它的創(chuàng)建者之間建立一個“從屬關(guān)系”。Kubernetes利用已經(jīng)建立的“從屬關(guān)系”進(jìn)行資源對象的進(jìn)行“級聯(lián)”清理工作。例如,當(dāng)一個dependent 資源的owner已經(jīng)被刪除或者不存在的時候,從某種角度可以判定,這個dependent的對象已經(jīng)異常(無人管轄),則需要進(jìn)行清理。而“Cascading Deletion”則是被通過Garbage Collector(GC)組件實(shí)現(xiàn)。

在級聯(lián)刪除中,又有兩種模式:前臺(foreground)和后臺(background)。

前臺級聯(lián)刪除(Foreground Cascading Deletion):在這種刪除策略中,所有者對象的刪除將會持續(xù)到其所有從屬對象都被刪除為止。當(dāng)所有者被刪除時,會進(jìn)入“正在刪除”(deletionin progress)狀態(tài),此時:

對象仍然可以通過REST API 查詢到(可通過kubectl 或kuboard 查詢到)

對象的deletion Timestamp 字段被設(shè)置

對象的metadata.finalizers 包含值foreground Deletion

一旦對象被設(shè)置為“正在刪除”的狀態(tài),垃圾回收器將刪除其從屬對象。當(dāng)垃圾回收器已經(jīng)刪除了所有的“blocking”從屬對象之后(ownerReference.blockOwnerDeletion=true的對象),將刪除所有者對象。

后臺級聯(lián)刪除(Background Cascading Deletion):這種刪除策略會簡單很多,它會立即刪除所有者的對象,并由垃圾回收器在后臺刪除其從屬對象。這種方式比前臺級聯(lián)刪除快的多,因為不用等待時間來刪除從屬對象。

第二類

孤兒(Orphan)

這種情況下,對所有者的進(jìn)行刪除只會將其從集群中刪除,并使所有對象處于“孤兒”狀態(tài)。在孤兒刪除策略(orphandeletion strategy)中,會直接刪除所有者對象,并將從屬對象中的ownerReference元數(shù)據(jù)設(shè)置為默認(rèn)值。之后垃圾回收器會確定孤兒對象并將其刪除。

Garbage Collector 組件的工作通常由三部分實(shí)現(xiàn),具體如圖3所示。

圖3 GarbageCollector 組件工作的實(shí)現(xiàn)

1

Scanner:它負(fù)責(zé)收集目前系統(tǒng)中已存在的Resource,并且周期性的將這些資源對象放入一個隊列中,等待處理(檢測是否要對某一個Resource Object 進(jìn)行 GC操作)。

2

GarbageProcessor:Garbage Processor 由兩部分組成。

a、Dirty Queue:Scanner會將周期性掃描到的Resource Object 放入這個隊列中等待處理

b、Worker:worker負(fù)責(zé)從Dirty Queue隊列中取出元素進(jìn)行處理

檢查Object 的metaData部分,查看ownerReference字段是否為空。如果為空,則本次處理結(jié)束,如果不為空,繼續(xù)檢測ownerReference字段內(nèi)標(biāo)識的Owner Resource Object是否存在,如果存在:則本次處理結(jié)束;如果不存在:刪除這個Object。

3

Propagator:Propagator 由三個部分構(gòu)成。

a、Event Queue:負(fù)責(zé)存儲Kubernetes中資源對象的事件(Eg:ADD,UPDATE,DELETE)

b、DAG(有向無環(huán)圖):負(fù)責(zé)存儲Kubernetes中所有資源對象的“owner-dependent”關(guān)系

c、Worker:從EventQueue中,取出資源對象的事件,根據(jù)事件的類型會采取以下兩種操作:

1)ADD/UPDATE:將該事件對應(yīng)的資源對象加入DAG,且如果該對象有owner 且owner 不在DAG 中,將它同時加入Garbage Processor 的Dirty Queue 中;

2)DELETE:將該事件對應(yīng)的資源對象從DAG中刪除,并且將其“管轄”的對象(只向下尋找一級,如刪除Deployment,那么只操作ReplicaSet )加入Garbage Processor 的Dirty Queue 中。

其實(shí),在有了Scanner 和Garbage Processor 之后,Garbage Collector 就已經(jīng)能夠?qū)崿F(xiàn)“垃圾回收”的功能了。但是有一個明顯的問題:Scanner的掃描頻率設(shè)置多少好呢?時間間隔太長了,Kubernetes內(nèi)部就會積累過多的“廢棄資源”;間隔太短了,尤其是在集群內(nèi)部資源對象較多的時候,頻繁地拉取信息對API-Server 也是一個不小的壓力。

Kubernetes作為一個分布式的服務(wù)編排系統(tǒng),其內(nèi)部執(zhí)行任何一項邏輯或者行為,都依賴一種機(jī)制:“事件驅(qū)動”。說的簡單點(diǎn),Kubernetes中一些看起來“自動”的行為,其實(shí)都是由我們所說的“Event”驅(qū)動的。任意一個Resource Object發(fā)生變動的時候(新建、更新、刪除),都會觸發(fā)一個Kubernetes的事件(Event),而這個事件在Kubernetes的內(nèi)部是公開的,也就是說,我們可以在任意一個地方監(jiān)聽這些事件。

總的來說,無論是“事件的監(jiān)聽機(jī)制”還是“周期性訪問API-Server 批量獲取Resource Object信息”,其目的都是為了能夠掌握Resource Object的最新信息。兩者是各有優(yōu)勢的:

(1)批量拉?。阂淮涡岳∷械腞esource Object,獲取信息全面;

(2)實(shí)時監(jiān)聽Resource 的Event:實(shí)時性強(qiáng),且對 API—SERVER不會造成太大的壓力。

綜上所述,在實(shí)現(xiàn)Garbage Collector的過程中,Kubernetes向其添加了一個“增強(qiáng)型”的組件:Propagator。在有了Propagator 的加入之后,我們完全可以僅在GC 開始運(yùn)行的時候,讓Scanner 掃描一下系統(tǒng)中所有的Object,然后將這些信息傳遞給Propagator 和Dirty Queue。只要DAG 一建立起來之后,那么Scanner其實(shí)就沒有再工作的必要了。“事件驅(qū)動”的機(jī)制提供了一種增量的方式讓GC 來監(jiān)控k8s 集群內(nèi)部的資源對象變化情況。

總結(jié)

docker+Kubernetes確實(shí)是有效緩解分布式架構(gòu)升級改造后虛擬機(jī)設(shè)備激增代理的運(yùn)維壓力的一劑良方。充分熟悉Kubernetes的各種使用技巧,優(yōu)化對容器資源的有效管理和配置,是進(jìn)一步提升系統(tǒng)運(yùn)行性能,支撐系統(tǒng)穩(wěn)定運(yùn)行的基礎(chǔ),隨著容器化改造的不斷深入,早期的那種套用傳統(tǒng)虛擬機(jī)架構(gòu)下的運(yùn)維管理方式必然將被更有彈性更靈活的方式取代,請大家開始主動學(xué)習(xí)和掌握Kubernetes,以應(yīng)對未來的要求和挑戰(zhàn)吧。

THEEND

最新評論(評論僅代表用戶觀點(diǎn))

更多
暫無評論