Kubernetes核心设计思想:为什么、如何借鉴(一)
一、前言
看过很多系统的设计,最让人眼前一亮的是Kubernetes的设计思想。本文总结其部分思想,并尝试应用到其他系统。
二、自动控制(automation)
Kubernetes是用自动控制思想设计的平台。
- 什么是自动控制?
试着理解自动与手动驾驶飞机区别:前者是你输入的目的地给飞机,飞机自动控制自身各组件到达该目的地;手动驾驶就是人工给飞机各类指令。
同理,Kubernetes的方式是让你输入想要的状态(如想要3个nginx实例),Kubernetes内部组件自动驱动系统达到该状态
自动控制一般通过一个负反馈闭环实现,原理如下。
软件行业很少用自动控制的思想做系统架构,这是Kubernetes让人眼前一亮的点。
- 为什么用它?
大幅降低用户负担。你也许会问:就几个命令,负担很重?下文有更详细讲解 - 怎么做?
声明式API;控制器模式(水平触发,非边缘触发)
声明式API(外部)
- 是什么
与声明式API相对的是命令式API。- 命令式API:你直接发出服务器要执行的命令,例如: “运行容器”、“停止容器”等;系统执行命令;你监控运行情况,提供进一步的命令
- 声明式API:定义期望的状态;系统向着指定的状态工作
- 为什么
以你要部署一个容器为例,命令式API的设计如下:
声明式API的工作方式:
replica.yaml:你想以1个副本的方式在系统中运行容器X
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
spec:
replicas: 1
template:
metadata:
...
spec:
...
containers:
- name: nginx
image: internal.mycorp.com:5000/mycontainer:1.7.9
系统存储该API对象,搞清楚你想干啥(k8s以pod为调度单位,后续用pod这个词替代容器):
系统调度该API对象描述的pod到合适的节点,并启动该pod。你基本是放手让系统干事,也不需要自己监控pod状态。
这种声明式的方式还有更大的好处,就是自动恢复!
如下,假如节点B挂了
系统会将pod自动移动到健康的节点A:
从上可知,声明式API是实现自动控制思想的基础。
声明式API(内部)
Google将其称为:No hidden internal APIs,就是说k8s内部各组件的接口也和外部接口一样是声明式的,公开的。
如果内部API是命令式,会有什么问题呢?我们重过一遍建pod流程:
节点应该如何处理呢?
声明式内部API的做法:
总结一下声明式API的好处:
- 无单点故障
- Master节点更简单
- K8s的组件可以更具备组合性和扩展性(随意用自己的组件替换内置组件)
Level triggering(水平触发)
声明式API定义了交互方式,但没回答如何实现的问题。比如观察(watch),如何做?用事件驱动,轮询?Google用电子行业术语描述监听的两种方式:edge triggering(边缘触发) 和 level triggering(水平触发),前者本质是事件触发,有变化时触发;后者本身是不停轮询(loop)。
两者听起来差不多,但后者容错性更强。
如下,正常情况下,两者都可以拿到信号的变化情况(用ON/OFF表示),甚至边缘触发的代码可能还少一点(不需要自己判断变化)
但如果出现干扰、丢数据情况,水平触发的优势就很明显了。下图的信号有丢失,导致边缘触发理解的信号和真实差别较大。
Kubernetes控制组件就是用水平触发方式进行监听。
控制器模式
- 什么是控制器模式
官方解释:一个永不终止的控制循环,它持续管理着集群的状态,通过apiserver获取系统的状态,并且不断尝试以达到预期状态。底下是简单示意图:
控制器模式也可理解为上文提到的level triggering的具体实现。
伪代码:
for {
实际状态 := 获取集群中对象 X 的实际状态(Actual State)
期望状态 := 获取集群中对象 X 的期望状态(Desired State)
if 实际状态 == 期望状态{
什么都不做
} else {
执行编排动作,将实际状态调整为期望状态
}
}
- 好处:容错性极强,具体可参考level triggering的图示。
Kubernetes的核心部分正是由许多控制器组合而成
。
三、小结
Kubernetes的架构参考了自动控制的思想,思想的关键要素是:声明式API(内部和外部),控制器模式。那我们的系统可否参考这样的思想,如果可以,哪些系统更适合呢?下一篇文章进行分析。
参考资料