kubernetes系列随笔03:kubernetes的发展和设计思想

462 阅读13分钟

再谈kubernetes

我们先从kubernetes的发展史聊起

kubernetes源于 Google 内部的 Borg 项目,经 Google 使用 Go 语言重写后,被命名为 Kubernetes,并于 2014 年 6 月开源。

2015 年 7 月 21 日,Google 宣布成立 CNCF 基金会,并正式发布 Kubernetes 1.0 版本。经过这些年的迅猛发展,一方面,Kubernetes 的创始人均为 Google 的天才工程师;另一方面,该项目源自 Google 的 Borg,而 Borg 的能力已在 Google 内部得到充分验证。因此,Kubernetes 可以说天生自带光环,自出生之日起,就受到了各大 IT 科技巨头的追捧。

kubernetes重要版本迭代情况

  • 2017 年 3月 ,Kubernetes 1.6 发布,引入RBAC权限控制
  • 2017 年 6 月 ,Kubernetes 1.7 发布,限制kubelet访问的节点授权程序以及客户端/服务器TLS证书轮换
  • 2017 年 9 月 ,Kubernetes 1.8 发布,Kubelet的传输层安全性(TLS)证书轮换成为beta版
  • 2017 年 12 月 ,Kubernetes 1.9 发布,开始支持Windows了
  • 2018 年 3 月 ,Kubernetes 1.10 发布,将原来的kube-dns切换为CoreDNS
  • 2018 年 6 月,Kubernetes 1.11 发布,基于IPVS作为集群内负载均衡
  • 2018 年 9 月,Kubernetes 1.12 发布,Kubelet TLS Bootstrap GA
  • 2018 年 12 月,Kubernetes 1.13 发布,CoreDNS 作为默认的 DNS、使用 kubeadm 简化集群管理
  • 2019 年 3 月,Kubernetes 1.14 发布,对于管理 Windows node 的生产级支持
  • 2019 年 6 月,Kubernetes 1.15 发布,kubeadm 管理的证书轮换得到进一步加强
  • 2019 年 9 月,Kubernetes 1.16 发布,CRD功能 GA了
  • 2019 年 12 月,Kubernetes 1.17 发布,CSI 迁移(Migration)进入 beata 版
  • 2020 年 3 月,Kubernetes 1.18 发布,增加为特定Pod配置HPA速率
  • 2020 年 8 月,Kubernetes 1.19 发布,增加存储容量追踪、CSI卷运行状况监控
  • 2020 年 12 月,Kubernetes 1.20 发布,Dockershim弃用,Kubectl Debug功能步入beta阶段
  • 2021 年 4 月,Kubernetes 1.21 发布,IPv4/IPv6 双协议栈支持从 alpha 升级到 beta,并且默认启用
  • 2021 年 8 月,Kubernetes 1.22 发布,删除了一系列已废弃的 API,增加了Memory QoS

kubernetes为什么如此成功

截止到目前,kubernetes已经成为基础设施的事实标准,这一点是毋庸置疑的,基本上了解kubernetes的技术人员都很看好它的未来发展。

大厂的支持

大厂基于 Kubernetes 的产品不断落地,加速 Kubernetes 商业化进程之余极大提振了市场信心。大厂一边使用 Kubernetes 技术提升产品竞争力,一边积极把客户需求带到社区进行落地,这进一步促进了社区繁荣。

截至 2021 年 8 月,Kubernetes 代码主仓已获得 8 万个星标,主仓贡献者达到了 3100 多个,其在 GitHub 上的活跃度已超过 99.99%的项目。

Kubernetes 的成功使 CNCF 走向空前繁荣,无论是 CNCF 会员数量、通过一致性认证的产品数量还是云厂商数量都呈快速上涨趋势。

超前的设计理念

声明式API

如果谈论设计,首先就要说这个,通过声明式的API只需提供合适的spec定义即可,用户通过修改spec来告知系统他所希望的状态是什么样子的。如果是用命令式API则需要暴露十几甚至上百个接口用以满足不同资源的不同操作,用户使用起来也很麻烦。像k8s提供的扩缩容、修改镜像等操作,其底层都是对spec的修改。

