+-
kubernetes apiserver的node鉴权

近日使用团队内部一直在维护的ansible role playbook初始化一个新的k8s集群(v1.19)时遇到了问题,`kubectl get node`命令显示各个节点一直是NotReady状态,同时各节点上的kubelet日志一直报下面的错误:

... kubelet.go:2209] node "node1" not found ... reflector.go:127] k8s.io/client-go/informers/factory.go:134: Failed to watch *v1.Node: failed to list *v1.Node: nodes "node1" is forbidden: User "system:node:node1" cannot list resource "nodes" in API group "" at the cluster scope ... failed to ensure node lease exists, will retry in 7s, error: leases.coordination.k8s.io "node1" is forbidden: User "system:node:node1" cannot get resource "leases" in API group "coordination.k8s.io" in...mespace "kube-node-lease" ... kubelet_node_status.go:470] Error updating node status, will retry: error getting node "node1": nodes "node1" is forbidden: User "system:node:node1" cannot get resource "nodes" in API group "" at the cluster scope

从日志中初步分析是kubelet对apiserver的访问权限问题引起,由于使用这个ansible role是团队内部一直在维护的,目前管理着线上线下共4套k8s集群,一直没有这个问题出现。于是初步怀疑已有的4套集群是经历了各个历史版本逐步升级到1.19的,这个过程中可能k8s给自动做了相关的配置兼容性,现在初始化的这个新集群因为是全新安装,可能我们的ansible playbook生成的配置已经不正确了。

kubelet是使用SSL证书kubelet.crt访问apiserver的,我们并没有使用`kubelet tls bootstrapping`这个特性,而是在初始化个Node时使用ansible自动生成kubelet的证书,并为证书写入`CN=system:node:<node name>`.

kubelet在请求apiserver时使用的用户就是`system:node:<node name>`默认是在`system:nodes`这个Group组中,出现kubelet无权限访问apiserver的问题,应该是对应的权限或ClusterRole未关联到这个组上。带着这个问题,查看了k8s rbac的文档https://kubernetes.io/zh/docs/reference/access-authn-authz/rbac/,在"核心组件角色"一节中找到答案:

默认 ClusterRole 默认 ClusterRoleBinding 描述 ... system:node 无 允许访问 kubelet 所需要的资源,包括对所有 Secret 的读操作和对所有 Pod 状态对象的写操作。 你应该使用 Node 鉴权组件 和 NodeRestriction 准入插件 而不是 system:node 角色。同时基于 kubelet 上调度执行的 Pod 来授权 kubelet 对 API 的访问。 system:node 角色的意义仅是为了与从 v1.8 之前版本升级而来的集群兼容。 ...

在旧的版本k8s会自动创建`system:node`这个ClusterRole到`system:nodes`这个Group的ClusterRoleBinding,现在没有了,从上面的"描述"中可以看出是希望我们使用"Node鉴权组件",而不再推荐使用设置ClusterRoleBinding的方式。查看了一下当前我们`kube-apiserver`的参数包含`--authorization-mode=RBAC`和`--enable-admission-plugins=NodeRestriction`。

于是,先尝试使用不再推荐使用设置ClusterRoleBinding的方式解决这个问题,手动设置绑定:

kubectl set subject clusterrolebinding system:node --group=system:nodes

设置之后,等一会儿再次`kubectl get node`查看各个节点已经Ready了。下面删除这个clusterrolebinding,改由官方推荐的使用Node鉴权组件的方式:

kubectl delete clusterrolebinding system:node


删除后,各节点再次进入NotReady状态,此时修改kube-apiserver的启动参数包含`--authorization-mode=Node,RBAC`和`--enable-admission-plugins=NodeRestriction`,再重启apiserver,等一会儿再次`kubectl get node`查看各个节点又Ready了。问题完美解决。

Kubernetes的Node鉴权

Node鉴权是k8s apiserver的一种特殊用途的鉴权模式,专门用于对kubelet发出的请求进行鉴权。Node鉴权组件默认允许kubelet执行一系列操作,如对services, endpoints, nodes, pods, secrets, configmaps, pvcs以及绑定到kubelet所在节点的与Pod相关的持久化卷的读取操作;写入节点和节点状态,Pod和Pod状态,事件信息的写入操作;准入插件NodeRestriction用来限制kubelet只能修改写入其自己所在节点相关的资源。

要启用Node鉴权组件,需使用`--authorization-mode=Node`启动apiserver,另外最好启用使用`--enable-admission-plugins=NodeRestriction`启用NodeRestriction准入插件来限制kubelet只能修改写入其自己所在节点相关的资源。

我们可以把Node鉴权组件理解成官方提供的一个设置kubelet访问apiserver权限的最佳实践,随着k8s各个版本的迭代可能会添加或删除权限,但始终会确保kubelet具有正确操作apiserver所需的最小权限集,只要kubelet使用的凭证可以表示它在`system:nodes`组中,如用户名为 system:node:<nodeName>,就可以获得Node鉴权组件的授权,这显然比设置设置ClusterRoleBinding的方式更优雅。