查看原文
其他

自定义你的 Kubernetes 资源:一文细说 CRD+Operator

钟炯恩、郭耀星 高效运维 2022-11-05

在前面的文章中,常常会提到 CRD 和 K8s operator,但并没有对此进行深入的探讨。作为k8s中的一大亮点,在本篇文章中,我们会详细展开讲讲。

1、什么是CRD

如果 K8S 中的自带资源类型不足以满足业务需求,需要定制开发资源怎么办?自定义资源(Custom Resource)由此产生。那么,如何让 Kubernetes 认识这些自定义的资源呢?CRD(Custom Resource Definition)就承担了一个说明书的角色,让 Kubernetes 来认识这个自定义资源CR。

那么CRD是怎么来的呢?最早是谷歌提出 Third Party Resource 的概念,希望开发者以插件化形式扩展 K8s API 对象模型,以增强整个 K8s 的生态。基于Third Party Resource这一概念,Kubernetes 社区在 1.7 版本中提出了CRD 的概念。
随便打开一个 CRD 的 YAML 可以看到,其主体部分是使用 OpenAPI v3 schema 来描述CR的字段结构,类似编程语言中的强类型声明。
apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: lights.light.sreworks.iospec: group: light.sreworks.io names: kind: Light plural: lights scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: description: ... type: object properties: spec: type: object properties: company: type: string ...
有了 CRD 之后,我们可以自由地增加各种内置资源平级的资源。原本很多之前只维护在软件内部的元数据,也可以被写入到 K8s 集群中。这极大地拓宽了我们的想象力,比如作业、路由、账号等各种关联的资源都一股脑地放进集群里面去。
在各种自定义资源被放进去之后,就会有人问,这放进去是挺方便的,但是放进去就会生效吗?是的,资源的生效就是 Operator 的功劳。下面我们就开始介绍 Operator。

2、什么是Operator

首先随便翻看一本词典看一下 operator 这个词的定义:操作员/运算符,是个名词。那么,operator 描述的应该是一个围绕”操作、控制”概念的东西。为了让大家有个更直观的认识,我们来举一个例子,比如 1 + 2 = 3,这个 “+” 就是一个operator(运算符),这个 “+” 让两个数字发生了一些互动(相加)。

有了词典里的概念铺垫后,我们继续往下分析,既然是一种操作或运算,那么在 K8s 中,是谁来操作?而被操作的对象又是什么呢?让我们来看一下OperatorFramework官网上对于 Operator 的解释。

WHAT IS AN OPERATOR AFTER ALL?

An Operator represents human operational knowledge in software, to reliably manage an application. They are methods of packaging, deploying, and managing a Kubernetes application.

从这个定义中,我们可以看到,这个 operator 是指由人发出的,对k8s应用(Kubernetes application)展开的操作。一般围绕应用的操作有哪些?部署、升级、扩缩容、卸载等等。我们可以先这样理解,operator应该就是个类似控制器的东西,里面含有一些运维操作(后面会继续展开,其实不仅仅是这些)。
较真一点的读者可能会问,既然这样,这东西叫controller是不是会更贴切一点呢?事实上,问出这个问题的读者,和真相很接近了,每个operator基本都会有个控制器,但又不仅仅只有一个控制器,还会有前面提到过的资源定义: CRD (CustomResourceDefinition) 。每种自定义资源背后都会有一个或多个控制器,让这些资源看起来像活的一样。我们举一个比较贴近生活的例子:
我们为家里的灯制作一个CRD和operator,把这个operator和灯开关连起来,当用户修改这个YAML的时候,operator会向开关转发指令。
apiVersion: v1kind: Lightmetadata: name: bedroomspec: power: on brightness: 70 colorTemperature: 5000k

从名字可以看出这盏灯被放在卧室(bedroom),当power=on的时候电灯打开,power=off的时候电灯关闭,修改亮度(brightness)和色温(colorTemperature)能操纵这盏灯在打开状态下的视觉效果。

