一、概述 本文主要记录一下搭建一个kubelet搭建到加入集群到手动、自动(证书轮换的)证书认证过程, 那么我的环境是用的v1.12.4版本,有一台master,除了master上面的一些组件已经搭建完毕,主要重点是在kubelet上面,所以现在就是master已经工作正常的情况下,加入新的kubelet(worker节点)需要做一些什么样的操作。 然后需要实现的是:
通过bootstrap token以及boostrap.kubeconfig来引导worker节点申请证书 
如果第一步申请发出去之后,在手动在master端进行手动批准证书申请,然后发放kubelet证书 
实现kubelet证书的自动批准 
实现kubelet证书的自动轮换操作 
 
简单理一下TLS Bootstrapping的一个引导过程 
master端创建API Server和Kubelet Client通信凭证即生成 apiserver.pem/apiserver-key.pem/apiserver.csr; 
在集群内创建特定的 Bootstrap Token Secret,该 Secret 将替代以前的 token.csv 内置用户声明文件; 
在集群内创建首次 TLS Bootstrap 申请证书的 ClusterRole 即system:node-bootstrapper,并将上面的bootstrap token secret进行绑定即 clusterrolebinding kubelet-bootstrap,这样通过绑定就能以这个内置用户组信息去发起CSR请求啦; 
生成特定的包含 TLS Bootstrapping Token 的 bootstrap.kubeconfig; 
调整kubelete启动参数,指定引导文件bootstrap.kubeconfig; 
重载配置、重启服务,master端手动批准完成worker节点的CSR请求; 
后续如果没有配置证书轮换的话,就会一直使用由controller-manager批准颁发的证书文件了,有效期是一年。(自动批准&证书轮换下面再说) 
 
当然以我的理解要要实现证书轮换那么肯定是没有外界干预的情况下自动执行的,那这个功能也肯定是需要自动批准这个功能的。
那么自动批准需要做的一些事情却是在手动批准的基础上做了一些操作:
创建CSR请求的 TLS Bootstrap 申请证书的renew Kubelet client/server 的 ClusterRole,以及其相关对应的 ClusterRoleBinding;并绑定到对应的组或用户 
调整 Controller Manager 配置,以使其能自动签署相关证书和自动清理过期的 TLS Bootstrapping Token 
可选的: 集群搭建成功后立即清除 Bootstrap Token Secret,或等待 Controller Manager 待其过期后删除,以防止被恶意利用 
 
二、手动批准 2.1 创建API Server和Kubelet Client通信凭证 我这里已经有ca根证书文件了,那就从创建apiserver证书开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $  cat  >> apiserver-csr .json << EOF{   "CN" : "kube-apiserver" ,   "hosts" : [     "10.96.0.1" ,     "127.0.0.1" ,     "192.168.56.201" ,     "kubernetes" ,     "kubernetes.default" ,     "kubernetes.default.svc" ,     "kubernetes.default.svc.cluster" ,     "kubernetes.default.svc.cluster.local"    ],   "key" : {     "algo" : "rsa" ,     "size" : 2048    },   "names" : [     {       "C" : "CN" ,       "ST" : "Hangzhou" ,       "L" : "Hangzhou" ,       "O" : "Kubernetes" ,       "OU" : "Kubernetes-manual"      }   ] } EOF 
 
我这里就单台master,所以就写了个这一个IP地址
1 2 3 4 5 6 7 8 9 10 $cfssl  gencert \  -ca=/etc/ kubernetes/pki/ ca.pem \   -ca-key=/etc/ kubernetes/pki/ ca-key.pem \   -config=ca-config.json \   -profile=kubernetes \   apiserver-csr.json | cfssljson -bare ${PKI_DIR} /apiserver ls /etc/ kubernetes/pki/ apiserver*.pem /etc/ kubernetes/pki/ apiserver-key.pem  /etc/ kubernetes/pki/ apiserver.pem
 
2.2 创建引导配置文件 bootstrap-kubelet.kubeconfig 2.2.1 生成bootsrap—token 1 2 3 export  TOKEN_ID =$(openssl rand 3 -hex)export  TOKEN_SECRET =$(openssl rand 8 -hex)export  BOOTSTRAP_TOKEN =${TOKEN_ID} .${TOKEN_SECRET}
 
