Z.S.K.'s Records

kubectl exec 如何实现进入容器时指定用户

最近接到一个需求,用户在进入基于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

简单来说就是以下流程:

  1. 通过解析目标pod的yaml内容拿到相关信息,主要是nodeName, podnamespace, containerid, nodeName的作用是: 由于最终要转换成docker 命令,因此需要拿到pod运行在哪台什么主机上, 这样doker命令也必须运行在该主机上

  2. 新构建一个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在线安装

安装完krew后即可使用Kubectl krew命令了

krew插件在线安装

1
kubectl krew install exec-as

krew插件离线安装

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 省略获取pod相关信息的代码
if [[ ${CONTAINER} != "NONE" ]]; then
DOCKER_CONTAINERID=$( eval "$KUBECTL" get pod "${POD}" -o go-template="'{{ range .status.containerStatuses }}{{ if eq .name \"${CONTAINER}\" }}{{ .containerID }}{{ end }}{{ end }}'" )
else
DOCKER_CONTAINERID=$( $KUBECTL get pod "${POD}" -o go-template='{{ (index .status.containerStatuses 0).containerID }}' )
fi
CONTAINERID=${DOCKER_CONTAINERID#*//}

read -r -d '' OVERRIDES <<EOF || :
{
"apiVersion": "v1",
"metadata": {
"annotations": {
"sidecar.istio.io/inject" : "false"
}
},
"spec": {
"securityContext": { "runAsUser": 0 },
"containers": [
{
"image": "docker",
"name": "'$container'",
"stdin": true,
"stdinOnce": true,
"tty": true,
"restartPolicy": "Never",
"args": [
"exec",
"-it",
"-u",
"${USERNAME}",
"${CONTAINERID}",
$(to_json "${COMMAND}")
],
"volumeMounts": [
{
"mountPath": "/var/run/docker.sock",
"name": "docker"
}
]
}
],
$NODESELECTOR
$TOLERATIONS
"volumes": [
{
"name": "docker",
"hostPath": {
"path": "/var/run/docker.sock"
}
}
]
}
}
EOF
trap '$KUBECTL delete pod $container >/dev/null 2>&1 &' 0 1 2 3 15
eval "$KUBECTL" run -it --restart=Never --image=docker --overrides="'${OVERRIDES}'" "$container"

其实就是上述的一二点的实现,是不是很简单,但是这远远不够,再回想一下我们的需求:

kubectl exec命令中能否只以特定的用户进入容器

通过kubectl exec-as -u 是可以指定用户了,但这没办法控制用户指定什么用户,选择权是在用户侧,所以并没有达到限制特定用户的目的

同时,还有如下的几点缺点:

  1. 由于exec-as只是一个shell脚本,如果权限控制不好的话,用户随时可以改脚本的内容,并不安全

  2. 上面的代码中可以看到,新创建的pod中使用的是docker exec -u username ${containerID}进入到容器中,多数情况下要进入到容器只是用于debug场景,用完就应该退出,在这种场景下,不能直接做成一个deployment服务,因为它是long run的,会造成docker exec 一直是连着docker server的,也并不安全。

  3. 因为要使用到kubectl, 相对于给每个开发同学配置一遍环境,有没有更好、更快的办法?

所以,要想达到目的,只能在exec-as上层再做一层,一是限制用户直接使用exec-as且不能修改exec-as shell脚本,二是用完即可退出docker exec

webkubectl

webkubectl做为一个kubectl web化的开源工具,因为之前体验过,可以解决第3个缺点,先来说一下webkubectl的实现

1
2
3
4
5
6
7
8
9
10
11
_______________________________________________________________________
| Local Network | DMZ | VPC/Datacenter |
| | | |
| | _______________ | ---------------- |
| --------------- | | | /~~~~~>| Kubernetes A | |
| | Your Laptop |~~~~~~~>| Web Kubectl | / | ---------------- |
| --------------- | | | \ | |
| | --------------- \ | ---------------- |
| | \~~~~>| Kubernetes B | |
| | | ---------------- |
-----------------------------------------------------------------------

也很简单,就是用户通过上传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登录时指定,即:

  1. 登录webkubectl的用户即为进入到容器中的用户, 即,docker exec 中-u 的值

  2. 其它的两个参数: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

请叫我工具组合侠…

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?