这篇文章是我研究容器内存整理出的相关内容. 前后内容并没有上下文关系, 每个知识点都可以单独查看.
内存控制
使用这样的命令启动一个容器docker run -d -m 300M xxx
. 可以限制容器使用的内存最大为300M
. 那么docker
是如何实现容器的内存限制呢?
其实是操作系统已经做好了支持. Linux
中实现容器的两大技术是:
- namespace: 使用不同的命名空间实现资源的隔离. 包括:
- PID: 进程隔离
- Net: 网络环境隔离
- VFS: 文件系统隔离
- IPC: 进程通信隔离
- 等等, 可查看维基百科
- cgroups: 实现对进程资源的限制. 包括: cpu/内存/最大进程数量等等. 详情可查看官方文档
好, namespace
实现了多个容器间不同进程的隔离, cgroups
实现了对单个容器的资源限制. 就是这两个技术支撑了容器化的实现.
要查看一个进程的cgroups
限制, 可查看文件/proc/<pid>/cgroup
. 如果是一个容器进程, 你会看到类似于这样的内容:
...
10:memory:/docker/<docker_id>
9:cpuset:/docker/<docker_id>
8:blkio:/docker/<docker_id>
...
将限制指向了一个路径, 这个路径存放在/sys/fs/cgroup
这里, 比如memory
的限制路径为: /sys/fs/cgroup/memory/docker/<docker_id>
. 在这里能够看到对此进程的所有内存限制. 其中每个文件的含义在官方文档中也有说明.
至于具体的原理, 这里不做深究.
这里额外说一点, /sys/fs/cgroup
是一个树形结构, 子控制组的资源限制必定小于等于父控制组.
OOM Kill
如果一个进程的内存使用超过了限制, 会发生什么呢?
随便写一个脚本实验一下, 就会发现进程突然消失了, 被操作系统杀掉了.
使用docker inspect
命令查看, 发现OOMKilled
的值为true
. 或者直接查看系统日志/sys/log/message
也能够看到进程被杀掉的log.
Page Cache
我们可能会碰到这样奇怪的现象, 容器的内存限制为200M
, 且已经使用200M
内存, 此时再启动一个进程申请20M
内存仍然成功, 且申请后总的内存仍然是200M
.
造成这个奇怪现象的原因, 是因为在调用函数read
读取文件的时候, 会将文件临时存放在内存中, 以加速后续读取. 我们使用free
命令查看时, 其中的buff/cache
就是文件的缓存, RSS
则是实际使用的物理内存, VIRT
则是进程申请的虚拟内存. 某个进程的具体内存分布可查看文件proc/<pid>/smaps
.
容器RSS
和buff/cache
的和, 就是容器实际使用的物理内存总值. 应该与cgroup
路径下的memory.usage_in_bytes
值相同. (容器的内存分布也可以查看文件memory.stat
)
这么一说, 这个奇怪的现象是不是就可以解释了? 当内存不足的时候, 系统会回收文件缓存以供进程使用.
交换内存
如果容器开启了交换内存, 你就会惊奇的发现, memory cgroup
限制失效了. 容器申请了超过限制的内存仍然可以继续执行, 只不过部分内存被交换到磁盘上了.
如果同时又Page Cache
和交换内存, 操作系统优先选择哪一个呢? 可以通过修改memory.swappiness
的值来修改优先级, 其值为0-100
, 值越大, 使用交换内存的概率越大. 当值为100时, 则Page Cache
与叫换内存的概率相同. 官网介绍
不过一般启动容器的时候, 都是将swap
关闭的, 应该没什么需要开启的场景吧.
还有一些内存相关的其他知识点, 大部分都可查看memory group
的官方文档