关于这个token的格式要求参见这里 
2.2.2 创建boostrap token secret 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 cat > >  bootstrap-token-Secret.yml < <  EOF apiVersion:  v1kind:  Secretmetadata:   name:  bootstrap-token-{TOKEN_ID}   namespace:  kube-system type:  bootstrap.kubernetes.io/token stringData:   token-id:  {TOKEN_ID}   token-secret:  {TOKEN_SECRET}   usage-bootstrap-authentication:  "true"    usage-bootstrap-signing:  "true"    auth-extra-groups:  system:bootstrappers:default-node-token EOF sed - ri "s#\{ TOKEN_ID\} #${TOKEN_ID} #g"  bootstrap-token-Secret.yml sed - ri "/token-id/s#\S +\$ #'&'#"  resources/bootstrap-token-Secret.yml  sed - ri "s#\{ TOKEN_SECRET\} #${TOKEN_SECRET} #g"  resources/bootstrap-token-Secret.yml  kubectl create - f resources/bootstrap-token-Secret.yml  $ kubectl describe secret  bootstrap-token-b8cf79 - n kube-system Name:          bootstrap-token-b8cf79Namespace:     kube-systemLabels:        <none> Annotations: Type:          bootstrap.kubernetes.io/token Data == == auth-extra-groups:                39  bytestoken-id:                         6  bytestoken-secret:                     16  bytesusage-bootstrap-authentication:   4  bytesusage-bootstrap-signing:          4  bytes
 
2.2.2 创建启动引导配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ kubectl  config  set-cluster  kubernetes  \     --certificate-authority=/etc/kubernetes/pki/ca.pem  \     --embed-certs=true  \     --server=192.168.56.202  \     --kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  $ kubectl  config  set-credentials  tls-bootstrap-token-user  \     --token=${BOOTSTRAP_TOKEN}  \     --kubeconfig=/etc/kubernetes//bootstrap-kubelet.kubeconfig  $ kubectl  config  set-context  tls-bootstrap-token-user @kubernetes  \     --cluster=kubernetes  \     --user=tls-bootstrap-token-user  \     --kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  $ kubectl  config  use-context  tls-bootstrap-token-user @kubernetes  \     --kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig  
 
该文件生成之后需要拷贝至worker节点上面去,并且修改配置,指定该引导配置文件 这个流程在官方文档中也有描述:bootstrap初始化过程 
2.2.3 调整worker节点kubelet启动配置文件加上这个引导文件(参数) 1 2 3 4 5 6 7 8 9 10 11 12 [Service] ExecStart =/usr/local/bin/kubelet  --kubeconfig =/etc/kubernetes/kubelet.kubeconfig \   --network-plugin =cni \   --cni-conf-dir =/etc/cni/net.d \   --cni-bin-dir =/opt/cni/bin \   --logtostderr =false  \   --log-dir =/etc/kubernetes/log \   --config =/etc/kubernetes/kubelet-conf.yml \   --node-labels =node-role.kubernetes.io/master=''  \   --pod-infra-container-image =registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.1  \   --v =2  
 
此时如果启动kubelet服务的话在apiserver将会报错:
这是因为在默认情况下,kubelet 通过 bootstrap.kubeconfig 中的预设用户 Token 声明了自己的身份,然后创建 CSR 请求;但是不要忘记这个用户在我们不处理的情况下他没任何权限的,包括创建 CSR 请求;所以需要如下命令创建一个 ClusterRoleBinding,将预设用户 kubelet-bootstrap 与内置的 ClusterRole system:node-bootstrapper 绑定到一起,使其能够发起 CSR 请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cat > >  kubelet-bootstrap-clusterrolebinding.yaml < <  EOF apiVersion:  rbac.authorization.k8s.io/v1 kind:  ClusterRoleBindingmetadata:   name:  kubelet-bootstrap roleRef:   apiGroup:  rbac.authorization.k8s.io   kind:  ClusterRole   name:  system:node-bootstrapper subjects: -  apiGroup:  rbac.authorization.k8s.io  kind:  Group   name:  system:bootstrappers:default-node-token EOF 
 
将上面创建后就可以看到我们通过手动批准之后worker节点就加入到集群当中来了 而且在worker节点的/var/lib/kubelet/pki目录下已经获得controller-manager颁发下来的证书,并且还分发了一个kubelet.kubeconfig的文件,这个文件虽然配置中指定了,但这个也是由controller-manager分发给worker节点的。
1 2 3 4 5 6 [root@k8s-n0 kubernetes] total 12 -rw------- 1  root root 1293  Dec 30  10:14 kubelet-client-2018-12-30-10-14-50.pem lrwxrwxrwx 1  root root   59  Dec 30  10:14 kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2018-12-30-10-14-50.pem -rw-r--r-- 1  root root 2153  Dec 30  10:14 kubelet.crt -rw------- 1  root root 1679  Dec 30  10:14 kubelet.key 
 
还有个问题就是,集群已经完成,在我执行exec想进入一个容器的时候却出现了forbidden的问题:
1 2 [root@k8s-m1 resources]# kubectl exec -it kubectl-terminal-ubuntu /bin/bash error: unable to  upgrade  connection: Forbidden (user =kube-apiserver, verb =create, resource =nodes, subresource =proxy) 
 
这是因为kube-apiserveruser并没有nodes的资源存取权限,属于正常。由于 API 权限,故需要建立一个 RBAC Role 来获取存取权限;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 cat > >  apiserver-to-kubelet-rbac.yml < <  EOF apiVersion:  rbac.authorization.k8s.io/v1 kind:  ClusterRolemetadata:   annotations:      rbac.authorization.kubernetes.io/ autoupdate:  "true"    labels:      kubernetes.io/ bootstrapping:  rbac-defaults   name:  system:kube-apiserver-to-kubelet rules:   -  apiGroups:        -  ""      resources:        -  nodes/proxy        -  nodes/stats        -  nodes/log        -  nodes/spec        -  nodes/metrics      verbs:        -  "*"  - --apiVersion:  rbac.authorization.k8s.io/v1 kind:  ClusterRoleBindingmetadata:   name:  system:kube-apiserver   namespace:  ""  roleRef:   apiGroup:  rbac.authorization.k8s.io   kind:  ClusterRole   name:  system:kube-apiserver-to-kubelet subjects:   -  apiGroup:  rbac.authorization.k8s.io     kind:  User     name:  kube-apiserver EOF 
 
创建好之后就可以exec啦~
以上,TLS Bootstrapping的手动批准已经完成。
三、自动批准 上面已经完成了手动批准的操作,那接下来就是实现以下自动批准以及证书轮换的实现
上面需要的配置其实都差不多了,只需要再做一些配置即可完成;
kubelet 所发起的 CSR 请求是由 controller manager 签署的;如果想要是实现自动续期,就需要让 controller manager 能够在 kubelet 发起证书请求的时候自动帮助其签署证书;那么 controller manager 不可能对所有的 CSR 证书申请都自动签署,这时候就需要配置 RBAC 规则,保证 controller manager 只对 kubelet 发起的特定 CSR 请求自动批准即可;在 TLS bootstrapping 官方文档中,CSR有三种请求类型:
nodeclient: kubelet 以 O=system:nodes 和 CN=system:node:(node name) 形式发起的 CSR 请求 
selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN) 
selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求 
 
通俗点讲就是: nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet renew 自己作为 client 跟 apiserver 通讯时使用的证书产生的,selfnodeserver 类型的 CSR 请求则是 kubelet 首次申请或后续 renew 自己的 10250 api 端口证书时产生的
那么针对以上3种CSR请求分别给出了3种对应的 ClusterRole,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 kind:  ClusterRole apiVersion:  rbac.authorization.k8s.io/v1 metadata:   name:  system:certificates.k8s.io:certificatesigningrequests:nodeclient  rules: -  apiGroups:  ["certificates.k8s.io" ]  resources:  ["certificatesigningrequests/nodeclient" ]   verbs:  ["create" ] --- kind:  ClusterRole apiVersion:  rbac.authorization.k8s.io/v1 metadata:   name:  system:certificates.k8s.io:certificatesigningrequests:selfnodeclient  rules: -  apiGroups:  ["certificates.k8s.io" ]  resources:  ["certificatesigningrequests/selfnodeclient" ]   verbs:  ["create" ] --- 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" ] 
 
