Kubernetes-Operator篇-01-Operator开篇

1、kubernetes背景

1.2 Controller模式

controller通过根据被控制对象的属性和字段来实现编排,对于每一个build-in的资源类型,都有对应的controller。

比如以下是一个简单的Deployment的yaml示例:
其对应的Deployment Controller编排动作是确保app=test的Pod数量为2,Pod的属性和字段由spec.template定义

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
spec:
  selector:
    matchLabels:
      app: test
  replicas: 2
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

所有控制器的主要操作都是一个调谐循环(Reconcile loop)。主要有三步:

  • 观察期望的状态。
  • 观察所管理资源的当前状态。
  • 采取行动,使托管的资源处在期望的状态。
for {
    actualState := GetResourceActualState(rsvc)
    expectState := GetResourceExpectState(rsvc)
    if actualState == expectState {
        // do nothing
    } else {
        Reconcile(rsvc)
    }
}

1.2 声明式API

声明式API:告诉K8S你要什么,而不是告诉它你怎么做。

声明式API的操作体现在kubectl apply命令上,在对象创建和后续修改更新都使用apply命令,告诉k8s对象的终态即可,底层的实现是一个对原有API对象的PATCH操作实现的,可以一次性处理多个写操作,相对于命令时一个个的命令大大提高了操作效率。

比如上面的deployment.yaml文件,在提交后会通过Group/Version/Resource的分级来找到deployment在GO语言中的结构体定义,从而将YAML描述转换成一个序列化的deployment对象,并通过etcd的API将其保存起来。

1.3 “原生的资源-Controller”的问题

  • 原生资源不够用
    • K8S内部的基础资源,如Pod、Statefulset、Service,能够覆盖大部分应用的工作模式。
    • 但对以下情况不友好:
      • 有些应用组件无法找到合适的基础资源作为模板,比如GPU资源、训练数据集、训练任务等;
      • 有些应用组件需要多个基础资源共同输出一项能力,会变得很复杂;
  • 细粒度操作繁琐
    • 需要对云原生K8S的细粒度资源的种类比较熟悉;
    • 应用在提供服务时,存在通过Label等标签信息对应的逻辑。
  • 缺乏定制化能力,如:
    • 一些简单的中间件集成
    • 对不同资源的操作时序的流水线管理。

1.4 拓展自定义资源与控制器

主要做两件事:

  • 编写自定义资源,并将其部署到K8S集群中: 通过编写符合K8S资源和结构属性的文件,使得K8S能够校验该资源并进行持久化。
  • 编写其对应的控制器,并将其部署到K8S集群中: 通过实现调谐逻辑,来完成资源编排的实际需求。

2、Operator 介绍

2.1 Operator

借助 Kubernetes 的控制器模式,编写自定义的编排规则,完成对自定义资源的操作,比如增删改查等。

一般来说,Operator=CRD(自定义资源)+Controller(自定义控制器)+Webhook(Admission根据实际情况选择是否添加)

2.2 自定义资源 CRD

CRD(Custom Resource Definition)是用户的自定义资源类型,可以基于Kubernetes的API Server进行管理。其实例为CR(Custom Resource)。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: test ## CRD名称
spec:
  conversion:
    strategy: None
  group: tq   # REST API: /apis/<group>/<version>
  names:
    kind: App
    listKind: AppList
    plural: apps
    singular: app
  scope: Namespaced  # 资源是否区分namespace
  versions: [] # 资源的版本信息,例如:v1beta1、v1等

2.3 自定义控制器 Controller

Kubernetes控制器会监视资源的创建/更新/删除事件,并触发 Reconcile 函数作为响应。Reconcile 是一个使用 object(Resource 的实例)的命名空间和实例名来调用的函数,使object的实际状态与object的Spec中定义的状态保持一致。 调用完成后,Reconcile 会将object的状态更新为当前实际状态。
在实际环境中,Controller会被打包成镜像,以Deployment拉起的Pod的形式应用在集群中,并用service中的ClusterIP方式进行服务暴露发现。
在这里插入图片描述