然后就是稳定,声明式和命令式分别代表了两种不同的动作触发模式,水平触发和边缘触发,比如一个开关,我们进行了开、关、开三次操作,对于命令式API这意味着要发三次命令,如果网络出现了问题最后一次开的命令丢失了则这个开关会一直处于错误的状态,所以如果是边缘触发则需要配合补偿机制。如果是声明式API即使网络出了问题系统停留在错误的状态,但是当网络恢复之后系统依然可以调整到所期望的状态。设想我们在对系统做扩缩容的操作时,如果每次告诉k8s要增加或减少一个实例,一但这个代表具体操作的命令发生了丢失那么系统的状态就乱了,更好的做法是告诉k8s我们所期望的实例个数是多少,即使这期间网络发生了问题或者控制器出了问题,等到网络或者出问题的组件恢复后k8s依然可以为我们把实例调整到我们所期望的个数。

声明式API便于处理多写的操作,一次可以处理多个写操作并且具备merge能力。现在比较火的service mesh就是利用这种能力,为每个pod里面安装一个envoy容器,这个过程是通过k8s的Dynamic Admission Control功能往pod里面注入一个envoy容器。

比如你用命令式部署一套kafka集群,你会怎么做?需要step-by-step的编写脚本,需要设想目标环境的各种状况。运维流程很难有事务性,脚本执行过程被意外打断,比如Linux版本问题、jvm没安装这种未知情况出现如何处理。这种使用命令式很难去解决的问题,使用声明式就比较容易。声明式使用配置文件直接描述终态,不用考虑流程和目标环境细节,不会产生不一致的结果。

list-watch机制

 k8s所有的API对象状态都是存储在etcd中,所有的交互都是必须通过api server进行的,之前我们提到在声明式API的基础上控制器和调度器等都是需要监听API对象状态的变化,当API对象生命的状态发生变化后需要做出相应的操作动作。面对这种需求,一般需要引入MQ,而k8s本身为了降低系统本身对其它组件的依赖,并没有使用任何消息中间件,也没有使用轮询的方式。最终选择了一种list-watch机制。

list部分是通过list接口实现对API对象及状态的查询,watch部分是通过提供watch api来实现对资源的变更时间的监听,list和watch相互配合,通过list获取全量数据、通过watch获取增量数据。通过list-watch机制API Server相当于充当了消息总线的角色,k8s中的控制器、调度器、kubelet等其他组件只需通过该机制监听自己所关心的对象状态即可,相互之间并不知道彼此的存在。比如pod的调度,调度器通过监听发现有新的pod创建出来且尚未对其调度,调度器开始执行自己的调度逻辑为该pod选择node节点,然后调用API Server接口把要绑定的node信息写入pod对象的Node字段中即完成了调度。同时node节点上的kubelet组件通过订阅事件发现有一个pod调度到该节点上了,那么它就会通过API Server获取该pod对象的配置信息,根据pod的Spec完成部署。整个过程中kubelet并不知道scheduler的存在,通过这种机制对各组件的依赖关系进行了解耦。

解耦、抽象

对接网络k8s社区提供了cni标准、对接存储k8s社区提供了csi标准、对接runtime k8s社区提供了cri,使用这些接口用户可以很方便的构建自己的容器生态。k8s只是作为云原生应用的基础调度平台,相当于云原生的操作系统,方便了系统的扩展。

面向operator交付

在k8s生态中,有一个更加灵活和编程友好的管理“有状态应用”的解决方案,那就是operator。它的工作原理,实际上是利用了k8s的自定义API资源(CRD),来描述我们想要部署的应用,然后在自定义控制器里,根据自定义API对象的变化,来完成具体的部署和运维工作。

使用operator,我们可以根据自身业务情况开发属于自己的workload,比如可以发版的时候ip地址不变、简单的灰度发布,更加方便运维进行资源管理。这样我们的应用就是高度自动化、有自愈能力,研发、运维人员投入更少的精力就能更好的交付。

kubernetes核心组件介绍

API Server

Kubernetes API Server的核心功能是提供Kubernetes各类资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽,是整个系统的数据总线和数据中心。除此之外,它还有以下一些功能特性。

  • 集群管理API的入口
  • 资源配额控制的入口
  • 提供了完备的集群安全机制

API Server 通过kube-apiserver的进程提供服务,运行在master节点上,默认监听8080端口,提供rest服务,同时我们启动https安全端口(--secure-port=6443),加强rest api访问安全性。

