记录K8s 1.10.x TLS Bootstrap Bug一枚

1
2
3
4
OS-Release: CentOS7
kernel: 4.4-LTS
kubernetes version: 1.10.9
kubelet 与 kube-apiserver 通信方式: TLS Bootstrap

问题复现

集群基于Bear Token方式的 TLS Bootstrap 进行通信, 且使用 kubelet 客户端证书过期轮换的特性

根据 1.10.x 版本的官方文档介绍, 配置使用此特性, 这里具体列出 kubelet 上的相关配置

  • 在 kubelet 的启动参数中传入--rotate-certificates 此参数意为声明开启证书轮换机制, 为即将过期的客户端节点颁发新的证书文件
  • 在 kubelet 参数配置模板文件中添加如下配置, 该配置默认开启
1
2
featureGates:
RotateKubeletClientCertificate: true

创建集群角色绑定

1
2
3
kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--group=system:bootstrappers

为节点创建 Bootstrap Token

1
2
3
4
TOKEN=`kubeadm token create \
--description kubelet-bootstrap-token \
--groups system:bootstrappers:node6752 \
--kubeconfig ~/.kube/config`

为节点创建 Bootstrap kubeconfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kubectl config set-cluster kubernetes \
--certificate-authority=/opt/soft/k8s/pki/ca/ca.crt \
--embed-certs=true \
--server=https://k8smaster:6443 \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config set-credentials kubelet-bootstrap \
--token=$TOKEN \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config set-context default \
--cluster=kubernetes \
--user=kubelet-bootstrap \
--kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubectl config use-context default --kubeconfig=kubelet-bootstrap-node6752.kubeconfig

kubelet-bootstrap-node6752.kubeconfig Bootstrap kubeconfig 文件分发给 kubelet 客户端

在kubelet客户端指定配置文件

  • –bootstrap-kubeconfig=”/path/to/bootstrap/kubeconfig”
  • –kubeconfig=kubelet.kubeconfig

注意: 这里指定的--kubeconfig文件并不存在, 当 kubelet 完成认证, 拿到下发的证书后, 证书会存放到--cert-dir指定的文件夹, 然后根据实际证书来创建 kubeconfig 文件并写入到--kubeconfig指定的路径

复现问题

在启动 kubelet 服务后, 在 master 端可以看到 csr 请求, 手动审批

1
kubectl certificate approve node-csr-32YapU7hg6hB_87Hyus7NkD6a7-ew8sX2qP09ikjUh6

审批后, 在 kubelet 客户端指定的证书目录下, 你可以看到如下文件:

1
2
3
4
kubelet-client.crt (由 kube-controller-manager 颁发的客户端证书)
kubelet-client.key (本地自动创建的客户端秘钥)
kubelet.crt (kubelet本地自签名服务端证书)
kubelet.key (本地自动创建的服务端秘钥)

在 kubelet 指定的 --kubeconfig 文件, 也被写入了对应的内容, 问题就出现在了这里

其中users.user.client-certificateusers.user.client-key的配置中, 分别指定了kubelet-client.crtkubelet-client.key

1
2
3
4
5
users:
- name: default-auth
user:
client-certificate: /path/pki/kubelet-client.crt
client-key: /path/pki/kubelet-client.key

但问题是, 当kubelet-client.crt证书过期后, 由 Kubernetes 证书轮换特性续期的新证书, 却是以软连接的方式提供的, 而且, 软连接的名字和kubeconfig中配置的证书名字不一致. 以下是 kube-controller-manager 颁发的新证书形式

1
2
kubelet-client-2018-12-11-11-14-18.pem
kubelet-client-current.pem -> /path/pki/kubelet-client-2018-12-11-11-14-18.pem
  • 新的证书只有一个文件, 格式为 pem. 该文件中既包含了证书内容也包含了 key 的内容
  • 新证书的实际文件名为: kubelet-client-DATE.pem
  • 新证书的软连接为: kubelet-client-current.pem

可以看出, 当证书续期之后, 新证书的引用路径(文件名)与最开始自动创建的 kubeconfig 文件中指定的证书文件不一致. 这个问题将导致原有证书过期且 kubelet 重启后, 将无法再与 apiserver 通信, apiserver 端也将标记该节点为NotReady

解决方案

解决方案1: 手动修改 kubeconfig 文件

执行以下操作:

1
2
3
4
5
cat kubelet-client.crt kubelet.key > kubelet-origin.pem
ln -s kubelet-origin.pem /path/to/pki/kubelet-client-current.pem
sed -i 's/kubelet-client.crt/kubelet-client-current.pem/g' /path/to/kubelet.kubeconfig
sed -i 's/kubelet-client.key/kubelet-client-current.pem/g' /path/to/kubelet.kubeconfig
systemctl restart kubelet

原理就是在证书续期之前就手动构造证书续期之后的结构, 这样在证书续期后, 就可以无缝使用新证书了

解决方案2: 升级集群版本

通过查看官方文档, 在1.11.0版本中, 该问题得到了修复, 你也可以通过升级集群版本来解决这个问题

注意, 集群升级后, 原有的证书都还是继续使用的, 所以升级并解决不了原来的问题, 除非你将节点删除后, 重新使用 BearToken 的方式重新申请加入集群


参考文档: