一、概述

本文主要记录一下搭建一个kubelet搭建到加入集群到手动、自动(证书轮换的)证书认证过程, 那么我的环境是用的v1.12.4版本,有一台master,除了master上面的一些组件已经搭建完毕,主要重点是在kubelet上面,所以现在就是master已经工作正常的情况下,加入新的kubelet(worker节点)需要做一些什么样的操作。
然后需要实现的是:

  • 通过bootstrap token以及boostrap.kubeconfig来引导worker节点申请证书
  • 如果第一步申请发出去之后,在手动在master端进行手动批准证书申请,然后发放kubelet证书
  • 实现kubelet证书的自动批准
  • 实现kubelet证书的自动轮换操作

简单理一下TLS Bootstrapping的一个引导过程

  1. master端创建API Server和Kubelet Client通信凭证即生成 apiserver.pem/apiserver-key.pem/apiserver.csr;
  2. 在集群内创建特定的 Bootstrap Token Secret,该 Secret 将替代以前的 token.csv 内置用户声明文件;
  3. 在集群内创建首次 TLS Bootstrap 申请证书的 ClusterRole 即system:node-bootstrapper,并将上面的bootstrap token secret进行绑定即 clusterrolebinding kubelet-bootstrap,这样通过绑定就能以这个内置用户组信息去发起CSR请求啦;
  4. 生成特定的包含 TLS Bootstrapping Tokenbootstrap.kubeconfig;
  5. 调整kubelete启动参数,指定引导文件bootstrap.kubeconfig;
  6. 重载配置、重启服务,master端手动批准完成worker节点的CSR请求;
  7. 后续如果没有配置证书轮换的话,就会一直使用由controller-manager批准颁发的证书文件了,有效期是一年。(自动批准&证书轮换下面再说)

当然以我的理解要要实现证书轮换那么肯定是没有外界干预的情况下自动执行的,那这个功能也肯定是需要自动批准这个功能的。

那么自动批准需要做的一些事情却是在手动批准的基础上做了一些操作:

  1. 创建CSR请求的 TLS Bootstrap 申请证书的renew Kubelet client/server 的 ClusterRole,以及其相关对应的 ClusterRoleBinding;并绑定到对应的组或用户
  2. 调整 Controller Manager 配置,以使其能自动签署相关证书和自动清理过期的 TLS Bootstrapping Token
  3. 可选的: 集群搭建成功后立即清除 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: v1
kind: Secret
metadata:
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

# 查看详情,一定要放到kube-system这个namespace
$ kubectl describe secret bootstrap-token-b8cf79 -n kube-system
Name: bootstrap-token-b8cf79
Namespace: kube-system
Labels: <none>
Annotations:
Type: bootstrap.kubernetes.io/token

Data
====
auth-extra-groups: 39 bytes
token-id: 6 bytes
token-secret: 16 bytes
usage-bootstrap-authentication: 4 bytes
usage-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
# bootstrap set cluster
$ 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

# bootstrap set credentials
$ kubectl config set-credentials tls-bootstrap-token-user \
--token=${BOOTSTRAP_TOKEN} \
--kubeconfig=/etc/kubernetes//bootstrap-kubelet.kubeconfig

# bootstrap set context
$ kubectl config set-context tls-bootstrap-token-user@kubernetes \
--cluster=kubernetes \
--user=tls-bootstrap-token-user \
--kubeconfig=/etc/kubernetes/bootstrap-kubelet.kubeconfig

# bootstrap use default context
$ 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: ClusterRoleBinding
metadata:
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]# ll /var/lib/kubelet/pki/
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: ClusterRole
metadata:
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: ClusterRoleBinding
metadata:
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
# A ClusterRole which instructs the CSR approver to approve a user requesting
# node client credentials.
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"]
---
# A ClusterRole which instructs the CSR approver to approve a node renewing its
# own client credentials.
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"]
---
# 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"]

其中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
# 允许 system:bootstrappers 组用户创建 CSR 请求
# (这一步我在上面已经做过啦)
kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

# 自动批准 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 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=10m0s \
--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总结

流程总结

  1. 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 绑定;
  2. 对于首次 CSR 请求可以手动签发,也可以将 system:bootstrappers 用户组与 approve-node-client-csr ClusterRole 绑定实现自动签发(1.8 之前这个 ClusterRole 需要手动创建,1.8 后 apiserver 自动创建,并更名为 system:certificates.k8s.io:certificatesigningrequests:nodeclient)
  3. 默认签署的的证书只有 1 年有效期,如果想要调整证书有效期可以通过设置 kube-controller-manager 的 --experimental-cluster-signing-duration 参数实现,该参数默认值为 8760h0m0s

  4. 对于证书轮换,需要通过协调两个方面实现;第一,想要 kubelet 在证书到期后自动发起续期请求,则需要在 kubelet 启动时增加 --feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true 来实现;第二,想要让 controller manager 自动批准续签的 CSR 请求需要在 controller manager 启动时增加 --feature-gates=RotateKubeletServerCertificate=true 参数,并绑定对应的 RBAC 规则