Kubernetes集群的构成

Master Node (Control plane)

Master 是整个 Kubernetes 集群构成的基础,它负责整个集群的管理,例如处理集群的状态;组件包含 API Server, Controller manager, Scheduller, Etcd

API server

API 服务器是 Master 的统一前端入口,负责集群内其他组件的 协调 与 通信。该组件用于定义集群的状态。可以通过命令行, HTTP API, 第三方托管平台(dashboard, Rancker, Kuboard等)与 Kubernetes API 进行交互。

Scheduler

调度程序 Scheduler 负责根据可用资源来决定如何去部署容器,部署到哪里?确保所有 Pod(容器组)都分配给某一组节点。

Controller Manager

Controller manager,又分为Controller 和 Manager,Controller的组要作用是用于协调各种控制器(Deployment, Daemonset…),这些控制器可确保在节点发生故障时采取适当的措施。而 Manager 则管理的众多Controller;更一般地说,CM 负责随时将集群的当前状态调整到所需状态(Kubernetes设计基石)。

etcd

etcd 是控制平面内的一个组件,他提供了 Kubernetes 资源的存储,并为集群内组件提供了 Watch 的功能,这将意味着,etcd 在 kubernetes 集群中作为存储与分布式协调的功能。

Worker nodes

每个集群中至少需要存在一个工作节点,但是通常会有大量的节点;而工作节点包括的组件不限于 Kubelet, Kube-proxy, CNI Plugin。

Kubelet

kubelet是工作节点中管理运行时的组件,负责整个Pod (容器组)进程的生命周期

Kube-proxy

Kube-proxy 为整个集群内提供了 service 的功能,如果这个组件无法正常工作,那么整个集群内的网络通信将不能正常,因为 service 是作为集群内服务的访问入口,包含 Kubernetes API service。

CNI

CNI (Container Network Interface) 是 Kubernetes 集群中提供跨节点通信的一个组件,通常来说,它是一个独立于容器运行时(Docker等)的网络插件规范,旨在实现容器之间和容器与宿主机之间的网络连接。

一个 CNI 基本的功能就是去管理网络设备的生命周期,例如,生成网卡,添加IP地址,注销网卡,而构成这些网络功能的则有操作系统来提供的,例如网络隧道(VxLAN),而还存在一种情况就是三层网络,这时CNI会作为一个路由器来分发路由。除此之外,CNI还提供了更多的功能,例如数据包加密,网络策略,服务网格,网络加速,IPAM等功能

通过二进制安装Kubernetes cluster

硬件配置推荐

在选择节点数量时,需要考虑集群的用途,通常情况下建议至少使用 3 个节点 (APIServer),对于控制平面其他组件来说,通常最少为两个即可,因为HA架构中工作节点总是需要一个即可

要运行 apiserver 和 etcd,您需要一台具有 2 个内核和 2GB RAM 的 机器,用于中小型集群,更大规模集群可能需要更多的核心。工作节点必须有足够的资源来托管您的应用程序,并且可以有不同的配置。

etcd 硬件配置推荐

Kuberentes 中工作节点需要启动一个或多个 etcd 实例,通常官方推荐运行奇数个 etcd 实例,因为一个 3 实例时有两个活跃就具备了集群的 quorum ,同理五个实例时存在3个实例活跃即可,7=>4 等,通常集群规模不大于9,否则存在同步时的延迟。

集群的部署模式

  • 单独托管的 etcd 集群
  • master节点同台主机上托管 etcd 集群
  • 通过 kubeadm 生成的 etcd 集群

在这里我们使用二进制方式最小化部署,即 单节点的 etcd 集群,与同时为 control plane 与 worker 一体托管在单台物理/虚拟机之上的 Kubernetes 集群

题外话:kubeadm 和 二进制究竟有什么区别?

实际上 kubeadm 和 二进制本质上并没有什么区别,如果非要说存在区别的话,那么就是其 Procss Manager 不同,

  • 一个是由 systemd/system v 或其他操作系统维护的1 id的进程进行维护;
  • kubeadm 部署的 kubelet 将 由 kubelet 进程自己来监控,当 pod 崩溃时重启该 podkubelete 也无法对他们进行健康检查。静态 pod 始终绑定在某一个 kubelet,并且始终运行在同一个节点上,可以通过在 master 节点上查看 /etc/kubernetes/manifests 目录

配置证书

