K8s资源管理的权限控制
in DevOps with 0 comment

K8s资源管理的权限控制

in DevOps with 0 comment

简介

Kube APIServer 是 Kubernetes 系统提供对外和对内接口访问的服务,所以后面的实验基本上就是和这个服务打交道了。在和这个服务打交道之前,我们已经了解到 Kube APIServer 提供的是基于 HTTP 协议的接口。所以可以使用一些常规支持 HTTP 协议的命令如 curl 来完成一些简单的演示工作。

API 初试

在深入研究进行资源管理的 API 之前,先看一个简单的 API 访问,这个 API 就是 Kubernetes 提供的证明自己服务是否存活的接口。可以使用 curl 命令来访问下这个接口,看看它的输出,感受下这个交互的方式。

首先执行 kubectl cluster-info 查看集群 API 的地址。

image-1655176787770

然后在终端执行如下命令,注意替换成你的 API 地址:

curl https://127.0.0.1:32771/healthz -k

其中 -k 选项的意思是忽略验证 HTTPS 证书,因为这个证书是我们自签的,所以无法通过标准的 HTTPS 证书验证流程。这里我们只对输出感兴趣。这个命令如预期成功返回服务的健康是 OK 的。但是如果我们尝试访问另外一个接口,则会遇到一些错误。

curl https://127.0.0.1:32771/healthz/ping -k

image-1655176800690

从上面的错误信息和错误码显示,我们没有访问这个接口的权限。在 Kubernetes 中,对除 healthz 和 version 之外的访问都需要验证访问的账户是否有权限。

image-1655176809080

本节的课程就是为大家讲解 Kubernetes 的 API 访问权限问题,在本实验结束之后,你就可以完美解决这个问题了。

Kubernetes 账号系统

在讲解 Kubernetes 支持的账号类型之前,仔细想一想我们遇到过哪些类型的账号。

其中最常见的当然是用户名密码的方式了;其次在远程登录服务器的时候还会用到 SSH 的私钥和公钥;另外如果使用 Github 或者 Gitlab 来进行推送代码,还会遇到 AccessToken,就是一串长长的字符串等等。

在现实生活中已有不同的系统采用许多不同类型的账号,为了能够支持这些现有系统中存在的账号体系,Kubernetes 也是设计了各种方案。不过限于篇幅,我们在本节课程只关注下 Kubernetes 中提供的账户管理功能及其特点。

Kubernetes ServiceAccount

在 Kubernetes 中,由系统自身的接口来创建和管理的账号类型只有一种,叫做 ServiceAccount。可以使用下面的命令来查看目前系统已有的 ServiceAccount:

kubectl get serviceaccount --all-namespaces

image-1655176821646

由于 serviceaccount 这个单词比较长,所以 Kubernetes 也支持直接用下面的缩写形式。

kubectl get sa

虽然 Kubernetes 通过各种方式支持不同类型的账户,但是我们目前主要关注的还是 Kubernetes 内置的 ServiceAccount,所以在后面讲解的也是 ServiceAccount。

Kubernetes 访问控制模型

本节主要介绍 熟悉 ServiceAccount、创建自己的 ServiceAccount、Kubernetes 的访问控制模型、ServiceAccount 访问资源方法以及 ServiceAccount 访问资源授权。

熟悉 ServiceAccount

Kubernetes 自身支持的账号类型叫做 ServiceAccount,它也属于 Kubernetes 的一种资源。

我们上面看到过系统里面已经存在的 ServiceAccount,可以使用 get 命令来看下它的具体定义:

kubectl get sa default -o yaml

image-1655176832609

这个叫做 default 的 ServiceAccount 引用了一些 Secrets,也就是具体的鉴权信息保存对象。我们也可以顺便看一下:

# 这里的 default-token-pkdtj 是根据上面的输出获取的
kubectl get secret default-token-pkdtj -o yaml

image-1655176843324

仔细观察下这个 Secret 的定义会发现核心内容有 ca.crt、namespace、以及 token。其中:

这个 token 在上面的输出中其实是原始的凭证做了 base64 编码后的结果。我们这里不用手动去解码,可以用下面的命令来查看原始的 token:

kubectl describe secret default-token-pkdtj

image-1655176853116

