聊聊docker容器的memory限制

本文主要研究一下docker容器的memory限制

内存限制

arduino 复制代码
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)

yaml 复制代码
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)

makefile 复制代码
# free -h
              total        used        free      shared  buff/cache   available
Mem:          1.8Gi       437Mi       464Mi       2.0Mi       985Mi       1.3Gi
Swap:            0B          0B          0B

这里显示的是宿主机的,而非docker的

查看容器内存指标

shell 复制代码
# 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

css 复制代码
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"]

go 复制代码
// 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
}

github.com/docker/cli/...

k8s中统计

ini 复制代码
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指标,其计算指标如下:

ini 复制代码
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

json 复制代码
        "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

相关推荐
vx_bisheyuange2 分钟前
基于SpringBoot的海鲜市场系统
java·spring boot·后端·毕业设计
李慕婉学姐43 分钟前
【开题答辩过程】以《基于Spring Boot和大数据的医院挂号系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
大数据·spring boot·后端
源代码•宸2 小时前
Leetcode—3. 无重复字符的最长子串【中等】
经验分享·后端·算法·leetcode·面试·golang·string
0和1的舞者2 小时前
基于Spring的论坛系统-前置知识
java·后端·spring·系统·开发·知识
invicinble3 小时前
对于springboot
java·spring boot·后端
码界奇点4 小时前
基于Spring Boot与Vue的校园后台管理系统设计与实现
vue.js·spring boot·后端·毕业设计·源代码管理
爱编程的小庄4 小时前
Rust 发行版本及工具介绍
开发语言·后端·rust
Apifox.5 小时前
测试用例越堆越多?用 Apifox 测试套件让自动化回归更易维护
运维·前端·后端·测试工具·单元测试·自动化·测试用例
sunnyday04265 小时前
Nginx与Spring Cloud Gateway QPS统计全攻略
java·spring boot·后端·nginx
康王有点困5 小时前
Link入门
后端·flink