Kubelet 证书自动续期

一、问题现象和原因

Kubernetes 日志错误

当 Kubernetes 集群日志中出现 certificate has expired or is not yet valid 错误信息时,表明证书过期

证书过期原因

  • 服务器时间不对,导致证书过期
  • 确实证书过期了
    证书过期,很多同学会很疑惑,我证书明明签署10年有效期或者更久,怎么刚1年就过期了,下面就来解惑。

Kubernetes 集群证书

集群分为两种证书:一、用于集群 Master、Etcd等通信的证书。二、用于集群 Kubelet 组件证书

Kubernetes 集群中 Kubelet 组件坑

在搭建 Kubernetes 集群时,一般只声明用于集群 Master、Etcd等通信的证书 为 10年 或者 更久,但未声明集群 Kubelet 组件证书 ,Kubelet 组件证书 默认有效期为1年。

集群运行1年以后就会导致报 certificate has expired or is not yet valid 错误,导致集群 Node不能和集群 Master正常通信。

二、 解决方法

主要细节流程

kubelet 首次启动通过加载 bootstrap.kubeconfig 中的用户 Token 和 apiserver CA 证书发起首次 CSR 请求,这个 Token 被预先内置在 apiserver 节点的 token.csv 中,其身份为 kubelet-bootstrap 用户和 system:bootstrappers 用户组;想要首次 CSR 请求能成功(成功指的是不会被 apiserver 401 拒绝),则需要先将 kubelet-bootstrap 用户和 system:node-bootstrapper 内置 ClusterRole 绑定;

对于首次 CSR 请求可以手动批准,也可以将 system:bootstrappers 用户组与 approve-node-client-csr ClusterRole 绑定实现自动批准(1.8 之前这个 ClusterRole 需要手动创建,1.8 后 apiserver 自动创建,并更名为 system:certificates.k8s.io:certificatesigningrequests:nodeclient)

默认签署的的证书只有 1 年有效期,如果想要调整证书有效期可以通过设置 kube-controller-manager 的 –experimental-cluster-signing-duration 参数实现,该参数默认值为 8760h0m0s

对于证书自动续签,需要通过协调两个方面实现:

第一,想要 kubelet 在证书到期后自动发起续期请求,则需要在 kubelet 启动时增加 –feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 来实现;

第二,想要让 controller manager 自动批准续签的 CSR 请求需要在 controller manager 启动时增加–feature-gates=RotateKubeletServerCertificate=true 参数,并绑定对应的 RBAC 规则;同时需要注意的是 1.7 版本的 kubelet 自动续签后需要手动重启 kubelet 以使其重新加载新证书,而 1.8 后只需要在 kublet 启动时附带 –rotate-certificates 选项就会自动重新加载新证书

添加参数

  • 修改 kubelet 组件配置,具体添加下面参数
    1
    2
    3
    4
    --feature-gates=RotateKubeletServerCertificate=true
    --feature-gates=RotateKubeletClientCertificate=true
    # 1.8版本以上包含1.8都支持证书更换自动重载,以下版本只能手动重启服务
    --rotate-certificates
  • 修改 controller-manager 组件配置,具体添加下面参数
    1
    2
    3
    # 证书有效期为10年
    --experimental-cluster-signing-duration=87600h0m0s
    --feature-gates=RotateKubeletServerCertificate=true

