kubernetes架构

架构

kubernetes集群一般分为两个部分:

  • kubernetes控制平面(主节点)
  • 工作节点

主节点的组件

控制平面负责控制并使整个集群正常运转,其中包含如下组件:

  • etcd分布式持久化存储
  • API服务器
  • 调度器
  • 控制器管理器

这些组件用来存储、管理集群状态。

工作节点上运行的组件

  • Kubelet
  • Kubelet服务代理(kube-proxy)
  • 容器运行时(docker、containerd、rkt等) 运行容器时依赖于工作节点上的这些组件

kubernetes组件的分布式特性

检查控制平面的状态

API服务器对外暴露了一个名为ComponentStatus的API资源,用来显示每个控制平面组件的健康状态。可以通过kubelet命令展示各个组件的状态

1
2
3
4
5
6
[root@VM-20-9-centos kubelet.service.d]# kubectl get componentstatus
Warning: v1 ComponentStatus is deprecated in v1.19+
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy ok

组件间的通信

kubernetes组件只通过api-server进行通信,api-server是和etcd通信的唯一组件。其它组件不会和etcd通信,而是通过与api-server通信来修改集群状态。

当api-server与其它组件连接时,通常是由后者发起的,当使用kubectl查看日志、获取信息、连接容器或使用port-foward映射端口时,api服务器会向kubelet发起连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl get pod -o custom-columns=POD:metadata.name,NODE:spec.nodeName --sort-by spec.nodeName -n kube-system
POD NODE
kube-proxy-npg98 hcss-ecs-20b5
weave-net-ns7vk hcss-ecs-20b5 #每个工作节点都运行着一个kube-proxy pod和weave网络插件pod
weave-net-b4tk5 hdbgp-7682125498
kube-proxy-rtqlk hdbgp-7682125498
coredns-5b866bc4d9-k2whr hdbgp-7682125498
weave-net-jxg2t iz2zeiagx6ykthgr48snf2z
kube-proxy-strh7 iz2zeiagx6ykthgr48snf2z
kube-proxy-9mt2f vm-20-9-centos
etcd-vm-20-9-centos vm-20-9-centos
grafana-b75b975b-vj52p vm-20-9-centos
kube-scheduler-vm-20-9-centos vm-20-9-centos
kube-controller-manager-vm-20-9-centos vm-20-9-centos
kube-apiserver-vm-20-9-centos vm-20-9-centos # etcd、api-server、控制器管理器、调度器运行在master上
weave-net-wrcxg vm-20-9-centos
kube-proxy-nmpz6 vm-20-12-centos
coredns-5b866bc4d9-585pw vm-20-12-centos
weave-net-ndf5j vm-20-12-centos

如上命令所见,控制平面的组件在主节点上以pod运行,四个工作节点运行kube-proxy pod和weave网络插件pod,用来为pod提供重叠网络。

如上命令所示,可以通过-o custom-columns 选项自定义展示的列信息以及–sort -by 对列表进行排序

 Nodes、Pods、Services、Secrets 等所有 API 对象都会存储到etcd中,这样它们的manifest在API服务器重启和启动失败的时候保证数据不会丢失

唯一能和etcd通信的是kuberenetes的API服务器。其他组件通过API服务器间接读取、写入数据到etcd。这带来的好处就是增强乐观锁系统、验证系统的健壮性;并且,通过把实际存储机制从其他组件剥离,未来替换起来也容易。etcd是kuberenetes存储集群状态和元数据的唯一的地方

关于乐观并发控制

乐观并发控制是指一段数据包含一个版本数字,而不是锁住该段数据并阻止读写操作。每当更新数据,版本数就会增加。当更新数据时,就会检查版本是否在客户端读取数据时间和提交时间之间被增加过。如果增加过,那么更新会被拒绝,客户端必须重新读取新数据,重新尝试更新。

两个客户端尝试更新同一个数据条目,只有第一个会成功。

