k8s中使用s3fs和GPU冲突的解决方案

问题介绍

前段时间项目中碰到一个比较棘手的问题,同事在k8s平台上使用GPU做选练,训练数据存放在ceph对象存储中,为了方便我们使用了s3fs把对象存储挂载到容器文件系统中,这样训练代码就可以像访问本地文件系统一样操作了。但是问题是训练有时可以正常进行,重起pod后训练有时会失败,报GPU访问错误。通过这篇文章分享下问题的解决思路,希望对其他朋友有帮助。

原因分析

简单分析发现创建k8s任务时只申请了一个GPU,但从tensorflow错误日志看到应用启动时检测到了4个GPU,进一步分析原因是因为挂载s3fs时设置了privileged=true权限,导致容器中可以访问整个宿主机上的GPU,如果这些GPU都空闲着,训练不会出错,但是如果这中间某个GPU已经被k8s分配给了其他的pod,就会导致一些冲突。

这个问题有一些临时的变通方法:

  • 把对象存储里的东西挪到cephfs里,但是数据量太大,而且失去了存储层面进行用户隔离的功能。
  • 容器里直接调用S3 api访问对象,但这样对用户不友好,没有直接访问文件方便

有没有更好的办法?

解决方案

fuse简单介绍

fuse通过在user space运行一个daemon代理通过/dev/fuse这个虚拟设备和kernel打交道,从而使多种用户态文件系统在不改变kernel的前提下成为可能,比如sshfs, 或者本文中碰到的s3fs, /dev/fuse是在加载fuse内核模块后自动生成的。

容器中使用fuse的正确方法

如果要在容器中挂在sshfs或者s3fs这样的文件系统,必须能访问主机的/dev/fuse设备,而且要能使用mount所涉及到的syscall。

一个比较简单的做法就是给容器privileged权限,但这样的做法有很多弊端:

  • 违背了容器的安全隔离性原则,相当于容器可以访问主机内核的所有功能,容器中运行的恶意代码可能导致整个主机出问题
  • 容器可以看到主机上的所有设备,比如本文所涉及的GPU导致tensorflow这样的计算框架出错

docker借助于内核内置的安全方案可以做到更细粒度的控制,比如apparmor, selinux, seccomp, linux capability 具体要看主机系统是哪个linux发行版,以及kernel配置中是否启用了对应的功能(grep -i armor /boot/config-'uname -r'), 具体可以查看相应的功能介绍,这里不详细展开。 通过这些kernel自带的功能可以做到非privileged访问/dev/fuse设备:

  • ubuntu运行如下命令
docker run -it --rm --device /dev/fuse --security-opt apparmor:unconfined --cap-add SYS_ADMIN image-registry:5000/ubuntu:16.04-sshfs /bin/bash

通过aa-status可以看到系统中定义的profile,以及profile是enforce还是complain模式, profile中定义可以使用哪些功能,比如网络访问,capability,mount,对文件的读写访问权限。
docker daemon启动时会创建缺省的docker-default profile,如果不特别指定会使用这个profile,使用apparmor:unconfined表示禁用该限制功能。

  • RHEL, CentOS运行如下命令
docker run -it --rm --device /dev/fuse --security-opt seccomp:unconfined --cap-add SYS_ADMIN image-registry:5000/ubuntu:16.04-sshfs /bin/bash

注意rhel上可能还会用到selinux做一些类似的设置。--device /dev/fuse就是说把host上的/dev/fuse设备挂载到容器中,--cap-add SYS_ADMIN允许容器中运行的进程执行系统管理任务,如挂载/卸载文件系统,设置磁盘配额,开/关交换设备和文件等。

在k8s容器中使用fuse设备

通过前面的介绍我们大致已经能想到问题的解决思路:

  • 把/dev/fuse设备注入到容器
  • 给容器分配尽可能少的权限,比如可以执行mount命令挂载文件系统,避免使用privileged访问到所有的GPU

但是因为k8s做了一层转化,事情没有想象的直接,下面分别作介绍

/dev/fuse注入到容器