创建自动批准相关 CSR 请求的 ClusterRole

  • vim tls-instructs-csr.yaml && kubectl apply -f tls-instructs-csr.yaml
    1
    2
    3
    4
    5
    6
    7
    8
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
    name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver
    rules:
    - apiGroups: ["certificates.k8s.io"]
    resources: ["certificatesigningrequests/selfnodeserver"]
    verbs: ["create"]
  • 自动批准 kubelet-bootstrap 用户 TLS bootstrapping 首次申请证书的 CSR 请求
    1
    kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --user=kubelet-bootstrap
  • 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求
    1
    kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group=system:nodes
  • 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求
    1
    kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver --group=system:nodes

    重启kube-controller-manager 和 kubelet 服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ systemctl restart kube-controller-manager

    # 进入到ssl配置目录,删除 kubelet 证书
    $ rm -f kubelet-client-current.pem kubelet-client-2019-05-10-09-57-21.pem kubelet.key kubelet.crt

    # 重启启动,启动正常后会颁发有效期10年的ssl证书
    $ systemctl restart kubelet


    # 进入到ssl配置目录,查看证书有效期
    $ openssl x509 -in kubelet-client-current.pem -noout -text | grep "Not"

    Not Before: May 13 02:36:00 2019 GMT
    Not After : May 10 02:36:00 2029 GMT

证书及配置文件作用

  • token.csv

该文件为一个用户的描述文件,基本格式为 Token,用户名,UID,用户组;这个文件在 apiserver 启动时被 apiserver 加载,然后就相当于在集群内创建了一个这个用户;接下来就可以用 RBAC 给他授权;持有这个用户 Token 的组件访问 apiserver 的时候,apiserver 根据 RBAC 定义的该用户应当具有的权限来处理相应请求

  • bootstarp.kubeconfig

该文件中内置了 token.csv 中用户的 Token,以及 apiserver CA 证书;kubelet 首次启动会加载此文件,使用 apiserver CA 证书建立与 apiserver 的 TLS 通讯,使用其中的用户 Token 作为身份标识像 apiserver 发起 CSR 请求

  • kubelet-client.crt

该文件在 kubelet 完成 TLS bootstrapping 后生成,此证书是由 controller manager 签署的,此后 kubelet 将会加载该证书,用于与 apiserver 建立 TLS 通讯,同时使用该证书的 CN 字段作为用户名,O 字段作为用户组向 apiserver 发起其他请求

  • kubelet.crt

该文件在 kubelet 完成 TLS bootstrapping 后并且没有配置 –feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该文件为一个独立于 apiserver CA 的自签 CA 证书,有效期为 1 年;被用作 kubelet 10250 api 端口

  • kubelet-server.crt

该文件在 kubelet 完成 TLS bootstrapping 后并且配置了 –feature-gates=RotateKubeletServerCertificate=true 时才会生成;这种情况下该证书由 apiserver CA 签署,默认有效期同样是 1 年,被用作 kubelet 10250 api 端口鉴权

  • kubelet-client-current.pem

这是一个软连接文件,当 kubelet 配置了 –feature-gates=RotateKubeletClientCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-client-时间戳.pem;kubelet-client-current.pem 文件则始终软连接到最新的真实证书文件,除首次启动外,kubelet 一直会使用这个证书同 apiserver 通讯

  • kubelet-server-current.pem

同样是一个软连接文件,当 kubelet 配置了 –feature-gates=RotateKubeletServerCertificate=true 选项后,会在证书总有效期的 70%~90% 的时间内发起续期请求,请求被批准后会生成一个 kubelet-server-时间戳.pem;kubelet-server-current.pem 文件则始终软连接到最新的真实证书文件,该文件将会一直被用于 kubelet 10250 api 端口鉴权

TLS bootstrapping 配置相关

当集群开启了 TLS 认证后,每个节点的 kubelet 组件都要使用由 apiserver 使用的 CA 签发的有效证书才能与 apiserver 通讯;此时如果节点多起来,为每个节点单独签署证书将是一件非常繁琐的事情;TLS bootstrapping 功能就是让 kubelet 先使用一个预定的低权限用户连接到 apiserver,然后向 apiserver 申请证书,kubelet 的证书由 apiserver 动态签署

在有些用户首次启动时,可能与遇到 kubelet 报 401 无权访问 apiserver 的错误;这是因为在默认情况下,kubelet 通过 bootstrap.kubeconfig 中的预设用户 Token 声明了自己的身份,然后创建 CSR 请求;但是不要忘记这个用户在我们不处理的情况下他没任何权限的,包括创建 CSR 请求;所以需要如下命令创建一个 ClusterRoleBinding,将预设用户 kubelet-bootstrap 与内置的 ClusterRole system:node-bootstrapper 绑定到一起,使其能够发起 CSR 请求

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