通常情况下,大家都知道安装 kubernetes 集群需要证书,但是不知道为什么需要证书,这里需要了解 Kubernetes 认证机制,也就是“用户”在kubernetes集群中被称为什么

Kubernetes 中用户被分为几类:

  • X.509 证书
  • 静态 Token 文件
  • Bootstrap Tokens
  • Service Account Tokens

kubernetes 使用了客户端证书方式进行认证,这是作为外部用户的一种方式,这些证书会被默认生成对应的内部用户,所以需要准备证书文件,在本文中一切准备文件都是由脚本生成,不做基础的配置讲解。

对于 X.509 证书,需要注意有几点

  • kubernetes 中,对于证书的用户,CN就是用户名,O 就是组织。
  • 对于证书认证来说,客户端证书,也就是说客户端必须在可信任列表中,也就是 subject_name 中允许的
  • 对于 Kubernetes 组件来说,通常情况下需要包含 Kubernetes API service 的所有名称(短域名+完整域名)
    • kubernetes
    • kubernetes.default
    • kubernetes.default.svc
    • kubernetes.default.svc.cluster
    • kubernetes.default.svc.cluster.local

如果你希望使用IP,而不是域名,也可以对证书中sub_name增加对应域名

对于组件间认证,目前 Kubernetes 中基本上组件间认证都有 kubeconfig 完成,而 kubelet,kube-controller-manager 这两个组件,由于提供的功能不同,可能存在其他的认证方式;例如 kubelet 所作的事情是 “监听Pod资源进行部署” 那么这个时候与 APIServer 通讯使用了 kubeconfig,在例如 kubelet 要被 APIServer 签发时,此时认证是使用的 bootstrap token 进行的,而这个kubeconfig 就是 签发后生成的内容,本质上来说,kubelet 没有客户端证书,只有token,而例如 kube-scheduler 是有客户端证书,但是需要生产 kubeconfig 文件

签发kubelet

给 kubelet 签发证书主要由两部分组成,一种是 kube-controller-manager 自动签发,一种是 kubectl certifcate approve 手动签发,这里就有必要知道 kubelet 的认证的流程:

  • kubelet启动时会找 kubeconfig ,如果没有进入下一部
  • 没有 kubeconfig,会使用 bootstraps 进行认证,此时会被颁发客户端证书
  • 在通过后,kubelet 会根据签发证书 生成 –kubeconfig 指定的 kubeconfig 文件
  • 下次后,kubelet 会根据这个 kubeconfig 同 Kubernetes API 进行 验证

了解了 kubelet 的签发过程,就明白在二进制部署时,为什么需要做一个 clusterrolebinding ?

因为Kubernetes API 为 kubelet 的 bootstraps-token 使用的是用户 system:node-bootstrapper ,所以要想让这个用户可以访问 API 资源,那么就需要为这个用户绑定上集群角色(用户/组),这就需要执行一个命令

bash
1
$ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

此时再去查看证书颁发,这时因为有权限访问 API 资源了, 所以获得了证书的被签发

bash
1
2
3
$ kubectl get  csr
NAME                                                   AGE     REQUESTOR             CONDITION
node-csr-a-MREQ1IybB0U5M8RP5FasSjckQOZiCoCYlf8ipDwx8   5m11s   system:bootstrapper   Pending

kube-dns 的部署

coredns部署

sh
1
2
3
4
$ mkdir coredns && cd coredns
$ wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/coredns.yaml.sed
$ wget https://raw.githubusercontent.com/coredns/deployment/master/kubernetes/deploy.sh
$ bash deploy.sh -i 10.96.0.10 -r "10.96.0.0/12" -s -t coredns.yaml.sed |kubectl apply -f - 

开启IPVS

内核开启IPVS功能否则降级

sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat > /etc/sysconfig/modules/ipvs.modules << EOF
#!/bin/bash
ipvs_mods_dir="/usr/lib/modules/$(uname -r)/kernel/net/netfilter/ipvs"
for i in \$(ls \$ipvs_mods_dir | grep -o "^[^.]*"); do
    /sbin/modinfo -F filename \$i >/dev/null 
    if [ \$? -eq 0 ]; then
    /sbin/modprobe -- \$i 
    fi 
done
EOF