2.4 名词解释

  • CRD(Custom Resource Definition): CRD允许用户在Kubernetes API中定义新的资源类型。这意味着你可以创建符合自己应用或服务特性的资源,这些资源能够被Kubernetes以类似于内置资源(如Pods、Services)的方式来管理和操作。CRD实质上是扩展了Kubernetes API的功能,允许你定义资源的名称、字段结构、验证规则等元数据。通过YAML或JSON文件定义CRD后,Kubernetes会将其注册到API服务器,使得用户可以通过Kubernetes API来创建、读取、更新和删除这些自定义资源。
  • CR(Custom Resource): CR是基于CRD定义的具体实例。一旦定义了CRD,用户就可以创建该类型的资源实例,这些实例就是CR。每个CR代表了特定的配置或状态,比如一个特定数据库实例的配置、一个复杂应用的部署配置等。CR的工作原理类似于Kubernetes中的其他对象(如Deployment、Service),但它们是用户自定义的,能够更好地适应特定应用的需求。开发者或运维人员通过创建CR来声明他们希望在集群中部署或管理的资源状态,而与之关联的Operator会根据这些声明来创建、维护和更新实际的Kubernetes对象。
  • GVK(Group, Version, Kind): GVK代表了资源的组(Group)、版本(Version)和种类(Kind)。它是从资源的API层面描述资源的一种方式。
    • Group:指资源所属的API组,用于区分不同功能领域的API。例如,apps/v1中的apps就是组名,它通常对应着Kubernetes的一个特定功能领域,如应用程序管理。
    • Version:资源的API版本,表明资源定义遵循的特定API版本规范。随着Kubernetes的发展,资源的API可能会有变化,版本号帮助区分这些不同的规范。
    • Kind:资源的类型,如Pod、Service、Deployment等,它定义了一类资源的基本结构和用途。
  • GVR (Group, Version, Resource): GVR与GVK类似,但用Resource替换了Kind,它从资源的访问路径角度描述资源。
    • Group:指资源所属的API组,用于区分不同功能领域的API。例如,apps/v1中的apps就是组名,它通常对应着Kubernetes的一个特定功能领域,如应用程序管理。
    • Version:资源的API版本,表明资源定义遵循的特定API版本规范。随着Kubernetes的发展,资源的API可能会有变化,版本号帮助区分这些不同的规范。
    • Resource: 指的是资源在API路径中的名称,它直接关联到API端点,是用于与API服务器交互时的URL路径组成部分。Resource通常与Kind相似或相同,但在某些情况下可能有细微差别,特别是在使用自定义资源定义(CRDs)时。
  • Scheme: 负责管理和注册API类型(如各种资源对象)以及它们之间的转换关系。简单来说,Scheme是理解和操作Kubernetes对象的基础框架,它确保了对象的序列化、反序列化以及类型转换能够正确进行
    • 类型注册:Scheme允许你注册自定义或内置的API类型。这意味着你可以告诉Scheme如何识别和处理特定的Go结构体类型,比如Pod、Service、或者自定义资源定义(CRDs)等。
    • 对象转换:它提供了类型转换功能,允许在不同的API版本之间转换对象。这对于处理API的版本兼容性和升级非常重要。
    • 默认化:Scheme支持为对象设置默认值。当创建或更新资源时,如果没有明确指定某些字段,Scheme可以根据预设规则自动填充默认值
    • 序列化与反序列化:Scheme知道如何将Go结构体转换为JSON或YAML格式的数据(序列化),以及如何将这些数据反序列化回Go结构体。这对于与Kubernetes API服务器通信至关重要。
    • 元数据处理:它还负责处理对象的元数据,如API版本信息和Kind信息,确保这些信息在序列化和反序列化过程中得到妥善处理。
  • Manager:Manager整合了一系列功能,使得开发者能够更容易地创建和管理自定义资源(CRDs)及其对应的控制器逻辑。以下是Manager的一些关键职责和功能
    • 自定义资源管理:Manager负责监听和管理自定义资源定义(CRDs)的变化,当CR实例被创建、更新或删除时,它能确保相应的控制器逻辑得到执行。
    • 控制器注册:它提供了一个中心点来注册和管理不同的控制器(Controllers)。控制器是执行具体业务逻辑的组件,负责维护期望状态与实际集群状态的一致性。
    • 依赖注入:Manager框架通常支持依赖注入,使得开发者可以轻松地复用和共享服务,比如客户端集(ClientSets)用于与Kubernetes API交互、日志记录器、事件记录器等。
    • 领导者选举:在分布式环境中,Manager可以实现领导者选举逻辑,确保在一个集群中只有一个活跃的实例在执行操作,避免冲突和重复处理。
    • Webhook注册:它还可能支持注册和管理自定义的Admission Webhooks,允许在资源创建或修改时插入自定义验证或修改逻辑。
    • 生命周期管理:Manager负责启动、停止控制器,以及处理它们的生命周期事件,确保资源的有效管理和清理。
  • Controller:Kubebuilder为我们生成的脚手架文件,我们只需要实现Reconcile方法即可。
  • Informers:在Kubernetes中,Informers是客户端库(client-go)提供的一个核心组件,用于高效地监听和同步Kubernetes API资源的变化。它是实现控制器模式的关键技术之一,尤其是对于那些需要根据资源状态变化做出反应的组件,如自定义控制器、Operator等
    • 资源监听:Informers持续监听Kubernetes API服务器中指定资源类型(如Pods、Deployments等)的增删改事件,通过watch API机制实现近乎实时的资源变化感知。
    • 缓存同步:它在本地维护一个资源对象的缓存副本,这个缓存会随着API服务器的事件更新而自动保持最新状态。这样,控制器或其他组件可以直接查询本地缓存获取资源列表或详情,而不需要频繁地直接查询API服务器,大大提高了效率。
    • 事件处理:Informers提供回调机制,允许用户注册处理函数(如EventHandler或Informer的事件处理器),当资源发生变化时自动触发这些函数,执行相应的业务逻辑。
    • 列表和详细信息的统一管理:通过Lister接口,Informers不仅能够提供资源列表,还能高效地获取单个资源的详细信息,进一步简化了资源的管理和查询过程。
  • Index:由于Controller经常要对Cache进行查询,Kubebuilder提供Index utility给Cache加索引提升查询效率。
  • Finalizer:在Kubernetes中,Finalizer 是一种高级特性,用于确保资源在其被删除之前能完成必要的清理工作。它作为一种保障机制,可以让控制器或操作者有机会在对象被永久删除前执行一些清理、备份或其他必要的操作,确保资源的优雅删除和资源使用的完整性。
    • 阻止删除:删除操作会被暂停,直到所有列出的finalizer都被处理完毕。
    • 通知处理者:Kubernetes会通知或等待负责该finalizer的控制器(或外部系统)完成相应的清理操作。
    • 清除Finalizer:一旦处理者完成了其任务,它会通过API更新该对象,从metadata.finalizers字段中移除相应的finalizer项。
    • 继续删除流程:当所有finalizer都被移除后,Kubernetes才会最终从API服务器中删除该对象。
    • 配置Finalizer:Finalizer通常是通过自定义控制器(如Operator)动态添加和管理的。控制器可以在创建或更新资源时向对象的metadata.finalizers字段添加自定义的finalizer名称,同样,在完成清理工作后,需要通过API调用来移除这个finalizer,以允许资源被彻底删除。
  • OwnerReference工作原理:在Kubernetes中,OwnerReference 是一种机制,用于建立资源对象之间的所有权关系。这种关系定义了资源的生命周期依赖性,即“拥有者”资源(owner)控制着“被拥有”资源(owned resource)的生命周期。当一个资源作为另一个资源的Owner时,它会对被拥有资源的创建、更新和删除产生影响,确保资源之间的一致性和自动化管理。
    • 自动清理(Garbage Collection):当一个资源对象被删除,并且它拥有其他资源时,Kubernetes的垃圾收集机制会自动删除这些被拥有的资源,确保资源依赖关系被正确清理,避免孤儿资源的产生。
    • 生命周期耦合:被拥有资源的生命周期与拥有者资源紧密相连。如果拥有者资源被修改或删除,Kubernetes会相应地更新或清理被拥有资源。
    • 防止循环依赖:Kubernetes禁止创建会导致循环OwnerReference关系的资源,以防止资源管理混乱和潜在的死锁情况。
  • OwnerReference结构定义:OwnerReference是以API对象的一个字段形式存在的,通常位于被拥有资源的metadata.ownerReferences字段中。它包含几个关键属性:
    • APIVersion:拥有者的API版本。
    • Kind:拥有者的资源类型。
    • Name:拥有者的名称。
    • UID:拥有者的唯一标识符(UID),这是区分不同资源实例的关键。
    • Controller(可选):布尔值,表示是否由一个控制器(如Deployment或StatefulSet)管理这个关系。如果是控制器,Kubernetes会在适当的时候自动管理被拥有资源。

