前言

一直以来有个想法,想要将大象(OpenStack)塞进冰箱(Docker),像 OpenStack 这样的庞然大物,每次部署都会浪费至少大半个小时。

当然,这个想法在一年前就被本人验证可行了,但是仍存在一些不完善的地方......

特权模式的KVM虚拟化

特权模式,即使用--privileged选项创建容器,在这种场景下,会将宿主机中的设备目录(/dev),毫无保留的暴露到容器中,并且允许使用所有内核功能(CAP)。

但这种方式存在一个问题,当宿主机 /dev 目录被挂载到容器后,若使用 systemd 进行初始化(init),便会导致宿主机的终端不可用,宿主机所使用的 tty,会被容器中的 getty 占用;导致线下维护极大的不便。

后记:宿主机的终端被占用是由于本人显式挂载了宿主机的 /dev 目录,正常使用特权模式不会引发终端占用问题;但容器内的 /dev 目录是不完全跟随宿主机更新的。

不过,虚拟化相关的功能一切正常。

非特权模式的KVM虚拟化

当不使用--privileged选项创建容器时,需要确保以下两点:

  • /dev/kvm 设备文件能够被正确访问(使用KVM加速必须
  • /dev/net/tun 设备文件能够被正确访问(根据是否需要创建网络接口决定

其中,/dev/kvm 尤其需要注意,要从宿主机暴露至容器中,需要使用 --device 选项来完成主机设备的暴露。

如果使用 --mount type=bind,或是 -v(等价于mount type=volume + mkdir -p),会导致出现以下结果:

Could not access KVM kernel module: Permission denied

经过一番搜索,本以为是容器内的用户 gid 与宿主机不一致引发的问题,但经过测试后发现并非如此,由于容器内使用的用户是root,且 uid/gid 均为 0,不受 gid 权限问题的影响。

  • 后来据友人 xxxuuu 所言:
--devices会在cgroup上给设备加白名单 而-v不会
所以这种要通过ioctl控制的东西 都要用--devices
不然你可以在容器里看到 挂上的东西不会被标识为设备,只会是一个普通的文件
  • 本人才意识到问题所在:是缺少关于 cgroup 的权限。

尽管 mount bind 方式在容器中创建的设备文件,与宿主机中的设备文件别无二致,但如果缺少了关于 cgroup 的权限,还是会出现 Permission denied的情况。

后来查阅了 docker run使用文档 发现,是有单独针对设备文件进行分配、控制 cgroup 规则的选项。

最后发现这一切问题的源头,竟是源自于对 mount bind 的想当然,真可恨。

尽管如此,但这里面还是有一个例外影响了我的判断,/dev/net/tun 设备,似乎可以在 mount bind 的方式下正常使用;

补充:/dev/net/tun 设备是否可用,或许是由 NET_ADMIN 的内核功能(CAP)所决定;还有一种可能是程序仅对设备文件是否存在进行了判断,实际上设备也是不可用的。

但是不管是什么原因,以后只要使用 --device 的方式来分配设备就好了。

非特权模式下容器内的内核参数调教

经过了前文提及的一个案例,我开始思考 --privileged 使用与否差异是什么。

据我所知,--privileged 似乎是组合了 --cap-add allmount bind /dev,且允许对 sysfs 进行读写。

当我试图仅使用 --cap-add all 创建非特权容器时,会发现容器无法对内核参数进行调教;这是一个十分关键的问题,在这里我发现容器内的设备命名空间与宿主机中的设备命名空间是不一致的。

简单说就是:容器内创建的 TUN 设备,无法在宿主机下的 /sys 目录中发现。

也就是容器内的设备被隔离了,并且 容器内无法修改设备运行时的参数,因为容器中的 /sysRead Only 的。

宿主机无法访问容器内的设备,容器中无法修改 /sys 下的内容,很好,似乎路被堵死了。

如何解决呢?方法却很简单,在容器中重新挂载 sysfs 即可。

mount -o remount,rw /sys