容器內存可觀測性新視角:WorkingSet 與 PageCache 監控

作者:行疾、嘗君、佑禕、六滔

容器工作內存 WorkingSet 概念介紹

在 Kubernetes 場景,容器內存實時使用量統計(Pod Memory),由 WorkingSet 工作內存(縮寫 WSS)來表示。

WorkingSet 這個指標概念,是由 cadvisor 爲容器場景定義的。

工作內存 WorkingSet 也是 Kubernetes 的調度決策判斷內存資源的指標,包括節點驅逐等。

WorkingSet 計算公式

官方定義:參考 K8s 官網文檔

https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/#eviction-signals

可以由如下兩個腳本在節點上運行從而直接計算結果:

CGroupV1

https://kubernetes.io/examples/admin/resource/memory-available.sh

#!/bin/bash
#!/usr/bin/env bash

# This script reproduces what the kubelet does
# to calculate memory.available relative to root cgroup.

# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/memory/memory.usage_in_bytes)
memory_total_inactive_file=$(cat /sys/fs/cgroup/memory/memory.stat | grep total_inactive_file | awk '{print $2}')

memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
    memory_working_set=0
else
    memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi

memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))

echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_file"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"

CGroupV2

https://kubernetes.io/examples/admin/resource/memory-available-cgroupv2.sh

#!/bin/bash

# This script reproduces what the kubelet does
# to calculate memory.available relative to kubepods cgroup.

# current memory usage
memory_capacity_in_kb=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
memory_capacity_in_bytes=$((memory_capacity_in_kb * 1024))
memory_usage_in_bytes=$(cat /sys/fs/cgroup/kubepods.slice/memory.current)
memory_total_inactive_file=$(cat /sys/fs/cgroup/kubepods.slice/memory.stat | grep inactive_file | awk '{print $2}')

memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
    memory_working_set=0
else
    memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi

memory_available_in_bytes=$((memory_capacity_in_bytes - memory_working_set))
memory_available_in_kb=$((memory_available_in_bytes / 1024))
memory_available_in_mb=$((memory_available_in_kb / 1024))

echo "memory.capacity_in_bytes $memory_capacity_in_bytes"
echo "memory.usage_in_bytes $memory_usage_in_bytes"
echo "memory.total_inactive_file $memory_total_inactive_file"
echo "memory.working_set $memory_working_set"
echo "memory.available_in_bytes $memory_available_in_bytes"
echo "memory.available_in_kb $memory_available_in_kb"
echo "memory.available_in_mb $memory_available_in_mb"

Show me the code

可以看到,節點的 WorkingSet 工作內存是 root cgroup 的 memory usage,減去 Inactve(file) 這部分的緩存。同理,Pod 中容器的 WorkingSet 工作內存,是此容器對應的 cgroup memory usage,減去了 Inactve(file) 這部分的緩存。

真實 Kubernetes 運行時的 kubelet 中,由 cadvisor 提供的這部分指標邏輯的實際代碼如下:

從 cadvisor Code [ 1] 中,可以明確看到對 WorkingSet 工作內存的定義:

The amount of working set memory, this includes recently accessed memory,dirty memory, and kernel memory. Working set is <= "usage".

以及 cadvisor 對 WorkingSet 計算的具體代碼實現 [ 2]

inactiveFileKeyName := "total_inactive_file"
if cgroups.IsCgroup2UnifiedMode() {
  inactiveFileKeyName = "inactive_file"
}
workingSet := ret.Memory.Usage
if v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok {
  if workingSet < v {
    workingSet = 0
  } else {
    workingSet -= v
  }
}

容器內存問題的常見用戶問題 case

在 ACK 團隊爲海量的用戶提供容器場景的服務支持過程中,很多客戶在將自己的業務應用容器化部署時,或多或少都遇到過容器內存問題。ACK 團隊和阿里雲操作系統團隊經過大量客戶問題的經驗沉澱,總結了以下在容器內存方面用戶常見的問題:

