聊聊docker容器的memory限制

本文主要研究一下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

相关推荐
独自归家的兔22 分钟前
OCPP 1.6 协议详解:GetLocalListVersion 获取本地列表版本指令
java·后端·物联网·spring·ocpp1.6
Apache RocketMQ2 小时前
RocketMQ源码解析——秒级定时消息介绍
java·云原生·消息队列·rocketmq·java-rocketmq
xiaoming00182 小时前
JAVA项目打包部署运维全流程(多服务、批量)
java·linux·运维
拾-光2 小时前
【Git】命令大全:从入门到高手,100 个最常用命令速查(2026 版)
java·大数据·人工智能·git·python·elasticsearch·设计模式
无人不xiao2 小时前
springBoot 实现 接口进度条
java·spring boot·后端
smileNicky2 小时前
Docker 部署 SpringBoot 项目超详细教程
spring boot·docker·容器
pkowner3 小时前
若依分页问题及解决方法
java·前端·算法
珂玥c3 小时前
kubeadm方式部署 k8s 1.21
云原生·容器·kubernetes
largecode3 小时前
如何让电话显示店名?来电显示店铺名称,提升有效接通率
java·开发语言·spring·百度·学习方法·业界资讯·twitter
xuhaoyu_cpp_java3 小时前
SpringMVC学习(五)
java·开发语言·经验分享·笔记·学习·spring