or

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    cat > /etc/sysconfig/modules/ipvs.modules <<EOF
    #!/bin/bash
    ipvs_modules="ip_vs ip_vs_lc ip_vs_wlc ip_vs_rr ip_vs_wrr ip_vs_lblc ip_vs_lblcr ip_vs_dh ip_vs_sh ip_vs_fo ip_vs_nq ip_vs_sed ip_vs_ftp nf_conntrack_ipv4"
    for kernel_module in \${ipvs_modules}; do
        /sbin/modinfo -F filename \${kernel_module} > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            /sbin/modprobe \${kernel_module}
        fi
    done
    EOF
chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep ip_vs

Troubleshooting

apiserver报错 没有kubeappserver用户

text
1
Failed at step USER spawning  No such process
log
Mar  2 04:54:56 node02 systemd: controller-manager.service: main process exited, code=exited, status=1/FAILURE
Mar  2 04:54:56 node02 kube-controller-manager: Get http://127.0.0.1:8443/api/v1/namespaces/kube-system/configmaps/extension-apiserver-authentication: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"
Mar  2 04:54:56 node02 systemd: Unit controller-manager.service entered failed state.

Unable to connect to the server: net/http: HTTP/1.x transport connection broken: malformed HTTP resp - kevin_loving的博客 - CSDN博客

可能是启动单元文件有问题,手动启动后正常

text
1
2
Mar  2 09:44:51 node02 kube-controller-manager: W0302 09:44:51.672404   39926 client_config.go:554] error creating inClusterConfig, falling 
back to default config: unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined

node “xxxx” not found

如下面问题,可能原因为 kubelet 正式还未签发

text
1
2
3
4
5
Nov 14 16:31:03 master01 kubelet: E1114 16:31:03.677280    8587 kubelet.go:2270] node "master01" not found

380   19810 kubelet.go:2292] node "master-machine" not found
May 12 23:45:11 master-machine kubelet: E0512 23:45:11.415099   19810 kubelet.go:2292] node "master-machine" not found
May 12 23:45:11 master-machine kubelet: E0512 23:45:11.460097   19810 certificate_manager.go:434] Failed while requesting a signed certificate from the master: cannot create certificate signing request: certificatesigningrequests.certificates.k8s.io is forbidden: User "system:anonymous" cannot create resource "certificatesigningrequests" in API group "certificates.k8s.io" at the cluster scope

User “system:anonymous” cannot list resource xxx

原因为:kubelet 正式还未签发

text
1
k8s.io/client-go/informers/factory.go:135: Failed to list *v1.CSIDriver: csidrivers.storage.k8s.io is forbidden: User "system:anonymous" cannot list resource "csidrivers" in API group "storage.k8s.io" at the cluster scope

问题:"master01" is forbidden: User "system:anonymous"

原因:用户未授权

需要注意的是,这里使用的是 bootstrap 证书进行授权,所以绑定的用户必须为 bootstrap 证书授权的用户或组。

bash
1
$ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

The kube-apiserver has several requirements to enable TLS bootstrapping: Authenticating the bootstrapping kubelet to the system:bootstrappers group [2]

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@debian-template:~# kubectl get clusterrole system:node-bootstrapper -oyaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: "2024-11-23T04:26:57Z"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:node-bootstrapper
  resourceVersion: "54"
  selfLink: /apis/rbac.authorization.k8s.io/v1/clusterroles/system%3Anode-bootstrapper
  uid: 6f39c3aa-e46d-413f-8834-35c8832c328a
rules:
- apiGroups:
  - certificates.k8s.io
  resources:
  - certificatesigningrequests
  verbs:
  - create
  - get
  - list
  - watch

报错如下

text
1
2
3
master01 kubelet: E1114 17:16:33.581116    8559 kubelet.go:2270] node "master01" not found

failed to ensure node lease exists, will retry in 6.4s, error: leases.coordination.k8s.io "master01" is forbidden: User "system:anonymous" cannot get resource "leases" in API group "coordination.k8s.io" in the namespace "kube-node-lease"

容器内 dial tcp 10.96.0.1:443: connect: connection timed out

问题1: flannela访问 dial tcp 10.96.0.1:443: connect: connection timed out

问题2:open /run/flannel/subnet.env: no such file or directory

问题3: Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

解决:检查kube-proxy组件

