作者:行疾、尝君、佑祎、六滔
在 Kubernetes 场景,容器内存实时使用量统计(Pod Memory),由 WorkingSet 工作内存(缩写 WSS)来表示。
WorkingSet 这个指标概念,是由 cadvisor 为容器场景定义的。
工作内存 WorkingSet 也是 Kubernetes 的调度决策判断内存资源的指标,包括节点驱逐等。
官方定义:
参考 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
}
}
在 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 相似。
图/Kernel Level Memory Distribution
如上图所示,Pod WorkingSet 工作内存,除了不包含 Inactive(anno),用户使用 Pod 内存的其他各成分不符合预期,都有可能最终造成 WorkingSet 工作负载升高,最终导致发生 Node Eviction 节点驱逐等现象。
如何在众多内存成分中,找到真正导致工作内存升高的真正原因,犹如黑洞一般盲目。(“内存黑洞”就是指这个问题)。
通常情况下,内存回收延时都会伴随着 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 上,控制操作系统对内存的参数: