deepdive:一文分清与K8s打交道的三种account

大家好,我是二哥。

这期来和大家聊一个有意思的话题,大家在平时用K8s的时候,起码会在下面三种场景中碰到三种不同的账号:

  • kubectl连接到K8s需要一个account
  • Pod和api-server打交道需要一个account
  • 既然每个container本质上就是一个进程的话,那进程运行需要一个account

那么问题来了:这3种账号之间有什么区别?

来吧,进入正题。

二哥觉得有必要先把API Server在整个K8s中的地位和大家展示一下。如图1所示,它是绝对的C位和信息中枢。无论是Kubectl命令还是Kubelet这样的组件都需要与api-server打交道。

图 1:API Server在K8s中的位置示意图

K8s将连到API Server的客户端所用的账号分为两类:普通user(normal user)和service account。

每一个连接到API Server的请求都需要通过图2中所示的认证模块。K8s只负责service account,对于普通user,K8s一般是通过插件形式转交给外部服务如Azure AD进行认证。K8s内部甚至没有任何Object可以用来表示普通user,也没有提供任何API来管理普通user。

图 2:与K8s打交道的三种不同账号全景图

上图将不同场景中所用的三种不同账号和Kube-apiserver放在了一起,有对比才有感觉。这三种账号我分别用带圆圈的数字标示出来了,后面分别聊聊它们。

对每一个进来的请求,api-server有一个公共的处理流程:每一个请求都会依次通过包括authentication、audit logging、throttling、authorization等在内的handle chain。一切顺利的话,这个请求最终会在kube-aggregator处被分流进api-server自己实现的resource handler或者客制化的api server(Custom API Server)。

需要强调的是在这个链上,无论是authentication还是authorization都可以有多个plug-in。认证过程中,只要有一个插件(authentication plugin)成功返回了user name、user ID和group的话,认证这一步就算完成了。

1. kubectl 用到的普通user

我们来看看图2中kubectl与api-server的连接部分。这部分画出了一个大大的框,它把 ~/.kube/config这个文件的骨架画出来了。config文件分为三个主要的部分:

  • Clusters:包含可供访问的K8s cluster列表,每个元素包含签署api-server证书的CA.crt以及api-server具体访问地址。
  • Users:一个列表,每个元素是访问api-server时可选的User以及与该User相关的credential信息。
  • Contexts:一个列表,每个元素是一个Cluster加一个User的组合。

我们重点关注Users这部分。虽然前文提到K8s自身不管理普通user,但是它可以通过若干种方式来认识这个user,并接收以这个user的身份发起的请求。下面列举了4种身份生成和出示的方式,其它方式我就不一一列举了。

  • 这个User可以出示一个由K8s签署过的证书。
  • 这个User可以出示一个Bearer token,这个token是某一个service account token(详见第2节)。
  • 这个User可以出示一个Bearer token。当然这个token需要事先通过文件的方式传递给了api server。
  • 这个User可以出示一个Bearer token,且api server可以通过某种方式(如Webhook Token Authentication)到IAM处验证这个token
  • 其它方式

图2这个示例中 ~/.kube/config中所包含的是Bearer token。无论是在运行kubectl的时候即时从Authentication plug-in那里获取token还是事先就先取得token并写入到这个config文件中,在向api-server发起请求时,需要将Bearer token通过HTTP header递交给api server。api-server通过Webhook Token Authentication机制从authentication plug-in处获得确认的答复后就算认识这个用户了。

Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

Plug-in返回给api-server的认证答复里除了有user name、user ID外还包含group这样的重要信息。无论是这里说到的普通user还是下一节将要细聊的service account都可以属于一个或多个group。Group就是一个字符串,内容不限但有几个特殊的内置group的名字还是有必要单独列一下。大家顾名即可思义,我就不多废话了。

  • system:unauthenticated
  • system:authenticated
  • system:serviceaccounts
  • system:serviceaccounts:<namespace>

2. Pod用到的service account

前面说K8s虽然可以针对普通user做认证和授权操作,但它不负责管理。而对于service account(后文简称sa),K8s则上心多了。K8s以sa为核心,绑定了各式各样的功能。例如:

  • RBAC及role binding
  • 各种类型的Secret
  • 与PodSecurityPolicy相关的安全机制

二哥在图2中用圆圈2画出了sa和Secret的关系,好让大家对sa的作用有个直观的印象。

当我们使用命令kubectl describe pod,不出意外的话,会看到如下图所示的信息。默认情况下,红色方框高亮出来的部分,每个容器都会有。也就是说在每个容器内部,都可以访问/var/run/secrets/kubernetes.io/serviceaccount这个目录。

