k8s架构与核心概念

一、快速了解k8s

1.1 k8s 是什么

k8s,全称kubernetes,是一个跨宿主及管理容器的系统。容器的三大核心技术有名称空间、联合文件系统和cgroup。
Borg 系统(闭源,谷歌定制化的容器管理平台)--> kubernetes(Borg的开源版本),目前kubernetes没有长期稳定版本(LTS),一直在更新。在使用上,频繁升级可能会导致生产事故,这是kubernetes目前持续存在的问题。

kubernetes 用哪个版本(当前最新的是1.36版本)

  • 百度公司:2021年1.18版本
  • 百度公司:2023年1.2x版本

image-20260615084532654

1.2 为何要用 k8s

我们将从容器裸跑和云原生两个角度去探索k8s的意义。容器裸跑属于狭义维度、云原生属于广义维度。
狭义:为了跨主机管理容器,解决容器裸跑的诸多问题
广义:完善云原生方案,是云原生发展的必然

docker 是一个容器引擎,基于容器的三大核心技术(名称空间,cgroup, 联合文件系统)来创建容器。
docker 在管理容器上功能比较少。无法满足以下场景。于是诞生了k8s。

  • 监控容器的存活状态,挂死自动重启(容灾自愈机制)
  • 集群模式下,容器的合理调度
  • 容器跨宿主机进行网络通信
    kubernetes 本身并不会造容器,只是调用容器引擎来管理容器。

1.2.1 为解决容器裸跑的问题

需要k8s的原因:为了跨宿主机管理容器、解决容器裸跑的诸多问题(容灾自愈机制,大规模部署容器,扩缩容操作, 统一的配置管理中心,集群化成本高)。

  • 故障监控与自愈
  • 服务编排
  • 服务滚动升级和在线扩容能力
  • 配置管理
  • 完备的集群管理能力(智能负载均衡,跨主机通信,服务注册与发现,资源配额,自动调度,集群扩容)

1.2.2 为云原生而生

cloudNative:

  • cloud:云计算 -- 表示把资源统一管理形成一个资源池,一个资源池可以很大,但最终承载压力的还是具体的某一台物理机器,也就是说无法虚拟出一个比物理节点更大的机器出来。它提供的是资源池和循环利用。
  • native:应用设计之初就考虑到云环境,提升软件的质量(故障自愈、资源重复利用)

云原生4个核心技术:

  • 微服务:k8s一个service就可以运行一个微服务
  • devops:把开发运维凝成一股
    • dev:开发人员喜欢更新
    • op:运维人员喜欢稳定
  • CICD:有一套cicd平台,gitlab --- jenkins(流水线)--- 构建得到镜像 --- 镜像仓库harbor --- 部署到目标k8s环境
    • CI:持续集成
    • CD:持续交付
  • 容器:基本的承载载体

1.3 何时用 k8s

  • 公司是为服务架构,或者应用数量比较多的时候也可以用
  • 开发人员想要快速部署自己的新功能到测试环境进行验证
  • 降低硬件成本、提高资源使用率
  • 容器自动化管理、故障自愈、自动化部署等

不适用

  • 单体服务,没有高并发需求
  • 团队不适合变革

并不是上来就要用k8s,也许公司架构根本就不需要容器化,强制上只会适得其反。

二、k8s集群架构

master节点/主节点

  1. master节点负责集群的调度、整套k8s集群的维护工作,是k8s集群的总控中心
  2. 高可用集群建议部署至少3台Master
  3. master节点通过与Slave节点不间断通信来维持整个集群的健康状态,集群中各个资源的状态信息存放于etcd中。若master节点不可用,则我们便无法管理k8s集群中的各种资源
  4. Master上部署的组件有apiserver、scheduler、controller-manager等管理组件,此外通常还需要部署etcd