text
1
Nov 14 17:30:04 master01 kubelet: E1114 17:30:04.097261    1160 kubelet.go:2190] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized
text
1
2
3
4
$ kubectl logs kube-flannel-ds-dvsv7 -n kube-system
....
E1114 23:22:20.984238       1 main.go:243] Failed to create SubnetManager: error retrieving pod spec for 'kube-system/kube-flannel-ds-dvsv7': Get "https://10.96.0.1:443/api/v1/namespaces/kube-system/pods/kube-flannel-ds-dvsv7": dial tcp 10.96.0.1:443: connect: connection timed out
$ 

kubectl get csr 显示No Resources Found的解决记录

问题:kubectl get csr 显示No Resources Found的解决记录

解决:需要先将 bootstrap token 文件中的 kubelet-bootstrap 用户赋予 system:node-bootstrapper 角色,然后 kubelet 才有权限创建认证请求

授予KUBE-APISERVER对KUBELET API的访问权限

kubelet启动启动时,--kubeletconfig 使用参数对应的文件是否存在 如果不存在--bootstrap-kubeconfig指定的kubeconfig文件向kube-apiserver发送CSR请求

kube-apiserver 收到 CSR 请求后,对其中的 token 进行认证,认证通过后将请求的用户设置为system:bootstrap:<Token ID>,组设置为 ,system:bootstrappers此操作称为Bootstrap Token Auth

默认这个用户和组没有创建CSR的权限,kubelet没有启动,错误日志如下:

text
1
Unable to register node "master-machine" with API server: nodes is forbidden: User "system:anonymous" cannot create resource "nodes" in API group "" at the cluster scope

解决方法是:创建一个集群角色绑定,绑定一个组:system:bootstrapper 和一个clusterrole system:node-bootstrapper

text
1
$ kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

测试授权

bash
1
2
3
4
5
6
7
kubectl create clusterrolebinding kubelet-bootstrap --clusterrole=system:node-bootstrapper --group=system:bootstrappers

system:serviceaccount:default:default

kubectl create clusterrolebinding firewalld-default --clusterrole=system:aggregate-to-admin --user=system:serviceaccount:default:default

system:aggregate-to-admin

kube flannel cant get cidr although podcidr available on node

text
1
2
3
E0729 06:56:09.253632       1 main.go:330] Error registering network: failed to acquire lease: node "master-machine" pod cidr not assigned
I0729 06:56:09.253682       1 main.go:447] Stopping shutdownHandler...
W0729 06:56:09.253809       1 reflector.go:436] github.com/flannel-io/flannel/subnet/kube/kube.go:403: watch of *v1.Node ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented the request from succeeding

参考:kube flannel cant get cidr although podcidr available on node 原因为 CIDR无法分配

network plugin is not ready: cni config uninitialized

bash
1
Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized 

没有安装网络插件,安装任意网络插件后恢复。

mvcc: required revision has been compacted

log
kube-apiserver: W1120 17:18:20.199950   70454 watcher.go:207] watch chan error: etcdserver: mvcc: required revision has been compacted

这里是etcd返回的错误,被apiserver视为警告,在注释中有这么一句话

If the context is “context.Background/TODO”, returned “WatchChan” will not be closed and block until event is triggered, except when server returns a non-recoverable error (e.g. ErrCompacted).

如果这个上下文返回WatchChan将在下次事件被触发前不会被关闭或阻塞,除非服务器返回一个ErrCompacted (不可恢复)

对于etcd 对 Revision 有如下说明

etcd对每个kv的revision 都会保留一个压缩周期的值,例如每5分钟收集一次最新revision,当压缩周期达到时,将从历史记录中后去最后一个修订版本,例如为100,此时会压缩,压缩成功会重置计数器,并以最新的revision和新的历史记录进行开始,压缩失败将在5分钟后重试--auto-compaction-retention=10 是配置压缩周期的 每多少个小时键值存储运行定期压缩。

如果最新的revision已被修订,etcd返回一个 ErrCompacted 表示已修订,此时表示为不可恢复状态

返回错误时表示这个watch被关闭,为了优雅的关闭chan,kubernetes会对这个watch错误进行返回,而 ErrCompacted 本质上不算错误

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
wch := wc.watcher.client.Watch(wc.ctx, wc.key, opts...)
for wres := range wch {
    if wres.Err() != nil {
        err := wres.Err()
        // If there is an error on server (e.g. compaction), the channel will return it before closed.
        logWatchChannelErr(err)
        wc.sendError(err)
        return
    }
    ...

Reference

[1] Maintenance

[2] kubelet-tls-bootstrapping