2.5 CRD命名规范

CRD的全名必须是符合如下的命名规范: K i n d . {Kind}. Kind.{Group}.${Organization}.kubenode.alibaba-inc.com.

  • ${Organization}:一般为仓库的git group,即团队英文简称
  • ${Group}:必须是一种功能类别,如ops、apps、auth等,尽量用精简的单个英文单词的方式传达你的CRD属于的"类别"。组成的字母必须小写
  • ${Kind}:即为CRD真正的短名字,用精简的单个或多个英文单词的拼接来明明真正的CRD短名字。如AdvanceDeployment,NetBook等。使用大驼峰命名法(首字母也是答谢,即UpperCamelCase)。
  • alipay.com:根据自己的公司名称进行确定,即Company Name Domain
  • 目前对于CRD的版本转换不太友好,一般统一使用v1.

2.6 Spec,Status 规范

  • 用命令在apis包下生成CRD Types后,请不要随意修改apis里的结构体、命名规则、以及注释。
  • 只能,也只修改${Kind}_types.go文件里的Spec和StatusSpec结构体里的内容。
  • Spec和StatusSpec里的字段都必须是Public的,也就是字段名首字母是大写。
  • 每个字段,都应该写上JSON Tag,JSON Tag必须使用小驼峰命名法,即LowerCamelCase。
  • 如果字段允许为空,JSON Tag记得带上omitempty。StatusSpec的字段一般都是允许为空的。例子:
type DemoSpecs struct {
	// FiledA 允许为空
	FieldA string `json"fieldA,omitempty"`
	
	// FieldB 不允许为空
	FieldB string `json"fieldB"`
}

3、Operator 工作流程

在这里插入图片描述

3.1 核心组件

  • Informer:一个依赖 Kubernetes List/Watch API 、可监听事件并触发回调函数的二级缓存工具包。
    • 在启动时Reflector调用List API获得crd的全部Object实例,并缓存在Local Store中。
    • 然后Reflector调用Watch API来获取和监听对象实例的变化,维护缓存的变化。
    • 每当收到add/delete等实例请求时,Reflector会将变更的事件+对象推送到deltaFIFO的队列中。
    • 根据delttaFIFO中的内容,先到Local Store中更新对象的信息与状态,之后经过eventHandler进入WorkQueue,进而触发controller的reconcilet调谐逻辑。
  • WorkQueue:需要处理的事件队列,供controller来进行真正的业务处理。
  • Control Loop:实际的控制器角色,通过实现调谐逻辑,确保期望和实际运行状态是一致的。通过Lister向Local Store中读Object实例,通过Client向API Server写具体的操作逻辑。

3.2 实现细节

  • 消息可靠性:List短链接查询当前全量的资源及状态;Watch长链接接受增量的资源变更事件并做相应处理。两者互相帮助,达到消息的可靠性和数据的一致性。
  • 共享Informer:对同一类型的资源只建立一个链接,为多个controller提供共享cache的能力。
  • 若请求为添加操作,Indexer把API对象保存到本地缓存中,并为它创建索引;若为删除操作,则在本地缓存中删除该对象。
  • LocalStore 会周期性地把所有资源的信息重新放到 DeltaFIFO 中,确保二级缓存间的同步,让失败的事件得到重新处理。若在入队前发现DeltaFIFO中已经有新版本的Object实例,则不入队。
  • Reconcilers是核心处理逻辑,但其只获取资源的名称和命名空间,并不知道资源的操作(增删改)是什么,也不知道资源的其他信息。目的就是在收到资源变更时,根据object的期望状态直接调整资源的状态。

3.3 以Pod为对象的流程示例

在这里插入图片描述

  • Informer 在初始化时,Reflector 会先通过 List API 向 API Server 获得所有的 Pod 实例。
  • Reflector 拿到全部 Pod 实例后,会将全部 Pod 实例放到 Local Store 中
  • 如果后面 Controller 调用 Lister 的 List/Get 方法获取 Pod 实例时, 那么 Lister 会直接从 Local Store 中拿数据。
  • Informer 初始化完成之后,Reflector开始通过Watch API监听Pod相关的所有事件;如果此时 pod_1 被删除,那么 Reflector 会监听到这个事件。
  • Reflector 将 pod_1 被删除的这个事件发送到 DeltaFIFO
  • DeltaFIFO 首先会将这个事件(一般为key : value的形式)存储在自己的WorkQueue,然后会直接操作 Local Store 中的数据,删除 Local Store 中的 pod_1。
  • WorkQueue 会 Pop 这个事件到 Controller 中。
  • Controller 收到这个事件,通过 Lister 从本地 Local Store 中获取真正的对象实例,执行真正的业务逻辑。
    • 若有关联资源(ownerReference为该对象的资源),则删除关联资源。
    • 若无关联资源,则等待读取下一个事件。

4、原生实现

  • CRD实现: 首先利用CRD的定义文件types.go,通过官方的code-generator生成CRD的相关代码,包括标准client,deepcopy,informer和lister。
  • controller实现: 然后基于官方的sample-controller的实践示例,进行自定义的编排逻辑编写,包括对于不同add / update /delete 等操作的响应等细节操作。
  • YAML文件编写: 手动编写CRD定义和Deployment定义YAML文件,通过kubectl apply完成k8s资源的扩展和控制器的部署。
  • 其他开发工作: 比如对Controller进行二进制编译、镜像打包上传、以Deployment中的Pod的形式部署到K8s中等。

等后面来看下,这些工作中,脚手架能帮忙实现多少。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值