起因

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

1
2
3
4
... 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的文档Using RBAC Authorization,在"核心组件角色"一节中找到答案:

1
2
3
4
5
6
默认 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的方式解决这个问题,手动设置绑定:

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

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

1
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:,就可以获得Node鉴权组件的授权,这显然比设置设置ClusterRoleBinding的方式更优雅。

参考