通过上面这段灯的YAML我们可以发现,在CRD+operator的场景下,我们可以只关注对象终态,而不去关注其中的控制过程。比如当前家中网络不太稳定,要花1-2秒,重试3次operator才能成功下发指令打开灯,这些重试我们是不感知的。我们只知道只要将power设置为on,灯就会亮。类比到k8s的日常实践,也是这样:一个Pod被放到集群后,控制器会想方设法去克服困难: 从仓库拉取镜像,启动工作负载,如果crash掉了就立即重试,直到稳定运行为止。我们只关心这个Pod是否最终拉起可用。

所以,operator其实是一种架构理念,它区别于常见的shell等运维脚本方案:operator希望应用能够自管理,而不是由运维人员写脚本从外围来控制他们。不过,如果仅仅是这样,可能operator也只能叫controller了,只是一些自控制的逻辑而已。从最前面提到的operator的概念可以看出,operator能够让两种以上的资源产生一些互动关系,那么这是如何实现的呢?
我们继续用上面的灯的例子再加个YAML让大家感受一下:
我们把自己的家也用一个自定义资源对象来描述,用来承载一些家中的全局设置。
apiVersion: v1kind: Homemetadata: name: jiongsi-homespec: nobody: false stayOpen: []
当我们家中所有人都出门的时候,家中就没有人了,于是将nobody设为true。然后Home的operator会遍历家中所有的开关、电器、灯等设备,全部都给关上(在YAML上设置power=off)。同时也会根据常亮的策略(stayOpen),保持某些电器不关闭,比如冰箱。

从上面的例子可以看出,每个控制器只负责自己的那部分,但从顶层往下看,已经实现了级联控制,能够实现牵一发而动全身的效果。这个就是上面所提到的operator的更深一层的机制:能够像运算符一样,让几种资源产生某种互动关系,一起协作完成复杂的工程行为。

3、如何实现K8S Operator

不管是原生 YAML / Helm 还是 Kustomize,都是通过配置来搞定各类事情。然而 CRD + Operator 就不一样了,它们让你直接接入 apiserver,作为 K8S 的一部分监听所有你关心的对象,并通过代码进行状态维持及管理。因为 CRD 的开发是非常复杂的,除了业务逻辑之外,还需要做很多基础的工作,非常不便,所以有了 Operator 的开发框架(常见的有 KubeBuilder 和 Operator-SDK),让开发人员专注于 CRD 的业务代码开发。
我们可以来看一下operator的架构实现,这个有助于我们理解operator的工作原理:

如图可知,Operator内部有个控制器来监听CR的变化,同时由于每个变化对应的函数执行需要一定的耗时,所以引入一个队列来依次执行这些函数。由于整个逻辑的执行链路不同于普通的web服务,所以也需要一个框架来承载请求的流转。
市面上的KubeBuilder 或 Operator-SDK 开发框架可以降低Operator的难度,但 Operator 的开发在当前所有的几类组件托管方案当中仍然是最为复杂的。前前后后需要 CRD 设计及安装,编译 Operator 及部署到集群,最后再下发 CR,外围为了配套这些内容可能还需要上面 Helm 或 Kustomize 的协助,配合对应的 CICD 流程及工具。

Spark Operator

Spark Operator是大数据分布式系统在k8s场景一次经典的实践。原本Spark的作业提交是需要通过spark-submit命令,但有了Spark Operator之后,我们可以直接向k8s提交作业YAML,然后Spark Operator监听CR,将这一作业提交给控制器。实现了我们前文提到的,将作业资源放在k8s集群进行管理这一目标。

4 大数据通用Operator设计与实践

上文讲述了operator实现的复杂性。不过,我们发现,越是这样复杂的应用,越是会有一些共通性:因为这些复杂应用基本都是分布式应用,只是在某些状态或部署顺序上的有些特殊需求。于是,我们针对这个现状,开发了一款通用的大数据Operator。
这个通用 Operator 的架构设计如下:

与市面上常见的golang编写的operator不同的是,我们鼓励用户不编写代码,而是直接用YAML来描述控制逻辑,按照 感知/决策/执行 三大环节来进行控制器的逻辑分解和编排设计。同时,因为有这几个环节抽象的辅助,用户在设计operator的时候能够更有目的性,对于复杂场景,不引入过多的复杂逻辑流,尽量用无状态的方式解决问题。

同时,我们还借鉴了前端框架 React 中的 VirtualDOM 的设计,在云原生场景下,引入了 VirtualResource 这样的一个概念。VirtualResource 能够将云原生对象资源映射进入Operator 的内存数据库中,让控制器能够用 SQL 语法快速查询和操作这些资源对象,简化 Reconcile(调和)场景的逻辑复杂性。对照React框架中生命周期的概念,VirtualResource 也存在生命周期的概念,用户能够控制在资源变化的不同阶段,追加一些自定义的运维描述动作。

我们在大量使用helm的情况下,发现golang template语法在进行模板渲染的时候,还是不够灵活。于是我们把整体架构栈切换到python,采用jinja2进行控制器的语法渲染,同时我们也保留helm在渲染框架中,用户能够无缝切换两种渲染引擎。
这个通用 Operator 的控制器将原本需要 golang 编写的控制层逻辑,简化成使用 CMD(指令) + YAML(资源)的方式进行描述。控制器的描述示例如下:通过helm将vvp这个应用的所有 yaml 下发,监听 service 的状态变化,同步更新 ingress 资源的状态。
default: def: crd.yaml deploy: - cmd: helm chart: vvp/vvp values: vvp/values.yaml maintain: - watch: category: ResourceDidChange kind: Service apiVersion: v1 action: - cmd: kube-patch file: ingressUpdate.yaml

5、总结

对于承载组件 (Component) 这个概念而言,CRD+Operator 可以说是最为复杂的,但是又是最万能的,如果 Helm 或者 Kustomize 无法满足需求,Operator 基本上是唯一的选择。另一方面来说,CRD+Operator 一般又会和 Helm / Kustomize 相辅相成一起出现,最难搞的事情通过 Operator 与 apiserver 交互解决,剩下的胶水粘合,各种 YAML 拼接之类的交给 Helm / Kustomize 搞定。
同时,我们也可以看出,CRD+Operator是云原生演进时期的方案,比较适合原本非k8s的软件架构来适配 K8s 环境。那些原本就在k8s云原生架构下出现的软件,会逐渐淡化Operator这层概念: 所有的工作负载都有对应的资源定义(CRD),他们都有能力和k8s apiserver交互。

对于承载 SREWorks 中的应用 (Application) 这个概念而言,Operator 是不合适的,无他,太复杂了。一般来说,Operator 只要管好自己这个独立功能在 K8S 中的生命周期就已经足够了。从目前的社区方向来看,Operator 不会作为一整个业务场景应用解决方案去裸提供,而是与 Helm/Kustomize/KubeVela/AppManager(SREWorks中OAM实现)等集成并作为一个整体(组件 or 应用)对外发布。

来源:阿里智能运维,点击查看原文

2022年10月,GOPS 全球运维大会 2022 · 上海站,字节、小红书、ebay、中通等互联网一线运维经验,扫码解锁更多精彩~

<<  滑动查看下一张图片  >>

近期好文:

因违反数据保护条例,Meta接4亿天价罚单;贵州核酸检测系统崩溃:云上贵州回应;GitHub将关闭Trending|一周IT资讯

“高效运维”公众号诚邀广大技术人员投稿

投稿邮箱:jiachen@greatops.net,或添加联系人微信:greatops1118。
点个“在看”,一年不宕机

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存