常見問題 1:宿主機內存使用率和容器的按節點聚合使用率數值有差距,宿主機 40% 左右,容器 90% 左右

大概率是因爲容器的 Pod 算的 WorkingSet,包含 PageCache 等緩存。

宿主機的內存值,沒有包含 Cache、如 PageCache、Dirty Memory 等部分,而工作內存是包含了此部分。

最常見的場景爲 JAVA 應用的容器化,JAVA 應用的 Log4J,以及其非常流行的實現 Logback,默認的 Appender 會非常“簡單”地開始使用 NIO 的方式,使用 mmap 的方式來使用 Dirty Memory。造成內存 Cache 的上漲,從而造成 Pod 工作內存 WorkingSet 的增長。

一個 JAVA 應用的 Pod 的 Logback 寫日誌場景

造成 Cache 內存、WorkingSet 內存上漲的實例

常見問題 2:在 Pod 中執行 top 命令,獲取到的值比 kubectl top pod 查看到的工作內存值(WorkingSet)小

在 Pod 中 exec 執行 top 命令,由於容器運行時隔離等問題,實際是突破了容器隔離,獲取到了宿主機的 top 監控值。

故看到的是宿主機的內存值,沒有包含 Cache、如 PageCache、Dirty Memory 等部分,而工作內存是包含了此部分,所以與常見問題 1 相似。

常見問題3:Pod 內存黑洞問題

圖/Kernel Level Memory Distribution

如上圖所示,Pod WorkingSet 工作內存,除了不包含 Inactive(anno),用戶使用 Pod 內存的其他各成分不符合預期,都有可能最終造成 WorkingSet 工作負載升高,最終導致發生 Node Eviction 節點驅逐等現象。

如何在衆多內存成分中,找到真正導致工作內存升高的真正原因,猶如黑洞一般盲目。(“內存黑洞”就是指這個問題)。

如何解決 WorkingSet 高問題

通常情況下,內存回收延時都會伴隨着 WorkingSet 內存使用量高出現,那麼如何解決這一類問題呢?

直接擴容

容量規劃(直接擴容)是解決資源高問題的通用解法。

“內存黑洞” - 深層內存成本(如 PageCache )導致怎麼辦

但是如內存診斷問題,需要首先剖析、洞察、分析,或者講人話就是看清楚具體是哪塊內存被誰(哪個進程、或具體哪個資源如文件)持有。然後再針對性地進行收斂優化,從而最終解決問題。

第一步:看清內存

首先怎麼剖析操作系統內核級的容器監控內存指標呢?ACK 團隊與操作系統團隊合作推出了 SysOM(System Observer Monitoring) 操作系統內核層的容器監控的產品功能,目前爲阿里雲獨有;通過查看 SysOM 容器系統監控-Pod 維度中的 Pod Memory Monitor 大盤,可以洞悉 Pod 的詳細內存使用分佈,如下圖:

SysOM 容器系統監控能查看細粒度到每個 Pod 的詳細內存組成。通過 Pod Cache(緩存內存)、InactiveFile(非活躍文件內存佔用)、InactiveAnon(非活躍匿名內存佔用)、Dirty Memory(系統髒內存佔用)等不同內存成分的監控展示,發現常見的 Pod 內存黑洞問題。

對 Pod File Cache,可以同時監控 Pod 當前打開和已關閉文件的 PageCache 使用量(刪除對應的文件就可以釋放對應的 Cache 內存)。

第二步:優化內存

很多深層次的內存消耗,用戶就算看清了也不能輕易進行收斂,比如 PageCache 等由操作系統統一回收的內存,用戶需要大費周章地侵入式改動代碼,比如對 Log4J 的 Appender 加 flush(),來週期性調用 sync()。

https://stackoverflow.com/questions/11829922/logback-file-appender-doesnt-flush-immediately

