更多kubernetes文章:weiliang-ms/kubernetes-docs: kubernetes學習筆記 (github.com)
Memory Manager介紹說明
Memory Manager(譯為內存管理器)是 kubelet 內部的一個組件,旨在為 Guaranteed QoS 類型 pod 提供保證內存(和大頁內存)分配功能,該特性提供了幾種分配策略:
- 單 NUMA 策略:用于高性能和性能敏感的應用程序
- 多 NUMA 策略:補充完善單 NUMA 策略無法管理的情況
也就是說,只要 pod 所需的內存量超過單個 NUMA 節點的容量,就會使用多 NUMA 策略跨多個 NUMA 節點提供保證的內存。
在這兩種場景中,內存管理器都使用提示生成協議為 pod 生成最合適的 NUMA 關聯,并將這些關聯提示提供給中央管理器(Topology Manager)。此外,內存管理器確保 pod 請求的內存從最小數量的 NUMA 節點分配。
從技術上講,單 NUMA 策略是多 NUMA 策略的一種特殊情況,因此 kubernetes 開發團隊沒有為它們開發單獨的實現。
什么是NUMA?
早期的計算機,內存控制器還沒有整合進 CPU,所有的內存訪問都需要經過北橋芯片來完成。如下圖所示,CPU 通過前端總線(FSB,Front Side Bus)連接到北橋芯片,然后北橋芯片連接到內存——內存控制器集成在北橋芯片里面。
上面這種架構被稱為 UMA(Uniform Memory Access, 一致性內存訪問 ):總線模型保證了 CPU 的所有內存訪問都是一致的,不必考慮不同內存地址之間的差異。
在 UMA 架構下,CPU 和內存之間的通信全部都要通過前端總線。而提高性能的方式,就是不斷地提高 CPU、前端總線和內存的工作頻率。
由于物理條件的限制,不斷提高工作頻率的方式接近瓶頸。CPU 性能的提升開始從提高主頻轉向增加 CPU 數量(多核、多 CPU)。越來越多的 CPU 對前端總線的爭用,使前端總線成為了瓶頸。為了消除 UMA 架構的瓶頸,NUMA(Non-Uniform Memory Access, 非一致性內存訪問)架構誕生了:
- CPU 廠商把內存控制器集成到 CPU 內部,一般一個 CPU socket 會有一個獨立的內存控制器。
- 每個 CPU scoket 獨立連接到一部分內存,這部分 CPU 直連的內存稱為“本地內存”。
- CPU 之間通過 QPI(Quick Path Interconnect) 總線進行連接。CPU 可以通過 QPI 總線訪問不和自己直連的“遠程內存”。
和 UMA 架構不同,在 NUMA 架構下,內存的訪問出現了本地和遠程的區別:訪問遠程內存的延時會明顯高于訪問本地內存。
什么是大頁內存?
大頁內存(HugePages),有時也叫“大內存頁”、“內存大頁”、“標準大頁”。計算機內存以頁的形式分配給進程。通常這些頁相當小,這意味著消耗大量內存的進程也將消耗大量的頁。對于那些內存操作非常頻繁的業務來說,大頁內存可以有效的提高性能。簡而言之,通過啟用大頁內存,系統只需要處理較少的頁面映射表,從而減少訪問/維護它們的開銷!大頁內存在數據庫服務器這樣的系統上特別有用。像 MySQL 和 PostgreSQL 這樣的進程可以使用大頁內存,以減少對 RAM 緩存的壓力。
什么是Guaranteed QoS pod?
Kubernetes 通過 服務質量類(Quality of Service class,QoS class)定義了三種 pod 類型, Kubernetes 在 Node 資源不足時使用 QoS 類來就驅逐 Pod 作出決定:
- Guaranteed: pod 中 每個容器 limits.cpu == requests.cpu && limits.memory== requests.memory. Guaranteed 類型 Pod 具有最嚴格的資源限制,并且最不可能面臨驅逐。
- Burstable: Pod 中至少一個容器有內存或 CPU 的 request 或 limit 且非 Guaranteed。
- BestEffort: Pod 不滿足 Guaranteed 或 Burstable 的判據條件,即Pod 中的所有容器沒有設置內存 limit 或內存 request,也沒有設置 CPU limit 或 CPU request 。
三種 pod 優先級:Guaranteed > Burstable > BestEffort
Memory Manager開發動機
- 為容器(同一 pod 內的容器)提供最小數量的 NUMA 節點上有保證的內存(和大頁內存)分配。
- 保證整個容器組(同一 pod 內的容器)的內存和大頁面與相同 NUMA 節點的關聯。
內存管理器的設計應用對于性能敏感的程序、數據庫、虛擬化影響巨大:
由于數據庫(例如,Oracle, PostgreSQL和MySQL)需要相對大量的內存和大頁內存,來相對有效地訪問大量數據。而為了減少由跨 NUMA 內存訪問和共享引起的延遲,所有資源(CPU內核、內存、大頁內存和I/O設備)都應該對齊到同一個 NUMA 節點,這將極大地提高穩定性和性能。
并且內存數據庫(Redis等)具有更大的內存需求,可以擴展到多個 NUMA 節點。這就產生了跨多個 NUMA 節點維持和管理內存的需求。
Memory Manager架構設計
一旦 kubelet 請求 Guaranteed QoS 類型 pod 許可,如上圖所示,拓撲管理器(Topology Manager)就會向內存管理器 (Memory Manager) 查詢 pod 中所有容器的內存和大頁內存的首選 NUMA 親和關系。
對于 pod 中的每個容器,內存管理器使用其內部數據庫(即Node Map)計算關聯。Node Map 是一個對象,它負責跟蹤 Guaranteed QoS 類 Pod 中所有容器的內存(和大頁內存)的使用情況。一旦內存管理器完成計算,它將結果返回給拓撲管理器,以便拓撲管理器可以計算出哪個 NUMA 節點或一組 NUMA 節點最適合容器的內存固定。對 pod 中的所有容器執行總體計算,如果沒有容器被拒絕,則 kubelet 最終接收并部署該 pod。
在 pod 接收階段,內存管理器調用 Allocate() 方法并更新其 Node Map 對象。隨后內存管理器調用 AddContainer() 方法并強制分配容器的內存和大頁內存,并限制到對應 NUMA 節點或 NUMA 節點組。最終通過CRI 接口更新控制組配置項(cpuset.mems項)。
內存管理器為(且僅為) Guaranteed QoS類中的pod提供有保證的內存分配。
多NUMA節點保證內存分配原理
主要思想是將一組 NUMA 節點視為一個獨立的單元,并由內存管理器管理這些獨立的單元。
NUMA 節點組不能相交。下面的圖舉例說明了一組不相交的 NUMA 組。圖中的組是不相交的,即:[0],[1,2],[3]。必須遵守該規則,因為重疊的組基本上不能確保在多個 NUMA 節點上有保證的內存分配。
例如,以下組重疊,[0,1],[1,2]和[3],因為它們有一個以1為索引的公共 NUMA 節點。換句話說,如果組重疊(比如:[0,1]和[1,2]),則[1,2]組的內存資源可能會優先被另一組([0,1])消耗,[1,2]組可用內存資源會被搶占。
節點映射(Node Map)
內存管理器有一個內部數據庫,即節點映射(Node Map),它包含內存映射(Memory Map)。該數據庫用于記錄在 Guaranteed QoS 類中為已部署的容器保留的內存。節點映射對象記錄 Node 對象中不相交的 NUMA-node組的動態配置。注意,實際上內存映射提供了跟蹤內存的計數器。因此,不應該將映射理解為允許保留內存范圍或連續內存塊的映射。
在部署容器時,還使用映射來計算NUMA關聯。內存管理器支持傳統內存和各種可能大小的大頁內存(例如2 MiB或1 GiB),節點向內存管理器提供三種類型的內存,即:常規內存、hugepages-1Gi和hugepages-2Mi。
在啟動時,內存管理器為每個 NUMA 節點和各自的內存類型初始化一個 Memory Table 集合,從而生成準備使用的內存映射對象。
// MemoryTable 包含內存信息
type MemoryTable struct {
TotalMemSize uint64 `json:"total"`
SystemReserved uint64 `json:"systemReserved"`
Allocatable uint64 `json:"allocatable"`
Reserved uint64 `json:"reserved"` Free uint64 `json:"free"`
}
// NodeState 包含 NUMA 節點關聯信息
type NodeState struct {
// NumberOfAssignments contains a number memory assignments from this node
// When the container requires memory and hugepages it will increase number of assignments by two
NumberOfAssignments int `json:"numberOfAssignments"`
// MemoryTable 包含 NUMA 節點內存關聯信息
MemoryMap map[v1.ResourceName] *MemoryTable `json:"memoryMap"`
// NodeGroups contains NUMA nodes that current NUMA node in group with them
// It means that we have container that pinned to the current NUMA node and all group nodes
Nodes []int `json:"nodes"`
}
// NodeMap 包含 每個 NUMA 節點的內存信息.
type NodeMap map[int] *NodeState
內存映射(Memory Map)
內存映射用于跟蹤每個 NUMA 節點的內存使用情況。內存映射包含幾個計數器,用于跟蹤內存使用情況。存在以下等式:
Allocatable = TotalMemSize - SystemReserved
Free + Reserved = Allocatable
Free = Allocatable - Reserved
Reserved = Allocatable - Free
- TotalMemSize 的值由 cadvisor 為每種內存類型(常規內存、hugepages-1Gi等)提供給內存管理器。TotalMemSize 的值是恒定的,表示 NUMA 節點上可用的特定類型內存的總(最大)容量。
- SystemReserved 由 systemReserved 配置項設置,表示預留給系統服務的資源大小(如kubelet、其他系統服務)
- Reserved 表示 Guaranteed QoS 類型 pod 中為容器預留的保證內存總量
啟動階段內存映射
下圖展示了節點啟動后不久的內存映射(常規內存):
SystemReserved 的值是由 kubelet 啟動參數預先配置。SystemReserved 在運行時保持不變,因此Allocatable在運行時也是不變的。
SystemReserved 表示系統預留的內存大小,用于系統,即內核、操作系統守護進程和核心節點組件,如 kubelet (kubelet守護進程)。
運行階段內存映射
下圖中,容器A和容器B實際消耗的內存(紅色色塊、紫色色塊)少于內存管理器保留的(保證內存)內存大小(紅色虛線框、紫色虛線框),所以對于兩個容器來說,它們的內存消耗都低于它們的內存限制(limits.memroy),并且容器正常運行。
當 kubernetes 節點發生 OOM 時(cgroups內存限制、hard-eviction-treshold 等),由 kubelet、系統內核(linux oom killer)進行處理,而非由內存管理器處理。
工作方式
下面展示內存管理器如何管理不同 QoS 類(Guaranteed, bestefort /Burstable)中的 pod,以及內存管理器如何動態管理(創建或刪除) NUMA 節點的非相交組。
多NUMA節點
- 創建 pod1 ,其中 pod1 內存配額為15G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod)
內存管理器隨后創建 名為 group1 的組,group1 包含 NUMA 節點與 pod1,同時更新 pod1 控制組cpuset.mems 參數[[0,1], 15G]
- 創建 pod2 ,其中 pod2 內存配額為5G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod)
盡管 group1剩余內存(5G)滿足 Pod2 內存需求(5G),但由于 group1 是多 NUMA 節點,集群節點中存在能滿足 pod2 內存需求的單 NUMA 節點(假設存在), 所以 pod2 準入請求將被拒絕。
- 當 pod3 (非Guaranteed QoS類型)嘗試加入 group1 時,將被放行 。這是為什么呢?
因為內存管理器只管理 Guaranteed QoS類型 Pod,對于非Guaranteed QoS類型Pod一律放行準入。
由于 pod3 是非Guaranteed QoS類型 pod , pod3 內存使用有可能會超過 5G 時,當出現這種情況時將會將觸發 OOM。這種 OOM 情況有兩種處理機制:
- kubelet 觸發 pod 驅逐機制,進而釋放出更多可用內存
- 觸發linux OOM killer,優先 kill 低優先級進程(如:BestEffort/QoS、Burstable/QoS類型 pod)
兩種機制優先級:kubelet > linux OOM killer
單NUMA節點
- 創建 pod4 ,其中 pod4 內存配額為2G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod)
內存管理器隨后創建 名為 group2 的組,group2 包含 NUMA 節點與 pod4
- 創建 pod5 ,其中 pod5內存配額為6G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod)
由于group2仍有8G可用內存,pod5將被加入到group2
- 創建 pod6 ,其中 pod6內存配額為3G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod),由于group2僅有2G可用內存,pod6將被加入到group3
- 創建 pod7 ,其中 pod6內存配額為8G,且 requests.memory == limits.memory (即Guaranteed QoS 類型pod),由于group3僅有7G可用內存,pod7準入請求將被拒絕。(盡管group2 + group3 剩余容量之和滿pod7,但無法跨group)
參考資料