k8s架构与核心概念
一、快速了解k8s
1.1 k8s 是什么
k8s,全称kubernetes,是一个跨宿主及管理容器的系统。容器的三大核心技术有名称空间、联合文件系统和cgroup。
Borg 系统(闭源,谷歌定制化的容器管理平台)--> kubernetes(Borg的开源版本),目前kubernetes没有长期稳定版本(LTS),一直在更新。在使用上,频繁升级可能会导致生产事故,这是kubernetes目前持续存在的问题。
kubernetes 用哪个版本(当前最新的是1.36版本)
- 百度公司:2021年1.18版本
- 百度公司:2023年1.2x版本

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节点/主节点
- master节点负责集群的调度、整套k8s集群的维护工作,是k8s集群的总控中心
- 高可用集群建议部署至少3台Master
- master节点通过与Slave节点不间断通信来维持整个集群的健康状态,集群中各个资源的状态信息存放于etcd中。若master节点不可用,则我们便无法管理k8s集群中的各种资源
- Master上部署的组件有apiserver、scheduler、controller-manager等管理组件,此外通常还需要部署etcd
slave节点/从节点/worker节点/node节点
- kubelet会把本节点的资源信息、pod的状态等信息汇报给master的apiserver组件,然后存入etcd中
- kubelet会调用容器引擎来创建容器
- 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有两个功能
- 把本节点的资源信息、pod状态上报给master节点的apiserver,然后被apiserver存入etcd中
- 接收来自master的创建pod的请求,然后调用容器引擎以及网络插件把pod创建完成
2、kubelet是怎么对接容器引擎的
- 在k8s1.24版之前,可以非常方便的对接docker容器引擎
kubelet –> docker shim (在 kubelet 进程中的shim垫片/桥梁) –> dockerd –> containerd - 在k8s1.24版之后,默认对接的是containerd容器引擎
kubelet –> cri plugin(在 containerd 进程中启动的cri-containerd) –> containerd
对接container的优点:
- 调用路径更短,效率更高
- 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的特点
- service与pod是动态关联的(挂掉的pod会自动剔除对应代理,新pod启动会自动添加对应代理):
kube-proxy负责维护service状态,基于标签来选中service。 - 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组件会经过两个阶段来完成物理节点的挑选。
- 预选(在工作节点中选出符合条件的)
- 污点/容忍
- 标签/选择器
- 优选(在预选结果中选出资源最优的)
(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服务器的负载。
- 客户端首先执行一次list操作,来查询初始的资源状态(全量查询)
- 然后通过watch操作来持续接收后续的更新信息(增量查询)
2、创建pod的完整流程(重点)
0. controller-manager组件、scheduler组件、kubelet组件都watch监听apiserver、监听自己的关注的资源状态。只要有更新,apiserver就会上报该更新给对应的组件。
- 客户端命令kubectl提交创建pod的请求(假如该pod的控制器采用Replicaset)
- apiserver收到该请求,会把创建replicaset的请求写入etcd
- 上报事件replicaset created
- controller-mamanger收到replicaset created事件,进入调谐阶段。controller-mamanger发现当前该pod是0副本,预期要创建1个副本。
- 所以controller-mamanger发起要创建一个pod的请求给apiserver
- apiserver将创建pod的事件存入etcd中
- apiserver上报Pod created事件
- scheduler组件收到Pod created事件(因为scheduler一直watch着该资源的变动)。scheduler经过预选与优选来选出一台合适的物理节点来创建新pod
- scheduler把调度结果(要在某一台物理节点上创建新pod)发送给apiserver
- 将事件存入etcd中
- 上报该事件
- 某一个物理节点上的kubelet会收到该事件,进入pod创建环节
- 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

浙公网安备 33010602011771号