slave节点/从节点/worker节点/node节点

  1. kubelet会把本节点的资源信息、pod的状态等信息汇报给master的apiserver组件,然后存入etcd中
  2. kubelet会调用容器引擎来创建容器
  3. Slave节点上部署有kubelet、kube-proxy、容器引擎等工作组件。

三、k8s核心概念

首先要熟悉组件和资源这两种核心概念。

  • 组件:实际存在的软件
  • 资源:组件创建出的逻辑单位,例如namespace、pod、deployment、configmap、statefulset......

3.1 名称空间namespace

名称空间是一门技术,这门技术可以用在不同的地方。

  • 容器中用名称空间技术来隔离:pid、ipc、uts、mount、网络、user
  • k8s中用名称空间技术来把一系列资源隔离到一起(例如pod、deployment、configmap、statefulset)

k8s引入名称空间技术的好处

  • 当很多人共用一套k8s集群时,可以用namespace将彼此使用的资源隔离开,互不干扰
  • 一组资源被组织到一个namespace中后,更方便k8s进行统一性的管理配置,例如资源配额、访问控制等
    • Resource Quota限定一个名称空间总共能用多少资源
    • Limit range现在当前名称空间内,每个pod或者容器的最大资源使用量

3.2 pod

1、pod是什么?
pod是一种把相关的容器组织到一起的方式/单位,pod内可以组织一个或多个容器(准确的说一个pod内至少两个容器)
pod是k8s的最小工作/调度单位

2、为何要有POD?
为何要引入POD这种概念来组织一下容器,直接管理容器不好吗?
引入POD的好处

  • 1、封装,屏蔽底层容器的差异化
  • 2、POD内的多个容器共享网络名称空间、共享挂载卷。所以如果应用由多个容器构成,并且想要实现本地通信,也可能有共享存储的需求

提炼一下pod的两大特性:
1、pod内的多个容器共享网络(网络来自于pause容器)
2、pod内的多个容器共享该pod的存储卷

四、k8s核心组件

4.1 node节点上的组件

(1)容器引擎

k8s1.18版本以前还是使用docker作为容器引擎。
docker / docker-compose --> dockerd--> containerd--> containerd-shim--> OCI runtime (runc)--> container
k8s1.24以前:对接dockerd
k8s1.24以后:对接containerd

(2)kubelet

1、kubelet有两个功能

  1. 把本节点的资源信息、pod状态上报给master节点的apiserver,然后被apiserver存入etcd中
  2. 接收来自master的创建pod的请求,然后调用容器引擎以及网络插件把pod创建完成

2、kubelet是怎么对接容器引擎的

  1. 在k8s1.24版之前,可以非常方便的对接docker容器引擎
    kubelet –> docker shim (在 kubelet 进程中的shim垫片/桥梁) –> dockerd –> containerd
  2. 在k8s1.24版之后,默认对接的是containerd容器引擎
    kubelet –> cri plugin(在 containerd 进程中启动的cri-containerd) –> containerd

对接container的优点:

  1. 调用路径更短,效率更高
  2. k8s的kubelet版本更新迭代更便捷,直接对接cri-containerd即可
    3、kubelet的GC机制会负责定期清理工作节点上的镜像以及退出的容器

(3)kube-proxy组件 & service资源

1、kube-proxy
强调:kube-proxy不负责pod的网络构建,负责的是pod的负载均衡(智能负载均衡、4层),网络是由网络插件实现的
kube-proxy------------------》service资源(4层智能负载均衡)--> 通过标签/选择器 绑定pod。

2、service的特点

  1. service与pod是动态关联的(挂掉的pod会自动剔除对应代理,新pod启动会自动添加对应代理):
    kube-proxy负责维护service状态,基于标签来选中service。
  2. service底层基于ipvs(与lvs类似)实现流量的负载均衡,

4.2 master节点上部署的组件

(1)etcd分布式数据库

