irpas技术客

K8s 前世今生与架构组件简析_一切如来心秘密_k8s前身

网络投稿 6910

k8s 前世

k8s 的前世是google 内部的Borg 系统,是一个作业调度平台,调度的对象是一个个进程。 Borg 本身也利用了容器化技术比如 Cgroups, Namespace 实现应用的隔离。

运行在线上的业务主要分为在线业务(prod)和离线业务(non-prod, Batch),比如上边的 Gmail,Google Docs 和 Web Search 就是离线业务,中间的 MapReduce 等业务就是离线业务,大数据的批处理业务一般都是离线业务。

在线业务就要求服务的稳定性,高可用,对资源的消耗一般不高,离线业务对资源的要求比较高,比如说AI 模型训练等,但是离线业务对可用性要求相对于在线业务不高,比如说一个批处理业务跑失败了,可以重试。

Borg 就是整合了整个数据中心的离线业务和在线业务,来进一步提高资源的利用率。

Borg 简介 特性:

物力资源利用率高。服务器共享,在进程级别做隔离。应用高可用,故障恢复时间短。调度策略灵活。应用接入和使用方便,提供了完备的Job 描述语言,服务发现,实时状态监控和诊断工具。

优势:

对外隐藏底层资源管理和调度、故障处理等。实现应用的高可靠和高可用。足够弹性,支持应用跑在成千上万的机器。 Borg 基本概念

Workload

prod: 在线任务non-prod: 离线任务

Cell

一个Cell 就是一个集群Cell 对服务器资源进行了抽象,用户在使用Borg 的时候无需关心资源分配,程序安装,依赖管理、健康检查及故障恢复。

Job 和 Task

Job 是作业的的描述对象,用户可以通过Job 定义属性、元信息和优先级,优先级涉及到抢占式调度过程。一个Job 可以包含多个相同的Task, 每一个Task 运行相同的应用程序,Task 数量就是应用的副本数。

Naming

Borg 的服务发现通过BNS(Borg Name Service) 来实现。50.jfoo.ubar.cc.borg 可表示在一个名为cc的Cell中由用户ubar部署的一个名为jFoo的Job下的第50个Task。 Borg 架构

Borg 的架构是比较经典的Master-Worker 加 Scheduler 调度的简洁架构。

Borgmaster 主进程

处理客户端RPC请求,比如创建Job, 查询Job 等。维护系统组件和服务的状态,比如服务器、Task等。负责与Borglet 通信。

Scheduler 进程

调度策略

Worst Fit,将任务调度到集群资源利用率最低的节点运行,目的就是使提高整个集群各个节点的资源利用率Best Fit,根据任务指定的资源情况,分配到剩余资源最合适(最接近指定资源量)的节点,比如说一个任务需要2个CPU,刚好有个机器只剩下2个多CPU资源,那么这个任务就会被分配到该机器运行。 这样就会导致很多任务都运行在一个节点上,那么就会出现有的节点很忙,有的节点很闲,这样就意味着可以将空闲的节点移除,降低资源成本。Hybrid,混合模式。

调度优化

Score caching: 当服务器或者任务的状态未发生变更或者变更很少时,直接采用数据缓存,避免重复计算。Equivalencecalsses: 调度同一个Job 下多个相同的Task 只需要计算一次。Relaxed randomization: 引入一些随机性,即每次随机选择一些机器,只要符合需求的服务器数量达到一定值时,就可以停止计算,无需每次对Cell 中所有服务器进行feasibility checking.

Borglet

Borglet 是部署在所有服务器上的Agent, 负责接收master 进程的指令。

应用高可用

被抢占的离线任务放回pending queue ,等待重新调度。多副本应用跨故障域部署。对于类似服务器或操作系统升级的维护操作,避免大量服务器同时进行。支持幂等性,支持客户端重复操作。当服务器状态变为不可用时,要控制重新调度任务的速率。因为Borg 无法区分是节点故障还是出现了短暂的网络分区,如果是后者,等待网络恢复更利于保障服务可用性。当某种“任务 @ 服务器”的组合出现故障时,下次重新调度时需避免这种组合再次出现,因为极大可能会再次出现相同故障。记录详细的内部信息,便于故障排查和分析。保障应用高可用的关键性设计原则:无论何种原因,即使Borg 进程挂掉、失联,都不能杀死正在运行的服务(Task)。

Borg 系统自身高可用

Borgmaster 组件多副本设计。采用一些简单的和底层的工具来部署Borg 实例,避免引入过多的外部依赖。每个Cell的Borg 独立部署,避免不同Borg 系统相互影响。

资源利用率

通过将在线任务和离线任务混合部署,空闲时,离线任务可以充分利用计算资源;繁忙时,在线任务通过抢占的方式保证优先得到执行,合理地利用资源。夜间用户使用在线任务比较少,资源空闲多,所以一般将离线任务在夜间运行。

隔离性

