众所周知,Docker相比于虚拟机,在隔离性上略显不足,而这个不足的存在使得其在安全等方面可能存在着一些隐患(当然了,蜂巢采用在虚拟机中运行容器等的方法,使得运行在上面的业务绝对安全)。但今天的重点并不是要对这方面的问题进行探讨,本文主要是说明和解决因 Docker隔离性不足所引入的资源显示问题。
这是什么意思呢,请看以下示例(文中示例均基于 Docker 1.12.0):
由上面的示例我们可能会联想到这样的场景:如果我们在一台物理机上为不同用户提供了不同规格的容器作为开发机使用,但是每个用户进入容器却能看到完整的物理机资源,一方面这会让用户很困惑,另一方面又泄露了物理机信息,这对用户来说显得很不友好。
那么为什么会出现这种情况呢?
问题原因
虽然 Docker原生的资源查询接口可以正确地识别分配的资源,但是用户常用的 top、free等命令却未能正确地识别我们施加于 Docker的资源限制,那么原因究竟是怎样。
事实上,类似 top、free等命令,其实多半都是从一些系统文件中获取资源信息,如:
而 Docker的隔离性不足的问题里,就包括跟宿主机共享 sys、proc等系统文件,因此如果在容器中使用依赖这些文件的命令,如 uptime等,实际上都显示的是宿主机信息。
解决方法
容器的显示问题,在很早期的版本中就有人提出过,本人关注这个问题是从 1.7.1版本开始的。而 Docker官方可能是出于某些原因的考虑,并没有试图修复这些显示问题。目前来说,解决显示问题还没办法很好地在 Docker中进行集成,仍然需要在 Docker之外做一些修改。
依赖项目简介
本文的解决方案主要依赖于 lxcfs项目(https://github.com/lxc/lxcfs.git ),该项目提供以下 feature:
○ 为非特权容器提供 cgroupfs兼容的视角
○ 一系列 cgroup可感知的文件
cpuinfo
meminfo
stat
uptime
直白点说,实际上就是可以在容器里提供一个虚拟的 proc文件系统。
基本原理
lxcfs实际上是一个 FUSE用户态文件系统。先简单介绍下 FUSE的工作原理:
如上图所示,假设基于 FUSE的用户态文件系统 hello挂载在 /tmp/fuse目录下,当应用访问 /tmp/fuse下的文件时,通过 glibc中的函数进行系统调用,处理系统调用的 VFS函数会调用 FUSE在内核中的文件系统,内核中的 FUSE文件系统将用户请求转发给用户态文件系统 hello,用户态系统收到请求后,进行处理,将结果通过内核中的 FUSE文件系统返回给应用。
Docker应用 lxcfs
将 lxcfs应用于 Docker解决显示问题,也是需要先将 lxcfs模拟文件 mount到容器对应目录下,如:-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw,此时如果用户在容器内读取 /proc/cpuinfo信息,实际上就读取到了 /var/lib/lxcfs/proc/cpuinfo文件,fuse文件系统将读取 cpuinfo的进程 pid传给 lxcfs,lxcfs通过该 pid找到所属的 cgroup分组,并读取该分组中的 /cgroup/cpu信息并返回。
效果演示
下载 lxcfs项目并编译,然后启动 lxcfs:./lxcfs -s -f -o allow_other /var/lib/lxcfs/,现在挂载lxcfs到容器并启动:
在容器中运行相关命令查看显示效果:
其他问题
其他挂载项
上面的演示只加载了 cpuinfo和 meminfo,实际可加载的还包括:swaps、uptime等,但是如果直接像上面一样加载,会报错:cannot be mounted because it is located inside “/proc”,因为 Docker不允许在 proc目录下随意挂文件,为了规避这个问题,需要修改 Docker依赖项目 runc的代码:
在 proc可挂载白名单下加上相关项即可。
具体问题具体分析
从上面的演示可以看出,lxcfs确实很大程度上解决了显示问题,但是并不是完全解决,本人就曾遇到过不同容器中,top命令算出的 idle值有时对,有时错的情况,原因就是并非所有的 top都是从 proc文件中读取数据并计算显示,也有的是直接取得 cpu ticker和 cpu时钟来计算的。
(注:本文的写作对蘑菇街技术博客: http://mogu.io/docker-with-lxcfs-130 亦有参考)