etcd分布式数据库:整个k8s的状态都存在etcd中,并不一定要部署在master节点上,可以单独部署。
etcd是一个分布式key-value存储数据库,内部采用raft算法(分布式强一致算法,保证数据一致的,类似的还有paxdos算法),这种算法的特点是,超过半数以上的etcd节点挂掉后整个etcd集群才不可用。
补充:etcd集群的性能决定了k8s集群的规模。etcd数据库的数据量大小是有限制的,会影响k8s集群的规模。node增多、master也要增多。

(2)apiserver

查询/操作k8s集群本质都是在管理etcd集群中的数据,在k8s中,只有apiserver可以访问操作etcd,其他组件想操作etcd都必须请求apiserver,所以因此apiserver是整个K8S 集群的入口。

(3)kube-scheduler

cheduler负责节点的调度,选择最适合运行新pod的服务器。创建pod时,scheduler组件会经过两个阶段来完成物理节点的挑选。

  1. 预选(在工作节点中选出符合条件的)
    • 污点/容忍
    • 标签/选择器
  2. 优选(在预选结果中选出资源最优的)

(4)kube-controller-manager

这里我们需要了解controller 和 controller-manager两个概念。

1、controller控制器
调谐:控制器负载管理维护pod,使其始终处于预期的状态,该过程称之为调谐(Reconcile)
关系:控制器---------------管理维护-------------pod的状态

控制器的种类:提供不同的功能

  • deployment
  • replicaset
  • statefulset
  • cronjob

补充:上述几个控制器都是用于管理pod,实际上不仅仅是pod资源,几乎每一种k8s的资源都有对应的控制器来负载管理维护,使其始终处于预期状态,完成自动化管理工作。

2、kube-controller-manager组件

k8s的内置控制器都是由该组件统一管理,控制器管理器与控制器的关系如下:

  • controller-manager组件------>创建多种控制器(deployment、statefulset、cronjob等)资源------> 管理POD状态
    详解:controller-manger会通过apiserver访问etcd,来监听每种控制器的状态,一旦发现控制器提交的当前状态不符合预期设定,就会根据该控制器的特性,启动调谐的动作。

4.3 客户端工具

kubectl / 或者api客户端

五、pod这一种资源的创建流程

1、储备知识:List-watch机制
k8s中各组件间协同都采用list-watch机制进行通信(所有的组件都是采用这种机制)。与简单的轮询全量信息相比,这种机制可以更有效地获取资源状态的更新,并减少了网络流量和APIserver服务器的负载。

  1. 客户端首先执行一次list操作,来查询初始的资源状态(全量查询)
  2. 然后通过watch操作来持续接收后续的更新信息(增量查询)

2、创建pod的完整流程(重点)
0. controller-manager组件、scheduler组件、kubelet组件都watch监听apiserver、监听自己的关注的资源状态。只要有更新,apiserver就会上报该更新给对应的组件。

  1. 客户端命令kubectl提交创建pod的请求(假如该pod的控制器采用Replicaset)
  2. apiserver收到该请求,会把创建replicaset的请求写入etcd
  3. 上报事件replicaset created
  4. controller-mamanger收到replicaset created事件,进入调谐阶段。controller-mamanger发现当前该pod是0副本,预期要创建1个副本。
  5. 所以controller-mamanger发起要创建一个pod的请求给apiserver
  6. apiserver将创建pod的事件存入etcd中
  7. apiserver上报Pod created事件
  8. scheduler组件收到Pod created事件(因为scheduler一直watch着该资源的变动)。scheduler经过预选与优选来选出一台合适的物理节点来创建新pod
  9. scheduler把调度结果(要在某一台物理节点上创建新pod)发送给apiserver
  10. 将事件存入etcd中
  11. 上报该事件
  12. 某一个物理节点上的kubelet会收到该事件,进入pod创建环节
  13. kubelet会调用容器引擎以及网络插件创建出pod
  • (1)kubelet会先调用容器引擎创建出一个pause容器
  • (2)然后kubelet会调用网络插件来把pause容器的网络打通
  • (3)kubelet会调用容器引擎来创建出业务容器,并且业务容器采用的container网络模式与pause容器共享网络