其中selfnodeclient,nodeclient 已经是集群内置的了;
1 2 3 4 5 6 7 $ kubectl get clusterrole ...... ...... system:certificates.k8s.io:certificatesigningrequests:nodeclient       28h system:certificates.k8s.io:certificatesigningrequests:selfnodeclient   28h ...... ...... 
 
然后是三个 ClusterRole 对应的 三个ClusterRoleBinding;需要注意的是 在使用 Bootstrap Token 进行引导时,Kubelet 组件使用 Token 发起的请求其用户名为system:bootstrap:<token id>,用户组为 system:bootstrappers;所以 我们在创建 ClusterRoleBinding 时要绑定到这个用户或者组上;这里我选择全部绑定到组上:
1 2 3 4 5 6 7 8 9 10 11 12 13 cat >> kubelet-bootstrap-rbac.yml << EOF kubectl create clusterrolebinding kubelet-bootstrap --clusterrole =system:node-bootstrapper --group =system:bootstrappers kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole =system:certificates.k8s.io:certificatesigningrequests:nodeclient --group =system:bootstrappers kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole =system:certificates.k8s.io:certificatesigningrequests:selfnodeclient --group =system:nodes kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole =system:certificates.k8s.io:certificatesigningrequests:selfnodeserver --group =system:nodes 
 
即: kubelet worker节点证书手动或自动批准需要用到的角色以及角色绑定有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # ClusterRole $ kubectl get clusterrole ...... ...... system:certificates.k8s.io:certificatesigningrequests:nodeclient       2d system:certificates.k8s.io:certificatesigningrequests:selfnodeclient   2d system:certificates.k8s.io:certificatesigningrequests:selfnodeserver   5m system:node-bootstrapper                                               2d system:kube-apiserver-to-kubelet                                       17h ...... ...... # ClusterRoleBinding $ kubectl get clusterrolebinding | grep auto ...... ...... node-client-auto-approve-csr                           16h node-client-auto-renew-crt                             16h node-server-auto-renew-crt                             16h system:kube-apiserver                                  17h kubelet-bootstrap                                      18h ...... ...... 
 
创建好对应的角色及角色绑定之后就可以修改这个证书的有效期啦;只要在Description=Kubernetes Controller Manager 服务的启动配置参数中加入:
1 2 3 --experimental-cluster-signing-duration =10 m0s \--feature-gates =RotateKubeletClientCertificate=true  \--feature-gates =RotateKubeletServerCertificate=true  
 
以及kubelet服务启动配置参数中加入:
1 2 --feature-gates =RotateKubeletClientCertificate=true  \--feature-gates =RotateKubeletServerCertificate=true 
 
通过文档了解到--feature-gates 这个是kubernetes中特有的一些功能,有些功能是处于开发版本的,具体可以参看这里 
以下就是kubelet运行一段时间后,通过在controller-manager设置的证书有效期快过期的时候通过自动申请并自动批准的结果; 可以看出来node-csr-ysGrNbyioCNnj3UUFiQGn5WwLGF1P6wJ4DTIib1IcqQ 这个CSR是第一次启动时的用户system:bootstrap:fd2f4f的证书请求。
并且在证书轮换的过程也可以通过日志发现
1 Dec  31  04 :10 :06  k8s-n0 kubelet: I1231 17 :10 :06 .753064    18294  transport.go:132 ] certificate rotation detected, shutting down client connections to start using new credentials
 
那么以上就是kubelet证书的整个批准以及证书轮换的过程。
四、TLS Bootstrapping总结 流程总结
kubelet 首次启动通过加载 bootstrap.kubeconfig 中的用户 Token 和 apiserver CA 证书发起首次 CSR 请求,这个 Token 被预先内置在 apiserver 节点的 token.csv(新版本中已经推荐使用Bootstrap Token Secert) 中,其身份为 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 规则