在 Kubernetes 中,可以使用 describe 命令来查看任意资源的详细信息,这些信息以用户友好的方式进行输出,而不是直接输出资源的原始定义。从上面的 ServiceAccount 对应的 Secret 的输出中,可以看到对于一个 ServiceAccount 来讲,该账户对应的鉴权信息主要由 ca.crt 即服务端证书,namespace 即资源所在命名空间以及鉴权用的 Bearer Token 构成。

创建自己的 ServiceAccount

我们上面已经熟悉了系统中已有的 ServiceAccount,现在来自己创建一个 ServiceAccount 解决开篇提到的问题:

kubectl create sa shiyanlou-admin

image-1655176861469

好了,我们已经创建好一个 ServiceAccount 了。接下来使用同样的方式找到这个 ServiceAccount 对应的鉴权 Token:

kubectl get sa shiyanlou-admin -o yaml

image-1655176868400

从这个 ServiceAccount 里面找到对应的 Secret,然后根据这个名称查看 Secret 的详细信息:

kubectl describe secret shiyanlou-admin-token-qr9bt

image-1655176878004

到目前为止我们已经创建了一个新的 ServiceAccount,并且查找到了该 ServiceAccount 相关的鉴权信息,那么是否此时就可以访问到开篇提到的那个接口了呢?我们可以尝试下:

每个 ServiceAccount 的 token 都是不同的,文档里面的是示范,具体的 token 需要从上一个命令中获取,也可以记录一下,后面也会使用到。

curl https://127.0.0.1:32771/healthz/ping -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IlhsY215Zi1IQ1hCQXB3ZzBtMTlkb2ttaVBTcjFheHkwejFqY1R6b0pia1UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNoaXlhbmxvdS1hZG1pbi10b2tlbi1xcjlidCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJzaGl5YW5sb3UtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiIwY2E5NjdmNy03YTkwLTQ4ZGItYmRjMS04YTM3ZjFmYjgxYmEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpzaGl5YW5sb3UtYWRtaW4ifQ.cVt7D1HbCK-SlfFBX1GTol9tpW5cgoOWK5KIHColfTgLsf7LI5O7Jxg8mNyc8f8yZysQcTqu2H3qVSiLdWX0HLTiSsjWNx4S_YQkjZAjD3nSSjAMQ3SszlNuOBWC5PaeGbIYsGmQ7mYSYjp7qo93N4UjyHYLvs4Dl17ZrHahDpU-dArtegItD9aPFAZ5tkByWUMIFh3m1-2Wsud2nrw8S6wyjKvK86mv6S1CF19kDqdqkoJgMEr-HhZoZUBw3WMFdg-XymyqhtaR3IRfPMh3UqGgGDI3T4f0CSAZ5mNkV-91L9X-6AD0F30-UXcs-XHODn0_J-MzgCxUNuPidIPrqQ'

注意,在上面的命令中我们把该 ServiceAccount 对应的 Secret 的 Token 放入到请求的 Authorization 头部进行鉴权,但是结果并不是我们期望的那样:

image-1655176887219

这里的错误信息和之前的错误信息很明显发现了变化,但是仍然无法获取到正确的结果。这个问题的原因在于一个新的 ServiceAccount 创建完成之后,它是没有任何权限的,我们必须给它授予一些资源访问的权限,然后它才能去访问这些资源。

在我们继续解决这个问题之前,让我们先学习下 Kubernetes 中访问一个资源过程中都需要哪些信息。

Kubernetes 的访问控制模型

在了解和熟悉了 ServiceAccount 的简单使用之后,就可以深入学习下 Kubernetes 的访问控制模型了。

在接下来的讲解中,我们会区分使用鉴权和授权这两个概念。即使从字面来看,这两个词的意思也是有区别的,而在 Kubernetes 里面也正是如此。

在 Kubernetes 中鉴权表示验证用来访问接口的用户名密码或者 Token 是否合法,而授权则是在鉴权完成之后,检查这些进行鉴权的用户是否拥有操作 Kubernetes 系统中的资源的权限。这个操作的权限是细分的,主要分为 create、update、delete、patch、get、list、watch。前面四个对应了写对象的权限,后面三个对应了读对象的权限。

我们在第一节课程中提到过的那些 Pod、Service、Secret、Deployment、Ingress 等等资源都是需要授予账户访问或操作权限之后才能去访问或者操作资源的。

image-1655176901084

Kubernetes 系统中的资源是分为两种的。