1. apiserver 预先放置 token.csv,内容样例如下:

1
2
3
4
5
6
7
6df3c701f979cee17732c30958745947,kubelet-bootstrap,10001,"system:bootstrappers"

[root@linux-node1 ~]# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
ad6d5bb607a186796d8861557df0d17f
[root@linux-node1 ~]# vim /data/kubernetes/ssl/ bootstrap-token.csv
ad6d5bb607a186796d8861557df0d17f,kubelet-bootstrap,10001,"system:kubelet-bootstrap"

2. 允许 kubelet-bootstrap 用户创建首次启动的 CSR 请求

1
2
3
4
[root@linux-node1 ~]# kubectl create clusterrolebinding kubelet-bootstrap \
--clusterrole=system:node-bootstrapper \
--user=kubelet-bootstrap
clusterrolebinding "kubelet-bootstrap" created

3. 配置 kubelet 自动续期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RotateKubeletClientCertificate 用于自动续期 kubelet 连接 apiserver 所用的证书(kubelet-client-xxxx.pem)
RotateKubeletServerCertificate 用于自动续期 kubelet 10250 api 端口所使用的证书(kubelet-server-xxxx.pem)

KUBELET_ARGS="--cgroup-driver=cgroupfs \
--cluster-dns=10.254.0.2 \
--resolv-conf=/etc/resolv.conf \
--experimental-bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \
--feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true \
--kubeconfig=/etc/kubernetes/kubelet.kubeconfig \
--fail-swap-on=false \
--cert-dir=/etc/kubernetes/ssl \
--cluster-domain=cluster.local. \
--hairpin-mode=promiscuous-bridge \
--serialize-image-pulls=false \
--pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0"

4. 配置 controller manager 自动批准相关 CSR 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
如果不配置 --feature-gates=RotateKubeletServerCertificate=true 参数,则即使配置了相关的 RBAC 规则,也只会自动批准 kubelet client 的 renew 请求

KUBE_CONTROLLER_MANAGER_ARGS="--address=0.0.0.0 \
--service-cluster-ip-range=10.254.0.0/16 \
--feature-gates=RotateKubeletServerCertificate=true \
--cluster-name=kubernetes \
--cluster-signing-cert-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--cluster-signing-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--service-account-private-key-file=/etc/kubernetes/ssl/k8s-root-ca-key.pem \
--root-ca-file=/etc/kubernetes/ssl/k8s-root-ca.pem \
--leader-elect=true \
--node-monitor-grace-period=40s \
--node-monitor-period=5s \
--pod-eviction-timeout=5m0s"

5. 创建自动批准相关 CSR 请求的 ClusterRole

相对于 1.7 版本,1.8 的 apiserver 自动创建了前两条 ClusterRole,所以只需要创建一条就行了

1
2
3
4
5
6
7
8
9
10
# A ClusterRole which instructs the CSR approver to approve a node requesting a
# serving cert matching its client cert.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver
rules:
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests/selfnodeserver"]
verbs: ["create"]

6. 将 ClusterRole 绑定到适当的用户组,以完成自动批准相关 CSR 请求

1
2
3
4
5
6
7
8
# 自动批准 system:bootstrappers 组用户 TLS bootstrapping 首次申请证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient --group=system:bootstrappers

# 自动批准 system:nodes 组用户更新 kubelet 自身与 apiserver 通讯证书的 CSR 请求
kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group=system:nodes

# 自动批准 system:nodes 组用户更新 kubelet 10250 api 端口证书的 CSR 请求
kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver --group=system:nodes

一切就绪后重启 kubelet 组件,1.8 版本 kubelet 会自动重载证书


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!