i007.cc

i007.cc

优先队列-降维打击

05.价值资料

k8s的服务器和客户端是怎么回事?k8s有几个进程,各个进程之间是什么关系?

这个问题正好可以拿来跟刚聊完的 etcd 做个对比——两者的拓扑模型是完全相反的两种设计哲学。etcd 集群内部是”全员对等、两两互联”的 mesh 结构;而 Kubernetes 恰恰相反,是一个严格的枢纽型(hub-and-spoke)结构:只有一个真正意义上的”服务器”,就是 kube-apiserver,其余所有组件全部是它的客户端,组件与组件之间几乎不存在直接通信,都要绕道 apiserver。这是 Kubernetes 整个架构里最核心的设计原则,理解了这一条,其他所有组件之间的关系就都能推出来了。

先数一下一个典型集群里实际跑着的进程。控制面(通常跑在 control-plane 节点上)这边有这么几个:

kube-apiserver,唯一的真服务器,对外暴露 Kubernetes 那套 REST 风格的 API;

etcd,我们前面详细聊过的存储后端;

kube-scheduler,专门负责调度决策;

kube-controller-manager,这个名字容易让人以为是”一个控制器”,实际上它是一个把几十个独立控制器(Deployment 控制器、ReplicaSet 控制器、Node 控制器、Job 控制器等等)打包编译进同一个二进制、作为同一个进程里的不同 goroutine 一起跑的”合集”,这么设计纯粹是运维上的简化(少装一个东西),逻辑上这几十个控制器彼此是独立的,只是共享了一个进程;如果集群跑在公有云上,通常还会有一个 cloud-controller-manager,同样是把跟具体云厂商相关的控制器(比如给 Service 类型 LoadBalancer 去调用云厂商 API 创建负载均衡器)打包成一个进程,思路跟 controller-manager 一样。

节点侧(每个 worker 节点上都会跑一份)有:

kubelet,我们之前详细聊过,负责把 Pod 真正落地成容器;

kube-proxy,负责根据 Service/Endpoint 信息在本地维护 iptables/ipvs 规则,实现集群内的服务发现和负载均衡;容器运行时(比如 containerd、CRI-O),严格说不算 Kubernetes 项目自己的组件,但每个节点必备,kubelet 通过 CRI 接口(本地 Unix Socket,不走网络)跟它对话。另外还有你天天在用的 kubectl,本质上也只是 apiserver 的一个普通客户端,跟 kube-scheduler、kubelet 在”是不是客户端”这件事上没有任何特殊之处,唯一区别是它是被人直接敲命令调用的,其他组件是常驻后台自己跑循环的。

现在看关系:kube-scheduler、kube-controller-manager、cloud-controller-manager、kubelet、kube-proxy、kubectl,这些全部、无一例外,都是 kube-apiserver 的客户端,都是通过我们之前反复提到的 Informer/Watch 机制去订阅 apiserver 上的资源变化,然后各自决定要不要采取行动、行动完了再把结果写回 apiserver。它们彼此之间没有任何直接的网络连接——kube-scheduler 做完调度决策,不会直接给 kubelet 打个电话说”这个 Pod 归你了”,它只是把 spec.nodeName 写回 apiserver(apiserver 再落到 etcd),kubelet 是完全独立地、自己去 watch apiserver 才发现”喔有个新 Pod 分配给我了”,这个发现过程跟 scheduler 完全脱钩,两者甚至可以运行在不同批次启动、互相都不知道对方存在,只要都能连上同一个 apiserver 就行。这种”谁都不直接找谁,都对着同一份共享状态各自反应”的模式,正是 Kubernetes 控制器模式(controller pattern)能够工作的根基,也是我们前面聊 Crossplane 的 reconcile loop 时讲的思路的源头——Crossplane 本质上就是照着 Kubernetes 自己这套内部机制的样子做出来的一个扩展。

而 etcd 这一边,只有 kube-apiserver 一个组件会直接连它,这是一条被严格遵守的边界(虽然技术上没人能拦着你直接拿 etcdctl 去改数据,但这么做会绕开 apiserver 的认证、鉴权、准入控制、校验这一整套逻辑,是明确不允许的做法)。所以从 etcd 的视角看,它眼里只有一个客户端,就是 apiserver(生产环境里 apiserver 通常会部署多个实例做高可用,这几个 apiserver 实例都各自独立连接同一个 etcd 集群,apiserver 之间彼此也不直接通信,都是分别去读写同一份 etcd 数据,通过 etcd 的强一致性来保证多个 apiserver 实例看到的是同一份数据)。

有几个例外情况打破了”只能是 apiserver 的客户端”这个规则,值得单独提一下,因为方向反过来了——变成 apiserver 主动去连别的组件:一个是你执行 kubectl execkubectl logskubectl port-forward 的时候,apiserver 会反过来主动发起连接,去连目标 Pod 所在节点上 kubelet 自带的一个小型 HTTPS 服务(默认监听 10250 端口),把数据流转发回给你的 kubectl,这是唯一一处 apiserver 变成客户端、kubelet 变成服务端的场景;另一个是准入 Webhook(Admission Webhook)和自定义 API 聚合(Aggregated APIServer,比如 metrics-server 这类扩展 API),apiserver 处理请求过程中会主动调用这些外部服务做校验或者转发处理,这些也是 apiserver 主动作为客户端连出去的场景,跟正常的”大家都是 apiserver 客户端”这个主流模式是反过来的特例。

所以一句话总结这套关系:Kubernetes 是严格的星型拓扑,apiserver 是唯一枢纽和唯一真服务器,其他所有常驻进程都只是订阅+写回这个枢纽的客户端,彼此互不直连;只有 apiserver 才有资格碰 etcd;少数几个特例(exec/logs/webhook)里方向反过来,apiserver 临时变成别人的客户端。这跟 etcd 集群内部”人人平等、两两互联、谁都可能变成 leader”的对等模型是两种完全不同的设计取向,一个是为了实现强一致的分布式共识,一个是为了让海量组件能够松耦合、独立演化、互不阻塞。

发表回复