最近接到一个需求,用户在进入基于k8s内的容器时能否只以特定的用户进入?
听起来是一个很平常且合理的需求,但细想起来并不简单.
背景信息
k8s: v1.22
CRI: docker v20.10.18
CNI: cilium v1.9(并不涉及)
问题引申
首先,目前的kubectl exec 目前不支持使用参数指定用户进入到容器中, 即无法执行Kubectl exec –username,熟悉docker的人都知道,docker 是支持在exec 使用-u指定用户的,这时抛出第一个问题:
为什么kubectl exec 不支持指定username进入容器?
说实话,作者在这之前还真没有想过这个问题,但在想这个问题的时候,引出了另一个问题,因此上面的问题先放一放,先来回答另一个问题:
当执行kubectl exec时,进入的是哪个用户?
这个问题比较简单,当然进入的是默认的用户,那默认的用户是从何而来? 那自然是Dockerfile中指定的用户,默认情况下即为root用户,如果想进入后不为root怎么办?
在Dockerfile中使用USER
关键字指定某个用户为默认用户,这样当kubectl exec进入到容器后即为该用户而非root,但毕竟dockerfile只build一次,不可能在需要更换用户的时候对镜像进行重新build只为切换用户,这样显然成本较大,而在使用docker时就比较容易,因为本身支持exec -u username,那么只要是容器中存在的用户,都可使用-u进行切换。
因此回到第一个问题:
为什么kubectl exec 不支持指定username进入容器?
作者翻阅了一些官方的issue, 很早就有人提出了相关需求,但官方给的反馈总结起来就是基于安全及整体架构考虑,这个参数涉及到多个k8s组件的修改,同时又存在user mapping问题,相关issue感兴趣的可阅读参考文章的前几个链接
也确实有人提了个MR来支持这个参数,但由于提交的代码与docker CRI绑定较为密切,而kubernetes本身是支持多种CRI的,因此并未合并进主干,直到现在也未支持该参数
需求注意的是, kubectl 目前有–user, –username这两个参数,但都不是实现上述目的的
–user: 指的是kubectl 使用的config文件集群相应的username
–username: 指的是kubectl 与apiServer通信的用户名
综上所述,在不改变现状的情况下,有没有办法让kubectl exec 支持指定username呢?
答案是肯定的,docker exec -u就是好的参考例子
问题拆解
由于kubectl exec最终也是进入到容器中,那自然可以把kubectl exec命令转换成docker exec命令,那如何对应起来呢? 两者的桥梁就是DOCKER CONTAINER ID
简单来说就是以下流程:
通过解析目标pod的yaml内容拿到相关信息,主要是nodeName, podnamespace, containerid, nodeName的作用是: 由于最终要转换成docker 命令,因此需要拿到pod运行在哪台什么主机上, 这样doker命令也必须运行在该主机上
新构建一个pod, pod里运行一个docker client,然后把主机上的docker.sock挂载到pod中(docker in docker),这样再结合CONTAINER ID, 即可实现docker exec -u xxx ${CONTAINER ID}
是不是很简单,更兴奋的是, 已经有人专门写了个kubectl plugin来实现这个功能,exec-as
,也叫kubectl ssh
krew
kubectl有自己的插件体系,叫krew
, 由于篇幅有限,不在这里展开,这里只放krew在线及离线安装方式供大家参考
安装完krew后即可使用Kubectl krew命令了
krew插件在线安装
1 | kubectl krew install exec-as |
1 | ./krew-linux_amd64 install --manifest=krew.yaml --archive=krew-linux_amd64.tar.gz |
exec-as
在exec-as插件安装完之后即可实现以下命令实现指定用户
1 | kubectl exec-as -u xxx podname -- bash |
安装完之后可以看出, exec-as其实就是一个shell脚本,下面的代码是他的核心逻辑
1 | # 省略获取pod相关信息的代码 |
其实就是上述的一二点的实现,是不是很简单,但是这远远不够,再回想一下我们的需求:
kubectl exec命令中能否只以特定的用户进入容器
通过kubectl exec-as -u
是可以指定用户了,但这没办法控制用户指定什么用户,选择权是在用户侧,所以并没有达到限制只特定用户的目的
同时,还有如下的几点缺点:
由于exec-as只是一个shell脚本,如果权限控制不好的话,用户随时可以改脚本的内容,并不安全
上面的代码中可以看到,新创建的pod中使用的是docker exec -u username ${containerID}进入到容器中,多数情况下要进入到容器只是用于debug场景,用完就应该退出,在这种场景下,不能直接做成一个deployment服务,因为它是long run的,会造成docker exec 一直是连着docker server的,也并不安全。
因为要使用到kubectl, 相对于给每个开发同学配置一遍环境,有没有更好、更快的办法?
所以,要想达到目的,只能在exec-as上层再做一层,一是限制用户直接使用exec-as且不能修改exec-as shell脚本,二是用完即可退出docker exec
webkubectl
webkubectl做为一个kubectl web化的开源工具,因为之前体验过,可以解决第3个缺点,先来说一下webkubectl的实现
1 | _______________________________________________________________________ |
也很简单,就是用户通过上传kube config文件或者一个合法的token来连接k8s集群,
webkubectl是支持多用户、多session,它会为每个session开辟独立的内存空间,从而做到访问隔离.
截止目前,多用户这块做的并不完善,只支持用户预设定,新增用户需要重启webkubectl, 也不支持对接LDAP/AD等账号体系.
多session的隔离是通过挂载temp内存文件系统(退出即销毁),结合一些kubens、kubectx等工具实现的.
但也还不够,webkubectl只是一个kubectl web化的工具,需要将它与exec-as整合,
最后我们要实现的目的是:
当用户登录到webkubectl后,通过kubeconfig连接session后直接进入到对应的容器中,不给用户执行exec-as的机会,从而实现文章开头的目的,其中,登录webkubectl的用户即为需要进入到容器中的用户.
那如何告诉kubectl exec-as接受的参数?
很简单,通过webkubectl登录时指定,即:
登录webkubectl的用户即为进入到容器中的用户, 即,docker exec 中-u 的值
其它的两个参数:podname、namespace,这里为了改造最小化,就直接放在了登录webkubectl时的密码中了,通过分隔符来限定.
因为最终变成:
登录webkubectl的用户名密码为: username:password_namespace_podname
在webkubectl的源码中获取到这几个参数,然后直接os.Setenv到session的环境变量(因为session是隔离的,因为不会出现错乱)中,再让执行kubectl exec-as时从session环境变量中获取即可.
这样,就可实现用户以指定用户进入到某个pod的容器中.
还有以下几优化的点:
webkubectl由于采用的内存隔离,因此在当用户退出session后(退出浏览器tab页面),就不会保存任何数据,新创建的pod也会随之删除(看exec-as中的trap调用)
为了能够支持同一个session可以多次连接,对exec-as做了些改造,即,当新创建的pod存在且为Running状态时,就直接使用docker exec 进入,而不是每次都创建新的pod, 这样可以提高效率
另外,还有几个权限问题需要注意一下:
由于webkubectl中使用了unshare、mount等命令,因此当使用容器启动时需要使用privileged启动
由于exec-as中使用了kubectl delete pod,因此上传的kube config文件中对应的用户需要具备对ns pod删除的权限,注意,删除pod的ns并不是用户需要进入的pod所在的ns,而是新创建的pod(也就是由exec-as创建出来的)所在的ns的删除权限,需要进入的pod所在的ns其实只需要get/list pod的权限即可.
改造成本不高,目的基本达成
等等,为什么要说基本达成呢? 其实在易用上进行优化的空间:
改造webkubectl的用户认证模块,支持从CMDB中动态地获取用户,这样在有新用户添加时不需要重启webkubectl
username:password_namespace_podname这样糅合在一起用还是挺low的,如果有一个CMDB来记录用户与要访问pod的对应关系,用户对自己具备的pod可随时增删调整就更友好了
成果图
由于时间有限, 除了易用上可优化的点外,上述提到的点作者都已改造完成,贴几张图
登录
创建session, 上传kube config文件
使用webkubectl登录用户连接对应的pod
进入到pod中
当然,由于时间有限, 对webkubectl、exec-as都做了些改造,目前凑合使用
作者深知,这么个工具要能在生产上使用,简直是简陋至极,最好的解决方案肯定是kubectl exec官方来支持
最后,发现一个更简单的工具, 通过改写kubectl exec – su - username来实现,不过有个限制是,容器中必须存在su命令,而且不支持自定义command,嗯,是个办法,感兴趣的可看看这个go_pod
请叫我工具组合侠…