图 3:Pod description输出截图

这个目录里面有啥呢?主要有三个重要的文件:ca.crt,namespace和token。

  • ca.crt:签署api server证书的CA。比如在容器内部使用curl命令来访问api server时,可以通过参数--cacert来指定这个ca.crt,如果你需要验证api server所出示的证书是否有效的话。

    $ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes
    Note: 也可以通过 -k 选项跳过验证api server证书有效性这一步。

  • token:访问api-server时需要出示的token。它是JSON Web Token。api-server会用它来做认证和授权检查。使用示例如下所示: TOKEN=(cat /var/run/secrets/kubernetes.io/serviceaccount/token) export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt curl -H "Authorization: Bearer
  • namespace:可以通过读取这个文件快速地得知当前Pod所在的namespace(这里是指K8s namespace,不是Linux namespace)。如下面的示例中,通过这个方式可以方便地通过shell脚本来获取namespace。这在用脚本来访问某个namespace下面的资源时尤其有用。 NS=(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) curl -H "Authorization: Bearer

那图3里面的这个Secret default-token-kzq65是怎么来的呢?很简单,像下面这样创建一个service account foo后,K8s就会自动给它创建这个secret。

$ kubectl create serviceaccount foo

$ kubectl describe  sa  foo
Name:                             foo
Namespace:                  default
Labels:                            <none>
Image pull secrets:        <none>
Mountable secrets:        foo-token-qzq7j
Tokens:                            foo-token-qzq7j

kubectl describe sa foo返回的结果所示,一个sa可以对Secret进行更多的控制,比如只有出现在Mountable secrets名单中的secret才可以被mount到容器里面去。不过这个已经超出了本文讨论的范围了。

当Pod用这个token去访问api server时,authentication plugin会返回如下格式的信息:

system:serviceaccount:<namespace>:<service account name>

对于我们foo这个示例中的sa和token而言,就是:

system:serviceaccount:default:foo

api server再将其送给authorization plugin,让其决定这个pod相关的sa是否有权限执行它所希望做的操作。

下面的示例代码演示了如何将这个新建的sa foo分配给一个Pod。通过给不同的sa绑定不同的role,从而藉由RBAC来控制Pod可以访问哪些资源。

apiVersion: v1
kind: Pod
metadata:
  name: curl-custom-sa
spec:
  serviceAccountName: foo
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 0

3. container用到的uid

我们再来看图2中圆圈3所表示的第三种account:Pod里面的容器由若干种进程组成,每个进程运行的时候都涉及到一个user id。这个user id既可以在build docker image的时候通过指令USER <UID>[:<GID>]硬编码到image中去,也可以像上面的示例一样,在securityContext.runAsUser来指定,还可以通过PodSecurityPolicy来指定。总有一款适合您。

首先需要将这个地方的user和前面两种做区别。前面所聊的,无论是普通的user还是service account,都与K8s的认证、授权、安全等功能强相关,而这里所说的user其实是Linux OS user management中的概念。

更具体地讲,对于容器而言,这个user涉及到User ID mappings以及user namespace等OS提供的基础性服务,无论是Docker、Podman还是CRI-O都依赖这个服务。

拿图2来举例。当创建容器时指定了securityContext.runAsUser为0,意味着从容器所在的user namespace里看来,该进程的uid为0,也即root。那在运行这个Pod的宿主机看来,这个进程的uid是什么呢?是的,你肯定猜到了,是25,对应的user account是lance。

至于与容器相关的/proc/$$/uid_map这个文件,映射关系该如何填写,感兴趣的同学可以参考https://coolshell.cn/articles/17029.html中的示例代码。

限于篇幅,这里二哥就不详细介绍这个user ID mapping的细节。一句话来概括就是:通过这种mapping机制,可以将容器所在的user namepace里的uid映射到root user namespace中的uid。这是一种障眼法,容器认为自己拥有root身份,其实它只是一个普通账号。

4. 总结

本文二哥带大家看了3种不同的account,它们分别被用在了不同的使用场景中。

kubectl和api-server之间通信所用account为normal user。K8s只负责用,不负责管理。

K8s自身的运行离不开service account。与sa绑定在一起的有K8s资源有Secret、RBAC、PSP等等。

Linux OS user management中的user id以及uid mapping为容器正常运行提供了基础服务。

前两者需要K8s的认证和授权模块介入,而第三者不需要。

以上就是本文的全部内容。谢谢!

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
deepdive:一文分清与K8s打交道的三种account
这期来和大家聊一个有意思的话题,大家在平时用K8s的时候,起码会在下面三种场景中碰到三种不同的账号:
<<上一篇
下一篇>>