K8s Node 從垃圾回收到資源殘留,你看明白了嗎?
眾所周知,K8s 的控制器是面向終態(tài)的控制循環(huán)模式,一般的控制循環(huán)不會(huì)考慮復(fù)雜狀態(tài)流轉(zhuǎn),而是計(jì)算當(dāng)前和預(yù)期的狀態(tài)差異,進(jìn)行調(diào)整。但 Kubelet 是個(gè)例外,作為實(shí)際的資源控制者,有太多需要考慮的細(xì)節(jié),因此許多非核心操作均是異步執(zhí)行,包括資源的垃圾回收。
什么是垃圾
Kubelet 視角的 Pod 狀態(tài)
Kubelet 的控制循環(huán)中有大量的狀態(tài)參數(shù),這些異步的操作均依賴于這些狀態(tài)的準(zhǔn)確性。但 Pod 依賴的組件和資源多種多樣,早期的 K8s 版本,因?yàn)闋顟B(tài)不準(zhǔn)確、不一致、遺漏、竟態(tài)條件,導(dǎo)致的 Kubelet 行為異常的 Bug 比比皆是。
為了解決這些問題,Kubelet 提供了兩個(gè)不同的狀態(tài)子系統(tǒng)::
- podManager:反映 Pod 從 APIServer 角度看的狀態(tài),也就是期望狀態(tài)
- podWorkers:反映 Pod 從 Kubelet Worker 透出的狀態(tài),也就是事實(shí)狀態(tài)
根據(jù)這兩個(gè)狀態(tài)以及對(duì)比,就可以算出當(dāng)前 Pod 需要進(jìn)行的操作,以及某些操作是否安全。資源垃圾回收和兩個(gè) podWorkers 狀態(tài)相關(guān):
狀態(tài)名 | 含義 | 可能的情況 |
| 是否應(yīng)該積極清理所有和 Pod 相關(guān)的資源 | Pod 被驅(qū)逐 or Pod 被刪除并且容器停止 |
| 是否應(yīng)該積極清理 Pod 的運(yùn)行時(shí)資源 | 所有的容器已停止 |
容器回收策略
容器的回收以 1分鐘為間隔定期觸發(fā),不支持修改頻率。其規(guī)則受到很多條件的限制,但基本可以分為下面幾個(gè)情況。
- 清理退出 Pod 的容器,如果 Pod 在 podWorkers 中處于下面的狀態(tài),就觸發(fā)容器回收:
Pod 處于ShouldPodContentBeRemoved
Full GC 并且 Pod 處于 ShouldPodRuntimeBeRemoved
- 單個(gè)Pod 的非運(yùn)行容器數(shù)超出限制(--maximum-dead-containers-per-container)
- 總非運(yùn)行容器數(shù)超出限制(--maximum-dead-containers)
- 如果 2 和 3 無法同時(shí)滿足,則優(yōu)先保證 3
對(duì)于 k8s 管理的容器來說,不要通過 docker 等工具自行進(jìn)行容器回收,有一些退出容器起到占位符的作用,如果被刪除,k8s 可能會(huì)重啟把容器拉起。比如 docker system prune 會(huì)清理掉 init container 的退出容器,Kubelet 會(huì)誤認(rèn)為 init container 未執(zhí)行,嘗試將 init container 重新拉起(如果你的 Pod 無法重入,那就慘了)。
Sandbox 回收策略
Pod 生命周期主流程里并沒有同步的去刪除 Sandbox,而是依賴 GC 的能力完成 Sandbox 清理。
Sandbox 殘留是 containerd 場景下常見的問題,如果 containerd 部分資源釋放有問題,比如掛載點(diǎn)問題,偶見容器已經(jīng)清理但 Sandbox 還在。如果經(jīng)年累月,可能會(huì)出現(xiàn)這么一個(gè)錯(cuò)誤日志(https://github.com/kubernetes/kubernetes/issues/63858):
grpc: received message larger than max (4195017 vs. 4194304)
Sandbox GC 發(fā)生在 Container GC 之后,基本策略是:
- 清理退出 Pod 的 Sandbox:
Pod 處于ShouldPodContentBeRemoved,刪除和 Pod 關(guān)聯(lián)的全部 Sandbox
Full GC 并且 Pod 處于 ShouldPodRuntimeBeRemoved,刪除和 Pod 關(guān)聯(lián)的全部 Sandbox
- 如果 Pod 還在運(yùn)行中,則只保留一個(gè) Sandbox
鏡像回收策略
鏡像的垃圾回收策略是個(gè)愛恨功能,有時(shí)候你覺得他太勤快了,仿佛容器剛停掉,鏡像就需要重新再拉取一遍,有時(shí)候你感覺他太保守了,節(jié)點(diǎn)頻繁在磁盤壓力徘徊。
其實(shí)為了保持二者的平衡,鏡像的回收策略略微復(fù)雜。和容器回收一樣,鏡像回收也是以一個(gè)固定的時(shí)間間隔觸發(fā),目前是 5min ,不支持自定義配置。
影響 image 回收的配置:
- imageMaximumGCAge:如果設(shè)置,超過這個(gè)時(shí)間未使用鏡像則回收
- imageMinimumGCAge:雖然未使用,但小于這個(gè)值不會(huì)回收(避免清理剛拉取的鏡像)
- imageGCHighThresholdPercent:大于該閾值則進(jìn)行清理
- imageGCLowThresholdPercent:小于該閾值則結(jié)束清理
工作流程
Contaienr GC
圖片
Container GC 過程:
- 按創(chuàng)建時(shí)間,依次判斷需要 GC 的容器
- 優(yōu)雅退出,和執(zhí)行 pre stop hook
- 調(diào)用 container runtime 操作:StopContainer
- 清理容器日志
- 調(diào)用 container runtime 操作:RemoveContainer
Sandbox GC 過程:
- 按創(chuàng)建時(shí)間,依次判斷需要 GC 的 Sandbox
- 如果 Sandbox 包含任何容器,則跳過 GC
- 對(duì)需要 GC 的 Sandbox 依次執(zhí)行 container runtime 操作:
StopPodSandbox
RemovePodSandbox
Image GC
鏡像回收有兩個(gè)重要的數(shù)據(jù)來源:
- imagesInUse :每次觸發(fā)時(shí)獲取正在使用的 image,并更新 imageRecords 中的使用時(shí)間
- imageRecords:記錄了全量的 image,基于此數(shù)據(jù)進(jìn)行可回收判斷
使用中的不回收
標(biāo)記為 pinned 的不回收(https://github.com/containerd/containerd/pull/7944)
根據(jù)發(fā)現(xiàn)鏡像和使用時(shí)間進(jìn)行排序
基本回收流程:
- 如果開啟了 imageMaximumGCAge 配置,則超過該時(shí)間的鏡像會(huì)被回收
- 如果沒有超過 imageGCHighThresholdPercent 則結(jié)束
- 嘗試清理鏡像到足夠滿足 imageGCLowThresholdPercent
在開始 gc 后拉取的鏡像不清理
小于 imageMinimumGCAge 的鏡像不清理
調(diào)用 container runtime 操作:RemoveImage