安全性隔离 早期使用Chroot jail, 后期版本基于 Namespace 性能隔离 采用基于Cgroup 的容器技术实现。在线任务是延时敏感型的,优先级高,而离线任务优先级低。通过不同优先级之间的抢占式调度来优先保障在线业务任务的性能,牺牲离线任务。Borg 将资源分为两类: 可压榨资源,CPU是可压榨资源,资源耗尽不会终止进程。 不可压榨资源,内存是不可压榨资源,资源耗尽进程会被终止。 Borg 调度原理

作为开发人员除了编写健壮的代码逻辑,还希望自己的服务能够在资源足够的环境下运行,这个足够资源就难以判断,一般是通过压测来决定:

比如说我的服务目标是实现qps 最少为10,为了保证峰值资源足够,那么就以100作为基准进行压测,在申请服务器资源的时候就以10倍申请,这样在大多数空闲的时候,是极度浪费的。

针对这种情况,Borg做了优化,Borg 的调度在Task 申请资源的时候,并不会直接按照用户所说的资源大小去申请,而是会计算出实际使用的资源,然后再预留一点资源防止峰值,最后将剩下的多的资源回收。 这样在使用Borg 的时候,Task的资源就是动态变化的,而无需担心资源的浪费。

什么是kubernetes(K8S)

k8s 是google 基于 Borg 开源的容器集群管理系统,主要功能包括:

基于容器的应用部署、维护和滚动升级负载均衡和服务发现跨机器和跨地区的集群调度自动伸缩无状态服务和有状态服务插件机制保证扩展性

系统一般分为声明式系统和命令式系统,命令式系统关注如何做,声明式系统关注做什么:

比如说使用电视遥控器切换上下台,调节音量大小就是命令式系统,你要告诉电视如何做。 使用空调遥控器将温度设置为25度,就是声明式系统,你不用关心空调该如何做,而是告诉空调做什么。

声明式包括直接声明与间接声明,直接声明就是告诉你我需要什么,间接声明就是不直接告诉你我的需求,我会把我的需求放在特定的地方,请在方便的时候拿出来处理。声明式系统的好处就是幂等性,状态固定,每次我要你做事,请给我返回相同结果。声明式系统是面向对象的,把一切抽象成对象。

k8s 中的声明式 k8s 的所有管理能力构建在对象抽象的基础上,核心对象包括:

Node: 计算节点的抽象,用来描述计算节点的资源抽象、健康状态等。Namespace:资源隔离的基本单位,可以简单理解为文件系统中的目录结构。Pod: 用来描述应用实例,包括镜像地址,资源请求等。k8s 中最核心的对象,也是打通应用和基础架构的秘密武器。Service:服务如何将应用发布成服务,本质上是负载均衡和域名服务的声明。 k8s 架构

k8s 采用与Borg 类似的架构,都是主从结构,每个worker 上都有一个 Kubelet用来管理worker 节点,master 也只能通过kubelet 与 worker 交互。

用户与K8s的交互以及k8s 间各组件间的交互都需要通过API Server, master 与 worker 节点都不能直接与etcd 交互,也只能通过API Server。

包括的主要组件如下: master 节点包括认证鉴权模块, REST API模块,scheduler 模块,controller 模块, 分布式存储模块etcd。

用户的所有请求都只能通过该服务的REST API与k8s 交互。k8s 的元数据存储使用的是分布式存储服务etcd, 这是一个强大的、稳定的、高可用的键值存储。kube-controller manager 控制管理器,运行着所有处理集群日常任务的控制器。包括了节点控制器、副本控制器、端点控制器以及服务账户等。调度器Scheduler,会监控新建的pods(一组或一个容器)并将其分配给节点。

node 节点包括kubelet 模块,Proxy 模块,以及核心Pod 模块和运行在Pod 中的容器。

kebelet 负责调度到对应节点的Pod 的声明周期管理,执行任务并将 Pod 状态报告给主节点的渠道,通过容器运行时(拉取镜像、启动和停止容器)来运行这些容器。还会定期执行被请求的容器的健康探测程序。Kube-proxy 负责节点的网络,在主机上维护网络规则并执行连接转发。还负责对正在服务的 pods 进行负载均衡。 etcd

etcd 是基于 Raft 协议开发的分布式k-v 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。

基本的k-v 存储监听机制key 的过期及续约机制,用于监控和服务发现原子CAS 和 CAD, 用于分布式锁和 leader 选举

关于etcd 的操作,官方文档讲解的很清楚:https://etcd.io/docs/v3.3/demo/

API Server

kube-APIServer 是k8s 最重要的核心组件之一,主要提供以下功能:

提供集群管理的REST API 接口,包括: 认证:Authentication,校验身份是否合法 授权:Authorization,校验是否有权限 准入:Admission(Mutating & Valiating),校验是否运行执行该动作, 鉴权是层层递进的,当执行一个动作时,有权限不代表该动作在当时是合法的。提供其他模块之间的数据交互和通信的枢纽(其他模块通过APIServer 查询或修改数据,只有APIServer 才可以直接操作etcd)APIServer 提供etcd 数据缓存以减少集群对etcd 的访问。

API Server 展开: API Server 没有太多的逻辑,主要就是对各个请求进行权限验证:

APIHandler: 每一个请求都会对应一个handler 处理AuthN: 认证,这里的认证可以使用外部认证服务(AuthService),这样就可以和企业的一些自己的认证相结合。Rate Limit: 限流Auditing: 审计,会对请求进行log 记录,为了日后的安全审查AuthZ: 这里使用的是RBAC进行权限校验,可以使用k8s 自身的也可以使用外部的鉴权服务。Aggregator: APIServer 接收的是REST API,它可以像Nginx 一样将不同的请求路由的不同的服务,这里支持用户自定义一些APIServer验证逻辑,当走到 Aggregator 时,如果你的请求需要走自定义的那么 Aggregator 就会将请求转发到 Aggregated APIServer 然后最终请求到达 etcd。Mutating: 对请求参数进行变形封装,方便后边的校验Validating: 对请求参数进行校验 Controller Manager

Controller Manager 是集群的大脑,是确保整个集群动起来的关键,会监控APIServer。

确保k8s 遵循声明式系统规范,确保系统的真实状态与用户的期望状态一致。Controller Manager 是多个控制器的组合,每个 Controller 都是一个loop, 负责监听其管控的对象,当对象发生变更时完成配置。Controller 配置失败通常会触发自动重试,整个集群会在控制器不断重试的机制下确保最终一致性(Eventual Consistency)

比如说我启动一个deployment 期望启动两个Pod, 但是当时集群资源不足,只能够启动一个Pod, 那么另一个Pod 就会处于未调度状态, Controller 会不断的重试,当集群资源够的时候,吊起另一个Pod。

控制器的工作流程 k8s 的任何对象是允许用户自己去做监听的,针对这些对象的访问,k8s 提供了CodeGenerator 可以帮助用户生成访问这些对象的代码框架,这个代码框架就叫Controller Interface:

Informer: 可以通过Informer 监听任何对象,当对象的状态进行变化,比如说增删改查,都会生成一个event 事件,任何的controller 都是一个生产者消费者模型,会将事件对应的key, 放在一个队列中,然后worker 去消费这些key, 进行下一步动作。Lister: 会对对象的状态进行一些缓存,并且对外提供接口,这样就可以通过Lister 直接获取cache 中的对象状态,无需走APIServer

Informer 的工作机制

List & Watch 会将所有对象作为一个List 返回过来,然后保持一个长连接去监听对象的状态。APIServer 传递到Informer的对象是序列化的,在Informer 中就需要用到反射,将对象反序列化为一个个对象放到队列中。每个对象状态的变更,都会分配到一个Event 进行处理,当处理完毕后,会将处理过的状态也同步更新至 Thread Safe Store, 这里的 Thread Safe Store 就是Controller Interface 中的Lister 中缓存的对象状态。

控制器的协同工作原理 APIServer 中有各种各样的控制器,每个控制器的分工是不同的,比如上边创建一个Deployment 就先经过多个控制器: Deployment Controller:监听Deployment, 创建副本集。

ReplicaSet Controller: 监听副本集,创建Pod, 这个时候创建出来的Pod 是未调度状态的,需要经过调度器发现可用节点,然后再根据调度策略,将Pod 绑定至对应节点。

然后节点上的kubelet 就会加载对应Pod, 通过CRI(Container Runtime Interface) 将容器进程拉起来,通过CNI(Container Network Interface)加载对应容器网络,通过CSI(Container Storage Interface) 挂载对用容器存储,这个时候这个Pod 就处于已调度状态了。

Scheduler

特殊的Controller,工作原理与其他控制器无差别。

Scheduler 的职责在于监控当前集群所有未调度的 Pod, 并且获取当前集群所有节点的健康状况和资源使用情况,为待调度 Pod 选择最佳计算节点,完成调度。

调度阶段分为:

Predict: 过滤不能满足业务需求的节点,如资源不足、端口冲突等。Priority:按既定要素将满足调度需求的节点评分,选择最佳节点。Bind:将计算节点与Pod 绑定,完成调度。 Kubelet

k8s 的初始化系统(init system)

从不同源获取Pod 清单,并按需求启停 Pod 的核心组件: Pod 清单可从本地文件目录,给定的HTTPServer 或 Kube-APIServer 等源头获取Kubelet 将运行时,网络和存储抽象成了 CRI, CNI, CSI 负责汇报当前节点的资源信息和健康状态负责 Pod 的健康检查和状态汇报 Kube-Proxy

控制集群中用户发布的服务,并完成负载均衡配置。每个节点的Kube-Proxy 都会配置相同的负载均衡策略,使得整个集群的服务发现建立在分布式负载均衡器之上,服务调用无需经过额外的网络跳转。负载均衡配置置于不同插件实现: userspace操作系统网络协议栈不同的Hooks 点和插件: iptables ipvs 推荐的Add-ons kube-dns: 负责为整个集群提供DNS 服务(现在已经作为k8s内置组件了)ingress Controller:为服务提供外网入口MetricsServer: 提供监控资源Dashboard: 提供GUIFluentd-Elasticsearch: 提供集群日志采集、存储与查询


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #k8s前身 #k8s #的前世是Google #与k8s #的架构是什么样的以及k8s