這都是很不現實的。

ACK 容器服務團隊推出 Koordinator QoS 精細化調度功能

實現在 Kubernetes 上,控制操作系統對內存的參數:

當集羣開啓了差異化 SLO 混部時,系統將優先保障延時敏感型 LS(Latency-Sensitive)Pod 的內存 QoS,延緩 LS Pod 觸發整機內存回收的時機。

下圖中,memory.limit_in_bytes 表示內存使用上限,memory.high 表示內存限流閾值,memory.wmark_high 表示內存後臺回收閾值,memory.min 表示內存使用鎖定閾值。

圖/ack-koordinator 爲容器提供內存服務質量 QoS(Quality of Service)保障能力

內存黑洞問題如何修復,阿里雲容器服務通過精細化調度功能,依託 Koordinator 開源項目,ack-koordinator 爲容器提供內存服務質量 QoS(Quality of Service)保障能力,在確保內存資源公平性的前提下,改善應用在運行時的內存性能。本文簡介容器內存 QoS 功能,具體說明請參見容器內存 QoS [ 3]

容器在使用內存時主要有以下兩個方面的約束:

1)自身內存限制:當容器自身的內存(含 PageCache)接近容器上限時,會觸發容器維度的內存回收,這個過程會影響容器內應用的內存申請和釋放的性能。若內存申請得不到滿足則會觸發容器 OOM。

2)節點內存限制:當容器內存超賣(Memory Limit>Request)導致整機內存不足,會觸發節點維度的全局內存回收,這個過程對性能影響較大,極端情況甚至導致整機異常。若回收不足則會挑選容器 OOM Kill。

針對上述典型的容器內存問題,ack-koordinator 提供了以下增強特性:

1)容器內存後臺回收水位:當 Pod 內存使用接近 Limit 限制時,優先在後臺異步回收一部分內存,緩解直接內存回收帶來的性能影響。

2)容器內存鎖定回收/限流水位:Pod 之間實施更公平的內存回收,整機內存資源不足時,優先從內存超用(Memory Usage>Request)的 Pod 中回收內存,避免個別 Pod 造成整機內存資源質量下降。

3)整體內存回收的差異化保障:在 BestEffort 內存超賣場景下,優先保障 Guaranteed/Burstable Pod 的內存運行質量。

關於 ACK 容器內存 QoS 啓用的內核能力,詳見 Alibaba Cloud Linux 的內核功能與接口概述 [ 4]

在通過第一步觀測發現容器內存黑洞問題之後,可以結合通過 ACK 精細化調度功能針對性挑選內存敏感的 Pod 啓用容器內存 QoS 功能,完成閉環修復。

參考文檔:

[1] ACK SysOM 功能說明文檔

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/sysom-kernel-level-container-monitoring?spm=a2c4g.11186623.0.0.5376162eZWAPFU

[2] 最佳實踐文檔

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/use-sysom-to-locate-container-memory-issues?spm=a2c4g.11186623.0.0.197c162eyFPY5I

[3] 中文龍蜥社區

https://mp.weixin.qq.com/s/b5QNHmD_U0DcmUGwVm8Apw

[4] 國際站英文

https://www.alibabacloud.com/blog/sysom-container-monitoring-from-the-kernels-perspective_600792

相關鏈接:

[1] cadvisor Code

https://github.com/google/cadvisor/blob/50b23f4ed9bc53cf068316b67bee04c4145f1e73/info/v1/container.go#L391

[2] cadvisor 對 WorkingSet 計算的具體代碼實現

https://github.com/google/cadvisor/blob/50b23f4ed9bc53cf068316b67bee04c4145f1e73/container/libcontainer/handler.go#L862

[3] 容器內存 QoS

https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/memory-qos-for-containers

[4] Alibaba Cloud Linux 的內核功能與接口概述

https://help.aliyun.com/zh/ecs/user-guide/overview-23

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章