所有的Kubernetes包含一个metadata.resourceVersion字段,当更新对象时,客户端需要返回该值到API服务器。如果版本值与etcd中存储的不匹配,API服务器会拒绝该更新

资源如何存储在etcd中

kubernetes存储所有数据到etcd的/registry下

1
2
3
4
5
6
7
8
9
10
11
12
13
#  etcdctl --endpoints=https://127.0.0.1:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt  --key=/etc/kubernetes/pki/etcd/peer.key get /registry --prefix=true --keys-only

/registry/daemonsets/kube-system/weave-net
......
/registry/namespaces/default
......
/registry/persistentvolumeclaims/default/elasticsearch-pvc
......
/registry/services/endpoints/default/test-nodeport
......
/registry/roles/kube-system/kube-proxy
......
/registry/serviceaccounts/kube-node-lease/default

从获取到的key名称可以看出,pod是按照命名空间存储的

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
37
38
#  etcdctl --endpoints=https://127.0.0.1:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt  --key=/etc/kubernetes/pki/etcd/peer.key get /registry/pods --prefix=true --keys-only


/registry/pods/default/elasticsearch-6dcfb5b797-2jgdj
/registry/pods/default/filebeat-678fd9f686-r9v49
/registry/pods/default/kafka-0
/registry/pods/default/kafka-1
/registry/pods/default/kafka-2
/registry/pods/default/logstash-57f8d5586f-2whhm
/registry/pods/default/zookeeper-0
/registry/pods/default/zookeeper-1
/registry/pods/default/zookeeper-2
/registry/pods/kube-system/coredns-66f779496c-5s8cq
/registry/pods/kube-system/coredns-66f779496c-mfgfc
/registry/pods/kube-system/etcd-vm-20-9-centos
/registry/pods/kube-system/grafana-589b99c778-xjjcx
/registry/pods/kube-system/kube-apiserver-vm-20-9-centos
/registry/pods/kube-system/kube-controller-manager-vm-20-9-centos
/registry/pods/kube-system/kube-proxy-2fqxz
/registry/pods/kube-system/kube-proxy-42fhs
/registry/pods/kube-system/kube-proxy-f48r9
/registry/pods/kube-system/kube-proxy-gvgqz
/registry/pods/kube-system/kube-proxy-hr8nb
/registry/pods/kube-system/kube-scheduler-vm-20-9-centos
/registry/pods/kube-system/weave-net-24d5n
/registry/pods/kube-system/weave-net-fxphr
/registry/pods/kube-system/weave-net-lbbkx
/registry/pods/kube-system/weave-net-n7wqf
/registry/pods/kube-system/weave-net-wgn52
/registry/pods/monitor/kafka-exporter-7c7f55f6d8-79sxv
/registry/pods/monitor/kube-state-metrics-cbbffddbf-78xxs
/registry/pods/monitor/node-exporter-2gbr9
/registry/pods/monitor/node-exporter-hmtdn
/registry/pods/monitor/node-exporter-hwg8b
/registry/pods/monitor/node-exporter-jx4vz
/registry/pods/monitor/node-exporter-px66j
/registry/pods/monitor/prometheus-server-649ddb4cbd-6t4nq

API服务器做了什么

API服务器作为中心组件,其他组件或客户端(kubectl)都会去调用它。以RESTful API的形式提供可以查询、修改集群状态的CRUD(Create、Read、Update、Delete)接口。它API将状态存储到etcd中。

当以JSON文件创建一个资源,kubectl通过一个HTTP POST请求将文件内容发布到API服务器。

通过认证插件认证客户端

首先,API服务器需要认证发送请求的客户端。这是通过API服务器中的认证插件来实现的。API服务器会轮流调用这些插件,直到有一个插件确认是谁发送了请求。

通过客户端证书或者HTTP标头获取用户信息。抽取其中客户端的用户名、用户ID和归属组。

通过授权插件授权客户端

通过授权插件决定第一阶段认证的用户是否可以对请求资源进行请求操作。例如,使用kubectl创建pod时,API会轮询所有的授权插件,确认该用户是否有权限可以在请求命名空间创建pod,一旦插件确认了用户可以执行该操作,API会继续执行下一个操作。

通过准入插件修改资源请求

如果请求尝试创建、修改或者删除一个资源,请求需要经过准入控制插件的验证。API会配置多给准入控制插件。这些插件会因为各种原因修改资源,可能会初始化资源定义中漏配的字段为默认值甚至重写它们。插件甚至会去修改并不在请求中的相关资源,同时也会因为某些原因拒绝一个请求。资源要经过所有准入控制插件的验证。

如果请求只是尝试读取数据,则会绕过准入控制插件的验证

准入控制插件包括但不限于:

  • AlwaysPullImages——重写pod的imagePullPolicy为always,强制每次部署时拉取镜像。
  • ServiceAccount——未明确定义服务账户的使用默认账户。
  • NamespaceLifecycle——防止在命名空间中创建正在被删除的pod,或在不存在的命名空间中创建pod。
  • ResourceQuota——保证特定命名空间的pod只能使用该命名空间分配数量的资源,如CPU和内存。

通过kubeapiserver的配置文件中就通过属性开启准入控制器

1
2
3
# grep -i 'admission' /etc/kubernetes/manifests/kube-apiserver.yaml 
- --enable-admission-plugins=NodeRestriction

查看哪些插件是默认启用的

1
2
# kubectl exec -it kube-apiserver-vm-20-9-centos -n kube-system -- kube-apiserver -h | grep 'enable-admission-plugins strings'
--enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, ClusterTrustBundleAttest, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, ClusterTrustBundleAttest, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.

请求通过了所有的准入控制插件后,API服务器会验证存储到etcd的对象,然后返回一个响应到客户端。

API服务器如何通知客户端资源变更

客户端通过创建到API服务器的HTTP连接来监听变更。通过此连接,客户端会接收到监听对象的一系列变更通知。每当更新对象时,服务器把新版本对象发送至所有监听该对象的客户端。

kubectl作为API服务器的客户端之一,也支持监听资源。可以使用–watch,每当pod被创建、修改、删除时会通知你。

例如,当删除一个pod node-exporter-2gbr9时,pod会被销毁,控制器会重新创建一个pod以满足副本数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl get pod -n monitor --watch
NAME READY STATUS RESTARTS AGE
alertmanager-6d4bbb66fb-d5pmg 1/1 Running 0 8d
kafka-exporter-6cbf9774d5-47psj 1/1 Running 0 9d
kube-state-metrics-cbbffddbf-9mk52 1/1 Running 0 10d
node-exporter-2gbr9 1/1 Running 0 37d
node-exporter-hmtdn 1/1 Running 0 37d
node-exporter-hwg8b 1/1 Running 1 36d
node-exporter-jx4vz 1/1 Running 0 37d
node-exporter-px66j 1/1 Running 0 37d
prometheus-server-649ddb4cbd-6t4nq 1/1 Running 0 37d
node-exporter-2gbr9 1/1 Terminating 0 37d
node-exporter-2gbr9 0/1 Terminating 0 37d
node-exporter-fvpwg 0/1 Pending 0 0s
node-exporter-fvpwg 0/1 Pending 0 0s
node-exporter-fvpwg 0/1 ContainerCreating 0 0s
node-exporter-2gbr9 0/1 Terminating 0 37d
node-exporter-2gbr9 0/1 Terminating 0 37d
node-exporter-fvpwg 1/1 Running 0 3s

调度器

默认的调度算法

选择节点操作可以分解为两部分:

  • 过滤:过滤所有节点,找出能分配给Pod的可用节点列表。
  • 打分:对可用节点按优先级排序打分,kube-scheduler 会将 Pod 调度到得分最高的节点上。 如果存在多个得分最高的节点,kube-scheduler 会从中随机选取一个。

查找可用节点

