kubernetes 系列随笔 03:kubernetes 的发展和设计思想
再谈 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 的性能。
API Server 拥有大量高性能的底层代码。在 API Server 源码中使用协程(Coroutine)+队列(Queue)这种轻量级的高性能并发代码,使得单进程的 APIServer 具备了超强的多核处理能力,从而以很快的速度并发处理大量的请求。
普通 List 接口结合异步 Watch 接口,不但完美解决了 Kubernetes 中各种资源对象的高性能同步问题,也极大提升了 Kubernetes 集群实时响应各种事件的灵敏度。
采用了高性能的 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 本身很复杂,后面还会针对一些核心点做详细的阐述,敬请期待。
版权声明: 本文为 InfoQ 作者【谦寻】的原创文章。
原文链接:【http://xie.infoq.cn/article/566a71f54b291386a772bd612】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论