如何使用阿里云容器服务保障容器的内存资源质量
作者:韓柔剛(申信)
背景
云原生場景中,應用程序通常以容器的形式部署和分配物理資源。以 Kubernetes 集群為例,應用工作負載以 Pod 聲明了資源的 Request/Limit,Kubernetes 則依據聲明進行應用的資源調度和服務質量保障。
當容器或宿主機的內存資源緊張時,應用性能會受到影響,比如出現服務延時過高或者 OOM 現象。一般而言,容器內應用的內存性能受兩方面的影響:
前文《阿里云容器服務差異化 SLO 混部技術實踐》和《如何合理使用 CPU 管理策略,提升容器性能》分別闡述了阿里云在云原生混部、容器 CPU 資源管理方面的實踐經驗和優化方法,本文同大家探討容器使用內存資源時的困擾問題和保障策略。
容器內存資源的困擾
Kubernetes 的內存資源管理
部署在 Kubernetes 集群中的應用,在資源使用層面遵循標準的 Kubernetes Request/Limit 模型。在內存維度,調度器參考 Pod 聲明的 Memory Request 進行決策,節點側由 Kubelet 和容器運行時將聲明的 Memory Limit 設置到 Linux 內核的 cgroups 接口,如下圖所示:
CGroups(Control Groups,簡稱 cgroups)是 Linux 上管理容器資源使用的機制,系統可以通過 cgroups 對容器內進程的 CPU 和內存資源用量作出精細化限制。而 Kubelet 正是通過將容器的 Request/Limit 設置到 cgroup 接口,實現對 Pod 和 Container 在節點側的可用資源約束,大致如下:
Kubelet 依據 Pod/Container 的 Memory Limit 設置 cgroups 接口 memory.limit_in_bytes,約束了容器的內存用量上限,CPU 資源維度也有類似限制,比如 CPU 時間片或綁定核數的約束。對于 Request 層面,Kubelet 依據 CPU Request 設置 cgroups 接口 cpu.shares,作為容器間競爭 CPU 資源的相對權重,當節點的 CPU 資源緊張時,容器間共享 CPU 時間的比例將參考 Request 比值進行劃分,滿足公平性;而 Memory Request 則默認未設置 cgroups 接口,主要用于調度和驅逐參考。
Kubernetes 1.22 以上版本基于 cgroups v2 支持了 Memory Request 的資源映射(內核版本不低于 4.15,不兼容cgroups v1,且開啟會影響到節點上所有容器)。
例如,節點上 Pod A 的 CPU Request 是 2 核,Pod B 的 CPU Request 是 4 核,那么當節點的 CPU 資源緊張時,Pod A 和 Pod B 使用 CPU 資源的相對比例是 1:2。而在節點的內存資源緊張時,由于 Memory Request 未映射到 cgroups 接口,容器間的可用內存并不會像 CPU 一樣按 Request 比例劃分,因此缺少資源的公平性保障。
云原生場景的內存資源使用
在云原生場景中,容器的內存 Limit 設置影響著容器自身和整個宿主機的內存資源質量。
由于 Linux 內核的原則是盡可能使用內存而非不間斷回收,因此當容器內進程申請內存時,內存用量往往會持續上升。當容器的內存用量接近 Limit 時,將觸發容器級別的同步內存回收,產生額外延時;如果內存的申請速率較高,還可能導致容器 OOM (Out of Memory) Killed,引發容器內應用的運行中斷和重啟。
容器間的內存使用同時也受宿主機內存 Limit 的影響,如果整機的內存用量較高,將觸發全局的內存回收,嚴重時會拖慢所有容器的內存分配,造成整個節點的內存資源質量下降。
在 Kubernetes 集群中,Pod 之間可能有保障優先級的需求。比如高優先級的 Pod 需要更好的資源穩定性,當整機資源緊張時,需要盡可能地避免對高優先級 Pod 的影響。然而在一些真實場景中,低優先級的 Pod 往往運行著資源消耗型任務,意味著它們更容易導致大范圍的內存資源緊張,干擾到高優先級 Pod 的資源質量,是真正的“麻煩制造者”。對此 Kubernetes 目前主要通過 Kubelet 驅逐使用低優先級的 Pod,但響應時機可能發生在全局內存回收之后。
使用容器內存服務質量保障容器內存資源
容器內存服務質量
Kubelet 在 Kubernetes 1.22 以上版本提供了 MemoryQoS 特性,通過 Linux cgroups v2 提供的 memcg QoS 能力來進一步保障容器的內存資源質量,其中包括:
? 將容器的 Memory Request 設置到 cgroups v2 接口 memory.min,鎖定請求的內存不被全局內存回收。
? 基于容器的 Memory Limit 設置 cgroups v2 接口 memory.high,當 Pod 發生內存超用時(Memory Usage > Request)優先觸發限流,避免無限制超用內存導致的 OOM。
上游方案能夠有效解決 Pod 間內存資源的公平性問題,但從用戶使用資源的視角來看,依然存在一些不足:
? 當 Pod 的內存聲明 Request = Limit 時,容器內依然可能出現資源緊張,觸發的 memcg 級別的直接內存回收可能影響到應用服務的 RT(響應時間)。
? 方案目前未考慮對 cgroups v1 的兼容,在 cgroups v1 上的內存資源公平性問題仍未得到解決。
阿里云容器服務 ACK 基于 Alibaba Cloud Linux 2 的內存子系統增強,用戶可以在 cgroups v1 上提前使用到更完整的容器 Memory QoS 功能,如下所示:
典型場景
內存超賣
在云原生場景下,應用管理員可能會為容器設置一個大于 Request 的 Memory Limit,以增加調度靈活性,降低 OOM 風險,優化內存資源的可用性;對于內存利用率較低的集群,資源管理員也可能使用這種方式來提高利用率,作為降本增效的手段。但是,這種方式可能造成節點上所有容器的 Memory Limit 之和超出物理容量,使整機處于內存超賣(memory overcommit)狀態。當節點發生內存超賣時,即使所有容器的內存用量明顯低于 Limit 值,整機內存也可能觸及全局內存回收水位線。因此,相比未超賣狀態,內存超賣時更容易出現資源緊張現象,一旦某個容器大量申請內存,可能引發節點上其他容器進入直接內存回收的慢速路徑,甚至觸發整機 OOM,大范圍影響應用服務質量。
Memory QoS 功能通過啟用容器級別的內存后臺異步回收,在發生直接回收前先異步回收一部分內存,能改善觸發直接回收帶來的延時影響;對于聲明 Memory Request < Limit 的 Pod,Memory QoS 支持為其設置主動內存回收的水位線,將內存使用限制在水位線附近,避免嚴重干擾到節點上其他 Pod。
混合部署
Kubernetes 集群可能在同一節點部署了有著不同資源使用特征的 Pods。比如,Pod A 運行著在線服務的工作負載,內存利用率相對穩定,屬于延時敏感型業務(Latency Sensitive,簡稱 LS);Pod B 運行著大數據應用的批處理作業,啟動后往往瞬間申請大量內存,屬于資源消耗型業務(Best Effort,簡稱 BE)。當整機內存資源緊張時,Pod A 和 Pod B 都會受到全局內存回收機制的干擾。實際上,即使當前 Pod A 的內存使用量并未超出其 Request 值,其服務質量也會受到較大影響;而 Pod B 可能本身設置了較大的 Limit,甚至是未配置 Limit 值,使用了遠超出 Request 的內存資源,是真正的“麻煩制造者”,但未受到完整的約束,從而破壞了整機的內存資源質量。
Memory QoS 功能通過啟用全局最低水位線分級和內核 memcg QoS,當整機內存資源緊張時,優先從 BE 容器中回收內存,降低全局內存回收對 LS 容器的影響;也支持優先回收超用的的內存資源,保障內存資源的公平性。
技術內幕
Linux 的內存回收機制
如果容器聲明的內存 Limit 偏低,容器內進程申請內存較多時,可能產生額外延時甚至 OOM;如果容器的內存 Limit 設置得過大,導致進程大量消耗整機內存資源,干擾到節點上其他應用,引起大范圍的業務時延抖動。這些由內存申請行為觸發的延時和 OOM 都跟 Linux 內核的內存回收機制密切相關。
容器內進程使用的內存頁面(page)主要包括:
? 匿名頁:來自堆、棧、數據段,需要通過換出到交換區(swap-out)來回收(reclaim)。
? 文件頁:來自代碼段、文件映射,需要通過頁面換出(page-out)來回收,其中臟頁要先寫回磁盤。
? 共享內存:來自匿名 mmap 映射、shmem 共享內存,需要通過交換區來回收。
Kubernetes 默認不支持 swapping,因此容器中可直接回收的頁面主要來自文件頁,這部分也被稱為 page cache(對應內核接口統計的 Cached 部分,該部分還會包括少量共享內存)。由于訪問內存的速度要比訪問磁盤快很多,Linux 內核的原則是盡可能使用內存,內存回收(如 page cache)主要在內存水位比較高時才觸發。
具體而言,當容器自身的內存用量(包含 page cache)接近 Limit 時,會觸發 cgroup(此處指 memory cgroup,簡稱 memcg)級別的直接內存回收(direct reclaim),回收干凈的文件頁,這個過程發生在進程申請內存的上下文,因此會造成容器內應用的卡頓。如果此時的內存申請速率超出回收速率,內核的 OOM Killer 將結合容器內進程運行和使用內存的情況,終止一些進程以進一步釋放內存。
當整機內存資源緊張時,內核將根據空閑內存(內核接口統計的 Free 部分)的水位觸發回收:當水位達到 Low 水位線時,觸發后臺內存回收,回收過程由內核線程 kswapd 完成,不會阻塞應用進程運行,且支持對臟頁的回收;而當空閑水位達到 Min 水位線時(Min < Low),會觸發全局的直接內存回收,該過程發生在進程分配內存的上下文,且期間需要掃描更多頁面,因此十分影響性能,節點上所有容器都可能被干擾。當整機的內存分配速率超出且回收速率時,則會觸發更大范圍的 OOM,導致資源可用性下降。
Cgroups-v1 Memcg QoS
Kubernetes 集群中 Pod Memory Request 部分未得到充分保障,因而當節點內存資源緊張時,觸發的全局內存回收能破壞 Pod 間內存資源的公平性,資源超用(Usage > Request)的容器可能和未超用的容器競爭內存資源。
對于容器內存子系統的服務質量(memcg QoS),Linux 社區在 cgroups v1 上提供了限制內存使用上限的能力,其也被 Kubelet 設置為容器的 Limit 值,但缺少對內存回收時的內存使用量的保證(鎖定)能力,僅在cgroups v2 接口上支持該功能。
Alibaba Cloud Linux 2 內核在 cgroups v1 接口中默認開啟 memcg QoS 功能,阿里云容器服務 ACK 通過Memory QoS 功能為 Pod 自動設置合適的 memcg QoS 配置,節點無需升級 cgroups v2 即可支持容器 Memory Request 的資源鎖定和 Limit 限流能力,如上圖所示:
? memory.min:設置為容器的 Memory Request。基于該接口的內存鎖定的能力,Pod 可以鎖定 Request 部分的內存不被全局回收,當節點內存資源緊張時,僅從發生內存超用的容器中回收內存。
? memory.high:當容器 Memory Request < Limit 或未設置 Limit 時,設置為 Limit 的一個比例。基于該接口的內存限流能力,Pod 超用內存資源后將進入限流過程,BestEffort Pod 不能嚴重超用整機內存資源,從而降低了內存超賣時觸發全局內存回收或整機 OOM 的風險。
更多關于 Alibaba Cloud Linux 2 memcg QoS 能力的描述,可參見官網文檔:
https://help.aliyun.com/document_detail/169536.html
內存后臺異步回收
前面提到,系統的內存回收過程除了發生在整機維度,也會在容器內部(即 memcg 級別)觸發。當容器內的內存使用接近 Limit 時,將在進程上下文觸發直接內存回收邏輯,從而阻塞容器內應用的性能。
針對該問題,Alibaba Cloud Linux 2 增加了容器的后臺異步回收功能。不同于全局內存回收中 kswapd 內核線程的異步回收,本功能沒有創建 memcg 粒度的 kswapd 線程,而是采用了 workqueue 機制來實現,同時支持 cgroups v1 和 cgroups v2 接口。
如上圖所示,阿里云容器服務 ACK 通過 Memory QoS 功能為 Pod 自動設置合適的后臺回收水位線 memory.wmark_high。當容器的內存水位達到該閾值時,內核將自動啟用后臺回收機制,提前于直接內存回收,規避直接內存回收帶來的時延影響,改善容器內應用的運行質量。
更多關于 Alibaba Cloud Linux 2 內存后臺異步回收能力的描述,可參見官網文檔:
https://help.aliyun.com/document_detail/169535.html
全局最低水位線分級
全局的直接內存回收對系統性能有很大影響,特別是混合部署了時延敏感型業務(LS)和資源消耗型任務(BE)的內存超賣場景中,資源消耗型任務會時常瞬間申請大量的內存,使得系統的空閑內存觸及全局最低水位線(global wmark_min),引發系統所有任務進入直接內存回收的慢速路徑,進而導致延敏感型業務的性能抖動。在此場景下,無論是全局 kswapd 后臺回收還是 memcg 后臺回收,都將無法有效避免該問題。
針對上述場景,Alibaba Cloud Linux 2 增加了 memcg 全局最低水位線分級功能,允許在全局最低水位線(global wmark_min)的基礎上,通過 memory.wmark_min_adj 調整 memcg 級別生效的水位線。阿里云容器服務 ACK 通過 Memory QoS 功能為容器設置分級水位線,在整機 global wmark_min 的基礎上,上移 BE 容器的 global wmark_min,使其提前進入直接內存回收;下移 LS 容器的 global wmark_min,使其盡量避免直接內存回收,如下圖所示:
這樣,當 BE 任務瞬間大量申請內存的時候,系統能通過上移的 global wmark_min 將其短時間抑制,避免促使 LS 發生直接內存回收。等待全局 kswapd 回收一定量的內存后,再解決對 BE 任務的短時間抑制。
更多關于 Alibaba Cloud Linux 2 memcg 全局最低水位線分級能力的描述,可參見官網文檔:
https://help.aliyun.com/document_detail/169537.html
小結
綜上,容器內存服務質量(Memory QoS)基于 Alibaba Cloud Linux 2 內核保障容器的內存資源質量,各能力的推薦使用場景如下:
我們使用 Redis Server 作為時延敏感型在線應用,通過模擬內存超賣和壓測請求,驗證開啟 Memory QoS 對應用延時和吞吐的改善效果:
對比以上數據可得知,開啟容器內存服務質量后,Redis 應用的平均時延和平均吞吐都得到了一定改善。
總結
針對云原生場景下容器使用內存的困擾,阿里云容器服務 ACK 基于 Alibaba Cloud Linux 2 內核提供了容器內存服務質量(Memory QoS)功能,通過調配容器的內存回收和限流機制,保障內存資源公平性,改善應用的運行時內存性能。Memory QoS 屬于相對靜態的資源質量方案,適合作為保障 Kubernetes 集群內存使用的兜底機制,而對于復雜的資源超賣和混合部署場景,更加動態和精細化的內存保障策略顯得不可或缺。例如,對于內存水位的頻繁波動,基于實時資源壓力指標的驅逐策略,能夠敏捷地在用戶態進行減載(load schedding);另一方面,可以通過更細粒度地發掘內存資源,比如基于冷熱頁面標記的內存回收,或 Runtime (e.g. JVM) GC,來達到高效的內存超賣。敬請期待阿里云容器服務 ACK 支持差異化 SLO 功能的后續發布。
總結
以上是生活随笔為你收集整理的如何使用阿里云容器服务保障容器的内存资源质量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 恭喜我的同事丁宇入选年度 IT 领军人物
- 下一篇: 足不出户完成交付独家交付秘籍(第二回)