Z.S.K.'s Records

Kubernetes学习(Bootstrap Token机制)

CKA考试时可能会遇到一个大题: 大致题意是: 在已经存在的一个Kubernetes Cluster中新增一个Node, 使其加入集群及相关证书可自动续签.

我们一般搭建k8s集群,都使用了一些开源工具来做,这类工具都自动帮我们做了很多事情,因此我们只需要简单执行一条命令后,一个集群就完成搭建了, 非常方便,同时也隐藏了一些k8s的知识点, 比如Bootstarp Token就是上面这道题的考察内容.

要了解Bootstrap Token前,我们要知道Kubernetes集群的认证(Kubernetes)及鉴权(Kubernetes)机制.

Kubernetes中的存在非常多的证书, 原于Kubernetes的有些组件同时充当服务端及客户端, 比如apiserver, 其它组件访问它时,它是服务端, 同时,它也会访问etcd或者kubelet,这时它是客户端. 这种访问是需要通过认证的,kubernetes的认证也支持多种方式, 证书为其中最常用的, Kubernetes的证书可以存在多套,每套的证书可以由不同的根证书(CA)签发, apiserver访问kubelet跟访问etcd可以通过使用由不同根证书签发的证书来访问, 但是一般情况下,一个Kuberntes集群中我们习惯使用一个CA来签署所有的证书, 这也是几乎所有的部署工具采用的方式.

Bootstrap Token主要涉及认证阶段(TLS),至于鉴权机制(RBAC),不是本文重点.

集群启用RBAC后各组件之间的通信是基于TLS加密的,client和server需要通过证书中的CN,O来确定用户的user和group,因此client和server都需要持有有效证书, Bootstrap Token只用于kubelet tls 到apiserver的认证, 如果k8s集群本身都未开启tls,那自然就不需要了

Node在Kubelet的启动参数中指定事先在集群中配置好的Bootstrap Token,这个Bootstrap Token只具有引导Node加入集群的权限限定,除此之外,没有其它权限, 这样通过限定权限可以保障集群安全

Kubelet申请证书大致流程

  • 集群产生一个低权账号用户组,通过TOKEN进行认证创建ClusterRole使其具有创建证书申请CSR的权限
  • 给这个组添加ClusterRoleBinding,使得具有这个组的账号的kubelet具有上述权限
  • 给添加ClusterRoleBinding,使得controller-manager自动同意上述两个证书的下发
  • 调整 Controller Manager确保启动tokencleaner和bootstrapsigner(4中自动证书下发的功能)
  • 基于上述TOKEN生成bootstrap.kubeconfig文件,并下发给node节点
  • node节点的kubelet拿着这个bootstrap.kubeconfig向master的apiserver发起CSR(certificate signing request)
  • master自动同意并下发第一个证书
  • node节点的kubelet自动拿着第一个证书与master的apiserver通信申请第二个证书
  • master自动同意并下发第二个证书
  • node节点加入集群

上述过程要求,kubelet跟apiserver具有相同的CA证书

假设现阶段已经存在一个k8s集群, 然后存在一台需要加入改集群的node.

先对Node做一些准备工作:

Node系统初始化

1
2
3
4
5
6
7
8
9
10
11
swapoff -a

modprobe ip_vs_rr

modprobe br_netfilter

cat >> /etc/sysctl.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
  EOF
sysctl -p /etc/sysctl.conf

Node安装docker/kubelet

1
2
apt-get update
apt-get install -y docker kubelet

此时我们查看kubelet会发现它启动失败,因为目前还未配置bootstrap相关配置,它无法连接上apiserver

调整kube-apiserver

要使用bootstrap token引导Node加入集群, kube-apiserver 需要开启参数 --enable-bootstrap-token-auth=true

Bootstrap

生成一个token

1
2
echo "$(head -c 6 /dev/urandom | md5sum | head -c 6)"."$(head -c 16 /dev/urandom | md5sum | head -c 16)"
4b0928.8d7687fdb10e3707

Kubernetes的官方有详细说明, 点击查看Bootstrap Token

既然token只需要符合格式就行,CKA考试的时候可以直接使用页面上的Token来做,毕竟上面这一大串的命令不是谁都能记得住.

创建 Bootstrap 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
apiVersion: v1
kind: Secret
metadata:
# Name MUST be of form "bootstrap-token-<token id>"
name: bootstrap-token-4b0928
namespace: kube-system

# Type MUST be 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
# Human readable description. Optional.
description: "The default bootstrap token generated by 'kubeadm init'."

# Token ID and secret. Required.
token-id: 4b0928
token-secret: 8d7687fdb10e3707

# Expiration. Optional. 要注意这个过期时间, 如果相对于现在的时间已经过期, 则会被自动删除
expiration: 2021-03-10T03:22:11Z

# Allowed usages.
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"

# Extra groups to authenticate the token as. Must start with "system:bootstrappers:"
auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress

secret的格式有要求,如下:

  • namespace必须是kube-system
  • name的后半部分token-id必须是token的前部分
  • Expiration必须是未来的某个时刻, 不然的话一新建就会被删除
  • token必须属于system:bootstrappers开头的组.

更多信息可看这里kubelet-tls-bootstrapping

上述操作只是创建出了这个token, 下面需要给这个token绑定合适的权限, 用于引导Node.

创建 ClusterRole 和 ClusterRoleBinding

查看集群必须存在3个clusterrole及3个clusterrolebinding,如下:

3个clusterrole:

  • system:certificates.k8s.io:certificatesigningrequests:nodeclient 用于第一次证书认证
  • system:certificates.k8s.io:certificatesigningrequests:selfnodeclient 用于证书续签
  • system:certificates.k8s.io:certificatesigningrequests:selfnodeserver