API Server的性能是决定Kubernetes集群整体性能的关键因素,因此Kubernetes的设计者综合运用以下方式来最大程度地保证API Server的性能。

  1. API Server拥有大量高性能的底层代码。在API Server源码中使用协程(Coroutine)+队列(Queue)这种轻量级的高性能并发代码,使得单进程的APIServer具备了超强的多核处理能力,从而以很快的速度并发处理大量的请求。
  2. 普通List接口结合异步Watch接口,不但完美解决了Kubernetes中各种资源对象的高性能同步问题,也极大提升了Kubernetes集群实时响应各种事件的灵敏度。
  3. 采用了高性能的etcd数据库而非传统的关系数据库,不仅解决了数据的可靠性问题,也极大提升了API Server数据访问层的性能。

Controller Manager

controller相当于k8s的控制系统,它们通过API Server提供的(List-Watch)接口实时监控集群中特定资源的状态变化,当发生各种故障导致某资源对象的状态发生变化时,Controller会尝试将其状态调整为期望的状态。比如当某个Node意外宕机时,Node Controller会及时发现此故障并执行自动化修复流程,确保集群始终处于预期的工作状态。Controller Manager是Kubernetes中各种操作系统的管理者,是集群内部的管理控制中心,也是Kubernetes自动化功能的核心。

Controller Manager内部包含Replication Controller、NodeController、ResourceQuota Controller、NamespaceController、ServiceAccount Controller、Token Controller、Service Controller及Endpoint Controller这8种Controller,每种Controller都负责一种特定资源的控制流程,而Controller Manager正是这些Controller的核心管理者。

Scheduler

Kubernetes Scheduler在整个系统中承担了“承上启下”的重要功能,“承上”是指它负责接收Controller Manager创建的新Pod,为其安排一个落脚的“家”——目标Node;“启下”是指安置工作完成后,目标Node上的kubelet服务进程接管后继工作,负责Pod生命周期中的“下半生”。

具体来说,Kubernetes Scheduler的作用是将待调度的Pod(API新创建的Pod、Controller Manager为补足副本而创建的Pod等)按照特定的调度算法和调度策略绑定(Binding)到集群中某个合适的Node上,并将绑定信息写入etcd中。在整个调度过程中涉及三个对象,分别是待调度Pod列表、可用Node列表,以及调度算法和策略。简单地说,就是通过调度算法调度为待调度Pod列表中的每个Pod从Node列表中选择一个最适合的Node。随后,目标节点上的kubelet通过API Server监听到Kubernetes Scheduler产生的Pod绑定事件,然后获取对应的Pod清单,下载Image镜像并启动容器。

kubelet

在Kubernetes集群中,在每个Node上都会启动一个kubelet服务进程。该进程用于处理Master下发到本节点的任务,管理Pod及Pod中的容器。每个kubelet进程都会在API Server上注册节点自身的信息,定期向Master汇报节点资源的使用情况,并通过cAdvisor监控容器和节点资源。

kubelet在启动时通过API Server注册节点信息,并定时向API Server发送节点的新消息,API Server在接收到这些信息后,将这些信息写入etcd。通过kubelet的启动参数“--node-status- update-frequency”设置kubelet每隔多长时间向APIServer报告节点状态,默认为10s。

kube-proxy

了解过k8s的人想必都听过service的概念,service是对一组pod的抽象,它会根据访问策略,比如轮询,来访问这组pod,在很多情况下,Service只是一个概念,而真正将Service的作用落实的是它背后的kube-proxy服务进程。只有理解了kube-proxy的原理和机制,我们才能真正理解Service背后的实现逻辑。

在Kubernetes集群的每个Node上都会运行一个kube-proxy服务进程,我们可以把这个进程看作Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。此外,Service的Cluster IP与NodePort等概念是kube-proxy服务通过iptables的NAT转换实现的,kube-proxy在运行过程中动态创建与Service相关的iptables规则,这些规则实现了将访问服务(ClusterIP或NodePort)的请求负载分发到后端Pod的功能。由于iptables机制针对的是本地的kube-proxy端口,所以在每个Node上都要运行kube-proxy组件,这样一来,在Kubernetes集群内部,我们可以在任意Node上发起对Service的访问请求。综上所述,由于kube-proxy的作用,在Service的调用过程中客户端无须关心后端有几个Pod,中间过程的通信、负载均衡及故障恢复都是透明的。

最后

现在越来越多的企业在使用kubernetes,或者正在准备使用的路上,毋庸置疑,kubernetes整个生态在企业的技术体系中发挥越来越重要的作用。上面只是简单的介绍了kubernetes的发展和一些核心组件,当然,kubernetes本身很复杂,后面还会针对一些核心点做详细的阐述,敬请期待。