序
本文主要研究一下docker容器的memory限制
内存限制
docker run -m 512M -p 8081:8080 --rm docker-demo
通过-m参数指定限制的内存大小
buffer/cache
所谓Cache,就是为了弥补高速设备和低速设备之间的矛盾而设立的一个中间层。
缓冲(Buffer)是根据磁盘的读写设计的,它把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。
区别与联系
- 都是为了解决速度不对等的问题。
- 缓存(Cache)是把读取过的数据保存起来,重新读取时若命中(找到需要的数据)就不要去读硬盘了,若没有命中再读硬盘。其中的数据会根据读取频率进行组织,把最频繁读取的内容放在最容易找到的位置,把不再读的内容不断往后排,直至从中删除。
- Buffer是即将要被写入磁盘的,而Cache是被从磁盘中读出来的。
- 在应用场景上,Buffer是由各种进程分配的,被用在如输入队列等方面。一个简单的例子,如某个进程要求有多个字段读入,在所有字段被读入完整之前,进程把先前读入的字段放在Buffer中保存;Cache经常被用在磁盘的I/O请求上,如果有多个进程都要访问某个文件,于是该文件便被做成 Cache以方便下次被访问,这样可提高系统性能。比如linux系统中有一个守护进程定期清空缓冲内容(即写入磁盘),也可以通过 sync 命令手动清空缓冲。
操作系统中的Page Cache与Buffer Cache
磁盘数据会被读取到Page Cache进行缓存,程序要读取数据的时候,可以直接从Page Cache读取,这是读取数据的一条线路。 此外,当Page Cache的数据需要刷新时,Page Cache中的数据会交给Buffer Cache,而Buffer Cache中的所有数据都会定时刷新到磁盘。这是写入数据的另一条线。
page-cache.png
- Page Cache:Page Cache是文件系统层级的缓存,它从磁盘里读取的内容都会存储到这里,这样程序读取磁盘内容就会非常快。例如,使用grep和find等命令查找内容和文件时,第1次会比较慢,再次执行就快好多倍,几乎是瞬间。
- Buffer Cache:Buffer Cache是磁盘等块设备的缓冲,这部分内存数据是要写入到磁盘的。这里需要注意,位于内存 Buffer 中的数据不是即时写入磁盘的,而是系统空闲或者 Buffer达到一定大小统一写到磁盘中,所以断电易失。为了防止数据丢失,最好正常关机或者多执行几次sync命令,让位于Buffer上的数据立刻写到磁盘里。
Page Cache可以极大地提高系统整体性能。例如,进程A读一个文件,内核空间会申请Page Cache与此文件对应,并记录对应关系,进程B再次读同样的文件就会直接命中上一次的Page Cache,读写速度显著提升。但注意,Page Cache会根据LRU算法(最近最少使用)进行替换。
实例
top(不支持docker
)
top - 09:33:37 up 10 min, 0 users, load average: 0.03, 0.17, 0.18
Tasks: 4 total, 1 running, 3 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.2 us, 0.2 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.3 hi, 0.0 si, 0.0 st
MiB Mem : 1887.4 total, 463.7 free, 438.2 used, 985.6 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1303.0 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7 root 20 0 2553756 165584 16608 S 1.0 8.6 0:16.06 java
1 root 20 0 2388 756 692 S 0.0 0.0 0:00.02 sh
82 root 20 0 2388 1448 1356 S 0.0 0.1 0:00.01 sh
98 root 20 0 7980 3100 2672 R 0.0 0.2 0:00.00 top
上面显示的mem也是宿主机的,不是docker实例的
free(不支持docker
)
# free -h
total used free shared buff/cache available
Mem: 1.8Gi 437Mi 464Mi 2.0Mi 985Mi 1.3Gi
Swap: 0B 0B 0B
这里显示的是宿主机的,而非docker的
查看容器内存指标
# cat /sys/fs/cgroup/memory/memory.usage_in_bytes
240824320
# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
536870912
通过/sys/fs/cgroup/memory/底下的文件查看到的就是docker实例使用的以及docker实例的内存限制
docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
7f2f15949afc practical_spence 0.75% 141.8MiB / 512MiB 27.70% 2.23kB / 0B 0B / 0B 45
docker status这里的MEM USAGE统计的是mem.Usage - mem.Stats["inactive_file"]
// calculateMemUsageUnixNoCache calculate memory usage of the container.
// Cache is intentionally excluded to avoid misinterpretation of the output.
//
// On cgroup v1 host, the result is `mem.Usage - mem.Stats["total_inactive_file"]` .
// On cgroup v2 host, the result is `mem.Usage - mem.Stats["inactive_file"] `.
//
// This definition is consistent with cadvisor and containerd/CRI.
// * https://github.com/google/cadvisor/commit/307d1b1cb320fef66fab02db749f07a459245451
// * https://github.com/containerd/cri/commit/6b8846cdf8b8c98c1d965313d66bc8489166059a
//
// On Docker 19.03 and older, the result was `mem.Usage - mem.Stats["cache"]`.
// See https://github.com/moby/moby/issues/40727 for the background.
func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
// cgroup v1
if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage {
return float64(mem.Usage - v)
}
// cgroup v2
if v := mem.Stats["inactive_file"]; v < mem.Usage {
return float64(mem.Usage - v)
}
return float64(mem.Usage)
}
func calculateMemPercentUnixNoCache(limit float64, usedNoCache float64) float64 {
// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
// got any data from cgroup
if limit != 0 {
return usedNoCache / limit * 100.0
}
return 0
}
https://github.com/docker/cli/blob/master/cli/command/container/stats_helpers.go
k8s中统计
func decodeMemory(target *resource.Quantity, memStats *stats.MemoryStats) error {
if memStats == nil || memStats.WorkingSetBytes == nil {
return fmt.Errorf("missing memory usage metric")
}
*target = *uint64Quantity(*memStats.WorkingSetBytes, 0)
target.Format = resource.BinarySI
return nil
}
func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
ret.Memory.Usage = s.MemoryStats.Usage.Usage
ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage
ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
if s.MemoryStats.UseHierarchy {
ret.Memory.Cache = s.MemoryStats.Stats["total_cache"]
ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]
ret.Memory.Swap = s.MemoryStats.Stats["total_swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"]
} else {
ret.Memory.Cache = s.MemoryStats.Stats["cache"]
ret.Memory.RSS = s.MemoryStats.Stats["rss"]
ret.Memory.Swap = s.MemoryStats.Stats["swap"]
ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"]
}
if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
ret.Memory.ContainerData.Pgfault = v
ret.Memory.HierarchicalData.Pgfault = v
}
if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
ret.Memory.ContainerData.Pgmajfault = v
ret.Memory.HierarchicalData.Pgmajfault = v
}
workingSet := ret.Memory.Usage
if v, ok := s.MemoryStats.Stats["total_inactive_file"]; ok {
if workingSet < v {
workingSet = 0
} else {
workingSet -= v
}
}
ret.Memory.WorkingSet = workingSet
}
kubectl top pod命令查询到的内存使用为Memory WorkingSet = Memory.Usage - memory.stat[total_inactive_file]。
k8s的OOMKiller使用的是container_memory_working_set_bytes指标,其计算指标如下:
container_memory_working_set_bytes
= container_memory_usage_bytes - total_inactive_file
= total_cache + total_rss - total_inactive_file
= total_inactive_file + total_active_file + total_rss - total_inactive_file
= total_active_file + total_rss
oom killed
"State": {
"Status": "exited",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": true,
"Dead": false,
"Pid": 0,
"ExitCode": 137,
"Error": "",
"StartedAt": "2024-04-08T08:34:58.271711439Z",
"FinishedAt": "2024-04-08T08:35:57.360091044Z"
}
如果是因为内存原因被kill的话,通过docker inspect 容器id,查看State部分,可以看到
"OOMKilled": true
小结
- docker容器的memory限制使用的是
mem.Usage - mem.Stats["inactive_file"]
与limit的对比,如果超出则会被kill;free及top显示的都是宿主机的内存信息 - kubectl top pod命令是通过memory_working_set(
Memory.Usage - memory.stat[total_inactive_file]
)来统计容器的内存使用 - k8s的OOMKiller使用的是container_memory_working_set_bytes指标(
total_active_file + total_rss
),如果超出该容器的limit,则会被OOMKiller销毁掉
doc
- 高性能Linux服务器运维实战
- 内存统计说明
- Docker容器内存监控
- 正确监控容器 OOMKill 的指标(译)
- Monitors the correct indicator of container OOMKill
- K8s OOMkiller
- 09 | Page Cache:为什么我的容器内存使用量总是在临界点?
- hcache
- k8s pod container内存指标说明
- 容器内存QoS
- buffer/cach内存占用过高及k8s java后端pod容器超出内存限制被kill重启
- Linux查看哪些进程占用的系统 buffer/cache 较高 (hcache,lsof)命令
- Linux cache占用大量内存,如何分析是哪些进程、文件导致?