补充:关于kubelet如何对接容器引擎的详细如下:
kubelet对接docker引擎,对接流程:kubelet –> docker shim (在 kubelet 进程中) –> dockerd –> containerd
kubelet对接containerd引擎,对接流程:kubelet –> cri plugin(在 containerd 进程中) –> containerd

六、k8s的核心机制list-watch

6.1 list-watch机制

k8s是声明式系统,用户提交期望状态spec,而不是一步步的指令,真正干活的是一堆控制器。这些控制器需要回答某个资源现在的实际状态是什么,期望状态是什么,差多少,怎么调谐;这就要求控制器/组件能够持续感知资源变化,但又不能靠高频轮训把apiserver/etcd打爆,于是k8s选择list-watch机制。

  • list:一次性拿到某个资源的全量状态,提供对齐能力。
  • watch:在全量状态的基础上,持续监听增量的事件(增删改),提供实时性。

6.2 list-watch是如何实现的

List:获取某一时刻的全量状态快照,可以是一次或多次http请求,最终会结束;目的是为了对其基线,而非一直占用连接。
watch:获取持续流式的增量事件,是长生命周期的连接(长连接上的chunked流),事件一旦发生就推给客户端,低延迟。
现实里网络会抖、apiserver 会重启、etcd compaction 会把老版本清理掉。于是 K8s 规定了一个硬性契约

  • 服务端只在一定窗口期内保留历史变更(常见为5分钟)
  • 当watch一个太旧的状态时,apiserver会返回http 410。
  • 客户端收到410会清空本地缓存,重新list对齐,获取全量状态,再打开新的watch。
    即组件会定期发起list操作来获取最新的全量状态,弥补watch的不足。

6.3 list-watch设计理念

(1)消息可靠性

消息可能丢,但系统能自愈补齐。

  • list+watch:正常路径推送
  • 410+relist:一旦历史不够续传,就重新对其
    可靠性不是靠tcp协议保送,而是靠list快照+游标+relist重对齐等机制实现的。

(2)消息实时性

消息是由事件驱动,不是即时的。

  • 写请求在etcd提交之后,事件就会推往匹配的watch流,客户端几乎实时收到增删改事件。
  • 但它是异步流,存在极小的不一致的窗口。
  • delete也会产生事件。

(3)消息顺序性

每个对象/列表/事件都带 metadata.resourceVersion;同一类资源类型事件按照rv单调递增,客户端只需要记住处理到那个rv,就可以判断新旧事件。
客户端还会把同一个对象短时间内的多次变更合并,只看对象的最新状态,而不是死磕计数那种严格按照顺序逐条执行命令。

(4)高性能

  • etcd前面挡了一个watch cache,watch请求并非直接穿透到etcd,list有时也可能从cache走,带来了高性能。
  • rv的事件标记可以减少无效的relist。
  • watch有失效机制,过期410。
  • 同一个进程中多个控制器共享一个watch连接,不是每个开一条。对象多次连续更改会被合并,只拿最新状态。

补充:http1.1协议的churnk分片发送知识
watch机制长连接:使用率chunked的帧机制持续读取。解决了如何在不知道总长度时把字节流安全切成帧,让接收方能够精确识别边界。
apiserver不用把全部事件攒齐,而是来一个事件--> 序列化--> 当成一个chunked推出去--> flush;连接不断。

小练习:通过curl模拟watch

	$ kubectl proxy

	# 进行listwatch default名称空间下的pods

​	$ curl "127.0.0.1:8001/api/v1/namespaces/default/pods?watch=true"

	# list出defaullt名称空间下的pods

​	$ curl "127.0.0.1:8001/api/v1/namespaces/default/pods"

	# 创建pod进行观察

​	$ kubectl run nginx --image=nginx

posted @ 2026-06-22 12:43  cuckooyang  阅读(19)  评论(0)    收藏  举报