第一种叫做命名空间下的资源,另外一种当然就是不在命名空间下的资源。命名空间在第一个实验中已经介绍过了,主要是用来对相关的资源提供一个组管理来隔离不同的用户群体。

那么这里还有一些不在命名空间下的资源,当然就是和 Kubernetes 系统本身相关的资源,它们不隶属于某个 Kubernetes 命名空间,而是属于系统层面。比如关于节点的访问或者操作就不属于任何的命名空间,这个是系统层面的操作。

ServiceAccount 访问资源方法

我们在上面既介绍了 Kubernetes 的资源,也简单介绍了一下 ServiceAccount,并且了解了 Kubernetes 资源的访问控制模型,顺便还创建了一个 ServiceAccount,那么现在就轮到了新的问题,如何使用 ServiceAccount 去通过 API 访问这些资源。我们刚刚直接创建的 ServiceAccount 对象是否直接拥有权限访问 Kubernetes 的各种资源呢?

到目前为止我们只使用过 healthz 的接口查看系统是否正常,但是并没有访问 Kubernetes 中资源的经验。另外由于实验一和实验二主要是帮助大家了解 Kubernetes 相关的原理,所以还没有开始编写相关的代码,为了能够帮助大家深刻了解 ServiceAccount 和 Kubernetes 权限控制之间的关系,我们将继续使用 kubectl 这个工具来尝试操作 Kubernetes 的资源。我们在第一个实验介绍过,这个 kubectl 工具是 Kubernetes 自身提供的和 Kubernetes 系统进行交互的工具,内部实现也是通过调用资源管理的 API 来进行的,所以可以认为和我们即将开发的工具功能是一致的。

在上面的 kubectl 工具的使用中,我们都是在实验环境里面直接运行这个命令的。在实验环境中的机器上面的 kubectl 采用的是访问 Kubernetes 集群中的无鉴权接口,具体的配置信息位于 /home/shiyanlou/.kube/config 文件中。我们目前的目标是验证 Kube APIServer 的访问控制权限。

因为我们通过下面命令设置 kubectl 的 ServiceAccount 的话会覆盖 /home/shiyanlou/.kube/config 这个文件。所以当我们创建新的 ServiceAccount 之后如果要再使用 kubectl 来验证,需要先把这个 /home/shiyanlou/.kube/config 备份一下,然后给 kubectl 配置我们自己创建的 ServiceAccount 权限。如果需要你随时可以把这个 config 文件的内容恢复回去。

cp /home/shiyanlou/.kube/config /home/shiyanlou/.kube/config.bak

备份完这个文件之后,可以按照下面的方式来设置 kubectl 使用我们刚刚创建的 ServiceAccount 去访问 Kubernetes。

第一步:设置一个账号鉴权信息

kubectl config set-credentials shiyanlou-admin --token <TokenOfSecret>

image-1655176913196

上面的 <TokenOfSecret> 的内容就是我们上面通过 kubectl describe secret 获取到的 Token 值。由于 Token 比较长,为了不影响大家阅读,所以在实验的时候请自行复制粘贴在命令中。注意指定的值两边没有 <> 符号,它们只是用来表示一个变量。获取 Secret 的 Token 值的命令如下:

最后的 secrets name 需要根据自己的环境进行修改

kubectl describe secret shiyanlou-admin-token-zd445

第二步:设置集群的访问信息

所谓集群的访问信息就是指 API 服务的地址和服务端的证书,这个证书的内容会提供给大家,自己保存到任何路径都可以,这里演示我们放在了 /home/shiyanlou 目录下面,名字叫 ca.crt

使用下面的命令将 Kubernetes 服务端证书保存到 /home/shiyanlou/ca.crt 文件中。

docker exec kubernetes-control-plane cat /etc/kubernetes/pki/ca.crt | tee /home/shiyanlou/ca.crt

image-1655176922859

注意替换 API Server 的地址。

kubectl config set-cluster k8s-learning --server https://127.0.0.1:37283 --certificate-authority /home/shiyanlou/ca.crt --embed-certs=true

image-1655176930517

第三步:创建一个 Context,把集群信息和鉴权信息绑定在一起

kubectl config set-context k8s-learning-ctx --cluster k8s-learning --user shiyanlou-admin

image-1655176939035

第四步:使用这个创建的 Context

kubectl config use-context k8s-learning-ctx

image-1655176947287

