关于内存的 cgroup,它负责管理与统计命名空间(容器)内的内存使用情况。当该 cgroup 中的所有进程退出时,内存 cgroup 会被 Docker 释放。 但是,“内存”不仅是进程的内存,而且虽然进程内存的使用量已经消失,但事实证明,内核还为缓存空间分配了内存,例如 dentries 和 inode(目录和文件元数据),这些内容被缓存到内存 cgroup 中。从这个问题可以看出:
  • 僵尸 cgroup:那些没有进程运行并被删除的 cgroup 仍然持有一定得内存空间
  • 与其在 cgroup 释放的时候遍历所有的缓存页(这也可能很慢),内核会惰性地等待这些内存需要用到的时候再去回收它们,当所有的内存页被清理以后,相应的 cgroup 才会最后被回收。在被回收之前,这些 cgroup 仍然会被计入统计信息中。
  • 从性能的角度来看,它们通过分期回收每个页面来摊销直接整体回收的巨大耗时,选择快速地进行初始的清理,但这种方式会保留一些缓存在内存中。但这也没什么问题,当内核回收缓存中的最后一页内存时,cgroup 最终会被清理,因此这并不是一个“泄漏”。
  • 不幸的是,我们遇到的这个问题在于 memory.stat 执行搜索的方式,比如在我们的服务器内核时 4.4,这个版本的实现是递归的算法进行查询,再加上我们服务器一般都有大量的内存空间,这意味着最后一次内存缓存回收并清理僵尸 cgroup 可能要花很长时间。