docker提供了--device选项做到这个功能,但是查遍了k8s的文档,在网上也搜了一圈没有发现,这里还要提一下我们之前的一个错误做法:

    volumeMounts:
    - mountPath: /dev/fuse
      name: fuse
 ...
  volumes:
  - hostPath:
      path: /dev/fuse
      type: File | CharDevice
    name: fuse

其实这样是不生效的,真正在后台起作用的是设置的privileged.

后来联想到GPU是如何借助device-plugin功能不需要超级权限挂载到容器中的k8s中的,虽然GPU是通过设置环境变量NVIDIA_VISIBLE_DEVICES,然后nvidia-docker做了些特殊处理,和我们的情况不完全一样,但是从k8s代码看接口中确实提供了另一个选项可以直接设置pluginapi.DeviceRuntimeSpec结构的Device字段,抱着试试的想法,大功告成。

具体做法就是开发一个device plugin并以daemonset的形式部署在每个节点上,plugin会向kubelet注册所提供的资源类型,这里的资源定义为github.com/fuse

seq.JPG

实现代码已上传到https://github.com/JasonChenY/fuse-device-plugin

在容器中以普通权限作挂载文件系统

ubuntu

从k8s官方文档可以看到通过注解annotations: container.apparmor.security.beta.kubernetes.io可以控制使用哪个apparmor profile, 但是模仿docker尝试设置unconfined时报invalid,阅读K8s代码发现只支持localhost/xxxruntime/default这两种语法,不支持unconfined。其实runtime/default在启用apparmor时就对应docker的docker-default, docker-default这个profile的设置中有一条是不允许在容器中进行mount操作, 知道这个后问题就迎刃而解了,在docker-default的基础上创建一个新的profile

cat /etc/apparmor.d/docker | sed 's/deny mount/# deny mount/' | sed 's/profile docker-default/profile docker-fuse-2/' > /etc/apparmor.d/docker-fuse
apparmor_parser -a /etc/apparmor.d/docker-fuse

然后创建k8s对象时就设置

container.apparmor.security.beta.kubernetes.io/<container name>: localhost/docker-fuse

结合前面已经挂载的fuse设备就可以挂载文件系统了

mkdir /mnt/test
sshfs -o allow_other root@10.0.0.62:/root /mnt/test

RHEL, CentOS

SeLinux基于用户-角色-类型这个模型来运作的,类型系统是它的核心,可以做到非常细粒度的安全控制,f但是配置偏复杂,弄得不好会把IT自己搞死,所以一般都处于关闭状态,除非核心的关键服务器,这里不展开。

seccomp通过配置文件来限定可以使用哪些系统调用,docker也有个默认的docker/default,禁用了300多个系统调用里的40多个,确保容器里运行的进程不会对宿主系统产生安全威胁。

首先可以确认系统是否支持该功能

$ grep CONFIG_SECCOMP= /boot/config-$(uname -r)
CONFIG_SECCOMP=y
  • 如果不支持,容器就可以随心所欲了?记住文章开始提到的capability,命名空间的隔离, docker缺省限制很多capability, 比如使用raw socket, 加载内核模块,重起机器等等, 处处把关 。

  • 如果支持,那还要看k8s的行为,到目前为止(1.12版本),seccomp还处于alpha状态,缺省是用unconfined配置, 也就是说不对k8s容器启用任何seccomp配置,等同于上面不支持的效果。

  • 如果想通过seccomp做一些定制的细粒度控制,那要启用PodSecurityPolicy admissionController并设置seccomp.security.alpha.kubernetes.io/allowedProfileNames, 官方文档里说支持docker/default, unconfined, localhost/<path>, 或者*这些格式。 其中path是kubelet的启动参数seccomp-profile-root指定的目录下的文件名,在对应的文件里设置想限制容器做哪些系统调用。
    最后在yaml文件里设置对应的profile

container.seccomp.security.beta.kubernetes.io/<container name>: localhost/<path>

我们的平台属于第二种情况用unconfined, seccomp没生效,所以直接使用fuse-device-plugin就成功了,也没有对第三点作实际验证,若有错误勿拍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容