为了过滤哪些节点对Pod可用,调度器会给每个节点下发一组配置好的预测函数:

  • 节点是否能满足pod对硬件资源的请求
  • 节点是否耗尽资源
  • pod是否要求被调度到指定节点
  • 节点是否有和pod规格定义里的节点选择器一致
  • 如果pod有要求绑定主机的指定端口,节点上的端口是否被占用
  • 如果pod要求有特定类型的卷,该节点是否能为此pod加载卷,或者节点中的该卷已被其他pod使用
  • pod是否容忍节点的污点
  • pod是否定义了节点、pod的亲和性和非亲和性规则

当这些策略都通过时,节点才有资格调度给pod。在对每个节点做过这些检查后,调度器得到节点集的一个子集。在子集中的任何节点都可以运行该pod。

控制器

单个控制器、管理器进程当前组合了多个执行不同非冲突任务的控制器。这些控制器最终会被分解到不同的进程,如果需要的话,能够用自定义实现替换它们每一个。控制器包括:

  • RepliSet、DaemonSet以及Job控制器
  • Deploymnet控制器
  • StatefulSet控制器
  • Node控制器
  • Service控制器
  • Endpoints控制器
  • Namespace控制器
  • PersistentVolume控制器
  • 其他

每个控制器的作用通过名字显而易见。通过上述列表,可以知道每个资源对应的控制器是什么。资源描述了集群中应该运行什么,而控制器就是活跃的Kubernetes组件,去做具体工作部署资源。

kubelet

kubelet负责所有运行在工作节点上内容的组件。它第一个任务就是在API服务器中创建一个Node资源来注册该节点。然后需要持续监控API服务器是否把pod分配给该节点,然后通知容器运行时启动pod容器。随后kubelet持续监控运行的容器,向api服务器报告它们的状态、事件和资源消耗。

Kubelet也是运行容器存活探针的组件,当探针报错时它会重启容器。当pod从API服务器删除时,Kubelet终止容器,并通知API服务器pod已经被终止了。

kube-proxy

kube-proxy运行在每个Node节点上,确保service ip和端口的连接最终能到达某个pod。如果服务对应有多个Pod,kube-proxy还发挥对pod负载均衡的作用。

kube-dns

集群中的所有Pod默认配置使用集群内部DNS服务。这样Pod能够名称查询到服务。

DNS服务pod通过kube-dns服务对外暴露。服务的IP地址在集群每个容器的/etc/resolv.conf文件的nameserver定义,kube-dns pod利用API服务器的监控机制订阅Service和Endpoint的变动。

资源的完整调度过程

当部署一个deployment资源时,通常通过Kubectl POST提交到API服务器。API检查deployment的定义,存储到etcd中。并返回结果给kubectl。

Deployment控制器生成ReplicaSet

当创建Deployment资源时,Deployment控制器会接收到通知,按照Deployment定义创建ReplicaSet。API服务器会创建一个新的ReplicaSet。

ReplicaSet控制器创建Pod资源

ReplicaSet控制器会接收到新创建的ReplicaSet,控制器会考虑Replica数量、ReplicaSet中定义的Pod选择器,检查是否有足够满足选择器的Pod。

ReplicaSet控制器会基于ReplicaSet的pod模板创建pod资源,当Deployment控制器创建ReplicaSet时,会从Deployment复制pod模板。

调度器给新创建的Pod分配节点

新创建的pod模板会保存在etcd中,它们还缺少关联节点的属性,调度器会监控这种Pod,一旦发现,就会为Pod选择最佳节点。并将nodeName的属性分配给pod,pod的定义就会新增一个它应该运行在哪个节点的属性。

kubelet运行Pod容器

此时节点还没有启动容器,容器的镜像也没有下载。当pod关联了相应的节点,kubelet通过API服务器监听pod变更,发现有新的pod分配到本节点,会检查pod定义,然后命令docker或containerd或其他容器运行时来启动Pod容器,容器运行时就会运行容器。