如何使用阿里雲容器服務保障容器的記憶體資源質量

語言: CN / TW / HK

作者:韓柔剛(申信)

背景

雲原生場景中,應用程式通常以容器的形式部署和分配物理資源。以 Kubernetes 叢集為例,應用工作負載以 Pod 聲明瞭資源的 Request/Limit,Kubernetes 則依據宣告進行應用的資源排程和服務質量保障。

當容器或宿主機的記憶體資源緊張時,應用效能會受到影響,比如出現服務延時過高或者 OOM 現象。一般而言,容器內應用的記憶體效能受兩方面的影響:

  1. 自身記憶體限制:當容器自身的記憶體(含 page cache)接近容器上限時,會觸發核心的記憶體子系統運轉,此時容器內應用的記憶體申請和釋放的效能受到影響。
  2. 宿主機記憶體限制:當容器記憶體超賣(Memory Limit > Request)導致整機記憶體出現緊張時,會觸發核心的全域性記憶體回收,這個過程的效能影響更甚,極端情況能導致整機夯死。

前文《阿里雲容器服務差異化 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 功能,如下所示:

  1. 保障 Pod 間記憶體回收的公平性,當整機記憶體資源緊張時,優先從記憶體超用(Usage > Request)的 Pod 中回收記憶體,約束破壞者以避免整機資源質量的下降。
  2. 當 Pod 的記憶體用量接近 Limit 時,優先在後臺非同步回收一部分記憶體,緩解直接記憶體回收帶來的效能影響。
  3. 節點記憶體資源緊張時,優先保障 Guaranteed/Burstable Pod 的記憶體執行質量。 在這裡插入圖片描述

典型場景

記憶體超賣

在雲原生場景下,應用管理員可能會為容器設定一個大於 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 功能的後續釋出。

釋出雲原生技術最新資訊、彙集雲原生技術最全內容,定期舉辦雲原生活動、直播,阿里產品及使用者最佳實踐釋出。與你並肩探索雲原生技術點滴,分享你需要的雲原生內容。

關注【阿里巴巴雲原生】公眾號,獲取更多雲原生實時資訊!