nodeclient 类型的 CSR 仅在第一次启动时会产生,selfnodeclient 类型的 CSR 请求实际上就是 kubelet renew 自己作为 client 跟 apiserver 通讯时使用的证书产生的,selfnodeserver 类型的 CSR 请求则是 kubelet 首次申请或后续 renew 自己的 10250 api 端口证书时产生的

重点关注前两个,后面那个k8s新版本下会自动新建

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.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
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.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
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"]

同时,需要有以下3个clusterrolebinding 到 clusterrole

  • system:node-bootstrapper
  • system:certificates.k8s.io:certificatesigningrequests:nodeclient
  • system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
1
2
3
4
5
6
7
8
9
10
11
12
13
# enable bootstrapping nodes to create CSR
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: create-csrs-for-bootstrapping
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:node-bootstrapper
apiGroup: rbac.authorization.k8s.io
1
2
3
4
5
6
7
8
9
10
11
12
13
# Approve all CSRs for the group "system:bootstrappers"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: auto-approve-csrs-for-group
subjects:
- kind: Group
name: system:bootstrappers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:nodeclient
apiGroup: rbac.authorization.k8s.io
1
2
3
4
5
6
7
8
9
10
11
12
13
# Approve renewal CSRs for the group "system:nodes"
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: auto-approve-renewals-for-nodes
subjects:
- kind: Group
name: system:nodes
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
apiGroup: rbac.authorization.k8s.io

自动批准组system:bootstrappers所有CSR 请求(nodeclient)

自动批准组system:nodes的证书重签CSR请求.

所以这就是为何token需要属于system:bootstrappers

更多信息可看这里bootstrap-tokens

调整kube-controllermanager启动参数

kube-controllermanager需要开启支持自动签署证书的参数--controllers=*,bootstrapsigner,tokencleaner

这样,每当有Node通过Bootstrap token来请求CSR(certificate signing request)

对于 controller manager 来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下三种

  • nodeclient: kubelet 以 O=system:nodesCN=system:node:(node name) 形式发起的 CSR 请求
  • selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN)
  • selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求

对应上面的3个clusterrole

生成kubeconfig文件

1
2
3
4
5
6
7
8
# 设置集群参数
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-cluster kubernetes --server='https://10.170.0.2:6443' --certificate-authority=/etc/kubernetes/pki/ca.crt
# 设置客户端认证参数
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-credentials system:bootstrap:4b0928 --token=4b0928.8d7687fdb10e3707
#设置上下文参数
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig set-context kubernetes --user=system:bootstrap:4b0928 --cluster=kubernetes
# 设置默认上下文
kubectl config --kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig use-context kubernetes

通过上面的命令最终会在/var/lib/kubelet目录下生bootstrap-kubeconfig文件, 将该文件放到node节点上, kubelet指定改文件路径,启动时从改文件中获取apiserver及Bootstrap Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
clusters:
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt
server: https://10.170.0.2:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: system:bootstrap:4b0928
name: kubernetes
current-context: kubernetes
kind: Config
preferences: {}
users:
- name: system:bootstrap:4b0928
user:
token: 4b0928.8d7687fdb10e3707 #正是上面生成的token.

调整Node上kubelet启动参数

node上需要有ca.crt(或者ca.pem)

vim /lib/systemd/system/kubelet.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/home/

[Service]
ExecStart=/usr/bin/kubelet \
--bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig \
--kubeconfig=/var/lib/kubelet/kubeconfig \
--config=/var/lib/kubelet/config.yaml \
--network-plugin=cni \
--rotate-certificates=true \
--log-dir=/var/lib/kubelet/kubelet.log \
--register-node=true \
--v=2
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

其中, 在启动时,如果在–kubeconfig指定的路径下存在kubeconfig文件,则使用该文件做认证,如果不存在,则认证为第一次认证,则使用参数bootstrap-kubeconfig指定的文件, 正是包含bootstrap token的引导文件

通过kube-controller自动签署之后会把最终的kubeconfig写到–kubeconfig参数指定的路径,后续–bootstrap-kubeconfig则没有作用了.

config.yaml是kubelet的其它参数配置文件

1
2
3
4
5
6
7
8
9
10
#cat /var/lib/kubelet/config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: 10.170.0.3
port: 10250
readOnlyPort: 10255
cgroupDriver: systemd
clusterDNS: ["10.96.0.10"]
clusterDomain: cluster.local
failSwapOn: false

流程总结

  • kubelet 读取 bootstrap.kubeconfig,使用其 CA 与 Token 向 apiserver 发起第一次 CSR 请求(nodeclient),使用组system:bootstrappers
  • apiserver 根据 RBAC 规则自动批准首次 CSR 请求(approve-node-client-csr),并下发证书(kubelet-client.crt)
  • kubelet 使用刚刚签发的证书(O=system:nodes, CN=system:node:NODE_NAME)与 apiserver 通讯,并发起申请 10250 server 所使用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准 kubelet 为其 10250 端口申请的证书(kubelet-server-current.crt)
  • 证书即将到期时,kubelet 自动向 apiserver 发起用于与 apiserver 通讯所用证书的 renew CSR 请求和 renew 本身 10250 端口所用证书的 CSR 请求
  • apiserver 根据 RBAC 规则自动批准两个证书
  • kubelet 拿到新证书后关闭所有连接,reload 新证书,以后便一直如此

建议大家都全手动搭建一次kubernetes,这样对Bootstrap Token机制会了解更深入.

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?