通过以上步骤的设置,我们就可以在本地通过 kubectl 使用我们创建的 ServiceAccount 来调用 API 接口,访问集群中资源的信息了。

好了,让我们来简单查看下默认命名空间 default 中的 Pod 列表:

kubectl get pods

image-1655176955532

咦,好像出错了啊?仔细查看下出错的信息,我们会发现,Kubernetes 系统认为我们的 ServiceAccount 没有权限列举命名空间 default 中的 Pod。

让我们回想上面介绍过的 Kubernetes 的资源访问权限模型,我们创建的 ServiceAccount 好像仅仅拥有了合法的鉴权信息来调用 API,但是还有一步资源访问的授权还没有做,所以这个时候如果通过 kubectl 去调用 API 的话就会返回上面的错误信息。

这里你或许有一点点好奇心,如果这个 ServiceAccount 连鉴权信息都没有,那会返回什么错误呢?其实这里有个很简单的办法验证一下,我们上面通过 kubectl 设置的 ServiceAccount 的信息其实保存在本地文件 /home/shiyanlou/.kube/config 中。我们可以简单修改下这个文件的内容,把这个文件内容中的 Token 后面加上一个字符,相当于让 Token 失效,然后保存。再看看调用上面的 kubectl 命令会返回什么错误信息:

kubectl get pods

image-1655176962971

如果你自己动手做了下,就会发现返回的错误完全不同,这次是告诉我们要登录系统,说明我们提供的鉴权信息是错的,第一步鉴权就没有过。最后记住把你刚刚加的字符删除,我们要继续探索 ServiceAccount 访问资源的授权问题啦。

在上面的操作完成之后,让我们切回到 kubectl 默认的无鉴权的访问模式下,我们需要做一些操作。

# 查看有哪些 context
kubectl config get-context
# 切换默认 context
kubectl config use-context kind-kubernetes

image-1655176971505

ServiceAccount 访问资源的授权

在这里将给大家详细介绍如何给 ServiceAccount 授权,才能够去访问系统的各种资源。

在上面的讲解中,我们已经知道了 Kubernetes 的资源主要分为两类。其中一类是系统层面的资源,另外一类是命名空间下的资源。

在 Kubernetes 中,对 ServiceAccount 的授权是通过创建各种拥有操作资源权限的角色来进行的。因为资源分为两大类,所以角色也分为两大类。其中一类角色叫做 ClusterRole,从字面意思我们就可以理解这是操作系统层面的资源的权限;另外一类就叫做 Role,是操作命名空间层面的资源的权限。

在上面的内容中,我们也提到过对 Kubernetes 命名空间中的资源的操作主要有如下几种,即 create、update、delete、patch、get、list、watch,分别表示创建资源,更新资源,删除资源,修改资源的字段,获取单个资源,获取资源列表和监听资源。

所以我们简单梳理一下,给 ServiceAccount 授权的基本步骤如下:

  1. 首先需要创建一个 ClusterRole 或者是 Role,然后在这个角色里面定义对资源的操作,可以支持多个资源和多个操作
  2. 其次需要创建一个 ClusterRoleBinding 或者 RoleBinding,将这个 ClusterRole 或者 Role 和 ServiceAccount 绑定在一起。

通过上面的方式,这个 ServiceAccount 就真正获得了对资源的访问和操作权限。

在这里给大家演示下如何给本实验所需要涉及到的资源设置访问控制权限。本次实验主要涉及的资源都是命名空间下的资源,所以我们所要创建的对象就是 Role 和 RoleBinding。

第一步:创建一个 Role,在这个 Role 中授予操作相关资源的权限

kubectl create role shiyanlou-admin-role --resource pod,service,deployment,secret,ingress --verb create,update,delete,patch,get,list,watch

image-1655176981780

第二步:创建一个 RoleBinding,将这个 Role 和 ServiceAccount 绑定在一起

kubectl create rolebinding shiyanlou-admin-role-binding --role shiyanlou-admin-role --serviceaccount default:shiyanlou-admin

image-1655176988999

注意,上面的两个命令都需要回到 Kuernetes 集群的环境中去创建,创建完成之后,再切回到我们前面设置的 k8s-learning-ctx 下面,尝试之前的命令。

kubectl config use-context k8s-learning-ctx

然后再次尝试获取 Pod 列表。

kubectl get pods

image-1655176995728

好了,这次的结果对了。因为我们目前没有任何的 Pod 存在,所以返回没有找到资源,但是这个已经证明我们的授权成功了。当然如果你对此有疑虑,那么可以去查看下 Secret 的列表,因为我们的 ServiceAccount 对应的 Secret 肯定是在的。所以你可以像下面一样,至少能看到一个 Secret:

kubectl get secrets

image-1655177006738

解决授权的问题

现在已经可以解决开篇提到的问题了。我们在上面演示了如何创建一个 Role 并且通过 RoleBinding 将其和 ServiceAccount 绑定在一起,通过这种方式来给 ServiceAccount 授权访问命名空间下的资源。

我们上面希望访问的接口 /healthz/ping 是系统层面的接口,所以这个时候应该给 ServiceAccount 绑定一个系统层面的 ClusterRole。在 Kubernetes 内部有一个名称为 cluster-admin 的 ClusterRole。该 ClusterRole 拥有系统层面的资源访问权限。现在来给我们创建的 ServiceAccount 授权这个角色。

切回到无鉴权模式:

kubectl config use-context kind-kubernetes

然后给这个 ServiceAccount 通过 ClusterRoleBinding 授予一个名为 cluster-admin 的 ClusterRole。

kubectl create clusterrolebinding shiyanlou-admin-cluster-admin-binding --clusterrole cluster-admin --serviceaccount=default:shiyanlou-admin

image-1655177016388

我们通过创建一个 ClusterRoleBinding 来给 ServiceAccount 授权了这个 ClusterRole。接下来我们重试之前的 curl 命令看看是否能够获取正确的输出。

curl 'https://127.0.0.1:37283/healthz/ping' -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNoaXlhbmxvdS1hZG1pbi10b2tlbi16ZDQ0NSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJzaGl5YW5sb3UtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI3MTNkMjA3YS0yNTI0LTQzMjktOTMwZC1hYjlkYzJhYTNjNmEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpzaGl5YW5sb3UtYWRtaW4ifQ.BGNOSiIJErqsEhFH3sQ7ieuGTC--PfOH8eTSAax3qaVKjxitYTMOnl9xGsH9rM20e06R0GobvoOhjQ7y-Y1cNJGmcRcRFS5m5PS30HKihXis_3BvQbs8L-gi4ndP92LrQ2W7h7KVM_u5xB0syOQ3IUh5s4WzIF7TipuDz8n_ib_sDgtG6GlFKRIpTM3zyb-owjaqr0_CzvIvredkMLp-CR0ta14xdRRcysXErYA-lpXv3ot5J5o1nyh1HPzrwmZkTNhmBnXrknguYLMtoxD9xiXR5mDoPh7Nm2EpXQ5v1l3aRVQ3-clul7TqXD6QaL2wlSv-k3ivmYQpSWuJBV_vWw'

image-1655177026447

如预期的那样,我们获得了正确的输出。其实这个名称为 cluster-admin 的 ClusterRole 权限特别大,我们还可以获取集群的所有节点信息。

切换到我们的 ServiceAccount。

kubectl config use-context k8s-learning-ctx

通过这个 ServiceAccount 访问下 Kubernetes 的节点信息。

kubectl get nodes -o wide

image-1655177037617

小结

在本节课程里面,我们重点介绍了如何创建 ServiceAccount,以及如何给 ServiceAccount 授权以访问和操作 Kubernetes 的资源,为我们后面编写程序来完成这个过程打下基础。虽然在后面的章节中会很少再提到 ServiceAccount 的内容,但是你必须了解 ServiceAccount 的重要性。

在最后,我们再好奇心一把,看看我们刚刚创建的 Role 到底定义了什么。

kubectl get role shiyanlou-admin-role -o yaml

image-1655177045840

通过仔细的观察,会发现这里其实是定义了两组 APIGroups,一组名称为空,另外一组名称为 extensions。在第一组 APIGroup 下面,有资源 pod、service、secret,对应的操作权限是 create、update、delete、patch、list、watch。而第二组的 APIGroup 下面,有资源 deployment 和 ingress,对应的操作权限和第一组相同。

据此,大致可以了解,其实所谓的授权过程就是给资源定义操作权限的过程,这些操作权限通过 Role 的方式组织起来,然后再通过 RoleBinding 的方式和 ServiceAccount 绑定在一起。