Bootstrap

Kubernetes Operator学习

一、基本概念

1、名词解释

  • API Server:在 Kubernetes 中,API 服务器(API Server)是 Kubernetes 系统的核心组件之一,负责管理和公开 Kubernetes 集群中的 API。API 服务器提供了一组 RESTful API,用于管理和操作 Kubernetes 集群的各种资源,包括 Pods、Deployments、Services、ConfigMaps等。

    • 理解:

      • API Server相当于k8s集群的前端接口来处理外部请求,可以将对应的如 kubectl 命令行工具的命令转成内部的操作

      • CRD的定义个人理解像是新加了一条Api接口,保证可以通过API Server对CR资源进行操作(通过Restful方式)

图片1.png

图1 Api扩展-ApiServer

  • CRD(Custom Resource Definition):允许用户自定义 Kubernetes 资源,是一个类型;

  • CR (Custom Resourse):CRD 的一个具体实例;

  • Webhook:它本质上是一种 Http 回调,会注册到 API Server 上。在 API Server 特定事件发生时,会查询已注册的 webhook,并把相应的消息转发过去。按照处理类型的不同,一般可以将其分为两类:一类可能会修改传入对象,称为 mutating webhook;一类则会只读传入对象(会进行校验),称为 validating webhook;

  • 工作队列:controller 的核心组件。它会监控集群内的资源变化,并把相关的对象,包括它的动作与 key,例如 Pod 的一个 Create 动作,作为一个事件存储于该队列中;

  • controller:它会循环地处理上述工作队列,按照各自的逻辑把集群状态向预期状态推动。不同的 controller 处理的类型不同,比如 replicaset controller 关注的是副本数,会处理一些 Pod 相关的事件;

    • 监视资源的 创建 / 更新 / 删除 事件,并触发 Reconcile函数作为响应。整个调整过程被称作 Reconcile Loop(协调一致的循环),其实就是让 POD 趋向CRD定义所需的状态;

image.png

图片2.png

图2 operator示意图

  • operator:operator 是描述、部署和管理 kubernetes 应用的一套机制,从实现上来说,可以将其理解为 CRD 配合可选的 webhook 与 controller 来实现用户业务逻辑,即 operator = CRD + webhook + controller

2、Controller

  • 参考:链接

  • 定义

    • 在 K8s 中,用户通过声明式 API 定义资源的“预期状态”,Controller 则负责监视资源的实际状态,当资源的实际状态和“预期状态”不一致时,Controller 则对系统进行必要的更改,以确保两者一致,这个过程被称之为调谐(Reconcile)。

  • Controller和Operator的区别

    • 有时候 Controller 也被叫做 Operator。这两个术语的混用有时让人感到迷惑。Controller 是一个通用的术语,凡是遵循 “Watch K8s 资源并根据资源变化进行调谐” 模式的控制程序都可以叫做 Controller。而 Operator 是一种专用的 Controller,用于在 Kubernetes 中管理一些复杂的,有状态的应用程序。例如在 Kubernetes 中管理 MySQL 数据库的 MySQL Operator。

  • K8s HTTP API 的 List Watch 机制

    • 在访问API Server URL 后面加上参数 ?watch=true,则 API Server 会对 default namespace 下面的 pod 的状态进行持续监控,并在 pod 状态发生变化时通过 chunked Response (HTTP 1.1) 或者 Server Push(HTTP2)通知到客户端。K8s 称此机制为 watch

    • 实践

      • 启动api server代理服务器:kubectl proxy --port 8080,通过 curl 来 List pod 资源

      • 在请求中加上 watch 参数:curl http://localhost:8080/api/v1/namespaces/default/pods?watch=true&resourceVersion=770715

      • 返回

        • image.png

        • ADDED 表示创建了新的 Pod,Pod 的状态变化会产生 MODIFIED 类型的事件,DELETED 则表示 Pod 被删除。

  • Informer 机制

    • 背景

      • k8s HTTP API 会产生大量的 HTTP 调用,会对 API Server 造成较大的负荷,而且网络调用可能存在较大的延迟

      • 除此之外,开发者还需要在程序中处理资源的缓存,HTTP 链接出问题后的重连等

    • 定义

      • 在 Kubernetes 中,Informer 是一个客户端库,用于监视 Kubernetes API 服务器中的资源并将它们的当前状态缓存到本地。Informer 提供了一种方法,让客户端应用程序可以高效地监视资源的更改,而无需不断地向 API 服务器发出请求。

    • Kubernetes Informer 架构

    图3 Informer架构

    • 说明:图中间的虚线将图分为上下两部分,其中上半部分是 Informer 库中的组件,下半部分则是使用 Informer 库编写的自定义 Controller 中的组件,这两部分一起组成了一个完整的 Controller。

    • 流程

      • Reflector 采用 K8s HTTP API List/Watch API Server 中指定的资源。

        • Reflector 会 List 资源,然后使用 List 接口返回的 resourceVersion 来 watch 后续的资源变化。对应的源码:Reflector ListAndWatch

        • 先list再watch

      • Reflector 将 List 得到的资源列表后续的资源变化放到一个 FIFO(先进先出)队列

      • Informer 在一个循环中从 FIFO 队列中拿出(POP)资源对象进行处理。对应源码:processLoop

      • Informer 将从 FIFO 队列中拿出的资源对象放到 Indexer 中。对应的源码:processDeltas

        • Indexer 是 Informer 中的一个本地缓存,该缓存提供了索引功能(这是该组件取名为 Indexer 的原因),允许基于特定条件(如标签、注释或字段选择器)快速有效地查找资源。

      • Indexer 将收到的资源对象放入其内部的缓存 ThreadSafeStore 中。

      • 回调 Controller 的 ResourceEventHandler,将资源对象变化通知到应用逻辑。对应的源码:processDeltas

      • ----------------------------------------------------------------------------------------------------------------------------

      • ResourceEventHandler 处于用户的 Controller 代码中,k8s 推荐的编程范式是将收到的消息放入到一个队列中,然后在一个循环中处理该队列中的消息,执行调谐逻辑。

        • 本地将对象的Key存入队列,进行循环处理

        • 推荐该模式的原因是采用队列可以解耦消息生产者(Informer)和消费者(Controller 调谐逻辑),避免消费者阻塞生产者

        • Reflector 会使用 List 的结果刷新 FIFO 队列,因此 ResourceEventHandler 收到的资源变化消息其实包含了 Informer 启动时获取的完整资源列表,Informer 会采用 ADDED 事件将列表的资源通知到用户 Controller。该机制屏蔽了 List 和 Watch 的细节,保证用户的 ResourceEventHandler 代码中会接收到 Controller 监控的资源的完整数据,包括启动 Controller 前已有的资源数据,以及之后的资源变化。【就是不用自己发请求了】

        • ResourceEventHandler 中收到的消息中只有资源对象的 key,用户在 Controller 中可以使用该 key 为关键字,通过 Indexer 查询本地缓存中的完整资源对象。

  • SharedInformer

    • 背景

      • 如果在一个应用中有多处相互独立的业务逻辑都需要监控同一种资源对象,用户会编写多个 Informer 来进行处理。这会导致应用中发起对 K8s API Server 同一资源的多次 ListAndWatch 调用,并且每一个 Informer 中都有一份单独的本地缓存,增加了内存占用。

      • K8s 在 client go 中基于 Informer 之上再做了一层封装,提供了 SharedInformer 机制。采用 SharedInformer 后,客户端对同一种资源对象只会有一个对 API Server 的 ListAndWatch 调用,多个 Informer 也会共用同一份缓存,减少了对 API Server 的请求,提高了性能。

    • SharedInformerFactory 中有一个 Informer Map。当应用代码调用 InformerFactory 获取某一资源类型的 Informer 时, SharedInformer 会判断该类型的 Informer 是否存在,如果不存在就新建一个 Informer 并保存到该 Map 中,如果已存在则直接返回该 Informer(参见 SharedInformerFactory 的  InformerFor  方法)。因此应用中所有从 InformerFactory 中取出的同一类型的 Informer 都是同一个实例。

  • InformerController runtime 和 Kubebuilder 来编写 Controller 的区别

    • Informer:直接使用 Informer 编写 Controller 需要编写更多的代码,因为我们需要在代码处理更多的底层细节,例如如何在集群中监视资源,以及如何处理资源变化的通知。但是,使用 Informer 也可以更加自定义灵活,因为我们可以更细粒度地控制 Controller 的行为。

    • Controller runtime:Controller runtime 是基于 Informer 实现的,在 Informer 之上为 Controller 编写提供了高级别的抽象和帮助类,包括 Leader Election、Event Handling 和 Reconcile Loop 等等。使用 Controller runtime,可以更容易地编写和测试 Controller,因为它已经处理了许多底层的细节。

    • Kubebuilder:和 Informer 及 Controller runtime 不同,Kubebuilder 并不是一个代码库,而是一个开发框架。Kubebuilder 底层使用了 controller-runtime。Kubebuilder 提供了 CRD 生成器代码生成器等工具,可以帮助开发者自动生成一些重复性的代码和资源定义,提高开发效率。同时,Kubebuilder 还可以生成 Webhooks,以用于验证自定义资源。

3、WebHook

  • 参考:链接链接。最佳实践:链接

  • 定义

    • Admission webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。 可以定义两种类型的 admission webhook,即  validating admission webhook 和 mutating admission webhook。 Mutating admission webhook(修改) 会先被调用。它们可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。

    • Webhook就是一种HTTP回调,用于在某种情况下执行某些动作,Webhook不是K8S独有的,很多场景下都可以进行Webhook,比如在提交完代码后调用一个Webhook自动构建docker镜像

image.png

  • 向 Api Server 注册 Admission Webhook

    • Apiserver 如何知晓服务存在,如何调用接口,答案是 ValidatingWebhookConfiguration。通过往 Kubernetes 集群写入该协议,最终 apiserver 会在其 ValidatingAdmissionWebhook controller 模块注册好我们的 webhook,注意以下几点:

      • apiserver 只支持 HTTPS webhook,因此必须准备 TLS 证书,一般使用 Kubernetes CertificateSigningRequest 或者 cert-manager 获取

      • clientConfig.caBundle 用于指定签发 TLS 证书的 CA 证书,如果使用 Kubernetes CertificateSigningRequest 签发证书,自 kube-public namespace clusterinfo 获取集群 CA,base64 格式化再写入 clientConfig.caBundle 即可; 如果使用 cert-manager 签发证书,cert-manager ca-injector 组件会自动帮忙注入证书。

      • 为防止自己拦截自己的情形,使用 objectSelector 将 server Pod 排除

      • 集群内部署时,使用 service ref 指定服务

      • 集群外部署时,使用 URL 指定 HTTPS 接口

      • apiVersion: admissionregistration.k8s.io/v1
        kind: ValidatingWebhookConfiguration
        metadata:
          name: denyenv
          annotations:
            ## for cert-manager CA injection
            cert-manager.io/inject-ca-from: default/denyenv-tls-secret
        webhooks:
          - admissionReviewVersions:
              - v1
            clientConfig:
              caBundle: "<Kubernetes CA> or <cert-manager CA>"
              url: 'https://192.168.1.10:8000/validate' # 集群外部署,使用此方式时,注释 service ref
              service:                                  #---------------------#             
                name: denyenv                           #---------------------#             
                namespace: default                      #       集群内部署      #            
                port: 443                               # 使用此方式时,注释 url #            
                path: /validate                         #---------------------#            
            failurePolicy: Fail
            matchPolicy: Exact
            name: denyenv.zeng.dev
            rules:
              - apiGroups:
                  - ""
                apiVersions:
                  - v1
                operations:
                  - CREATE
                resources:
                  - pods
                scope: '*'
            objectSelector:
              matchExpressions:
                - key: app
                  operator: NotIn
                  values:
                    - denyenv
            sideEffects: None
            timeoutSeconds: 3

  • Kubernetes CertificateSigningRequest 签发 TLS 证书

    • Kubernetes 本身就有自己的 CA 证书体系,且支持 TLS 证书签发。我们要做的就是使用 openssl 生成服务私钥、服务证书请求并巧用 Kubernetes CA 签名服务证书

    • 不想写了,参考:链接

  • cert-manager 签发 TLS 证书

    • Kubernetes 证书有效期为 1 年,复杂的生产环境可以考虑使用 cert-manager ,因为它具有证书自动更新、自动注入等一系列生命周期管理功能。

    • 同上,链接

  • 理解

    • Webhook是一种回调,使用Api Server调用的时候会主动去调用定义好的WebHook(通过 ValidatingWebhookConfiguration 或者 MutatingWebhookConfiguration 动态配置哪些资源要被哪些准入 Webhook 处理,可以向写好的某个url发送请求并接受返回)

4、kuberbuilder

  • GV & GVK & GVR 【链接

    • GV: Api Group & Version

      • API Group 是相关 API 功能的集合

      • 每个 Group 拥有一或多个 Versions

    • GVK: Group Version Kind

      • 每个 GV 都包含 N 个 api 类型,称之为 Kinds,不同 Version 同一个 Kinds 可能不同

    • GVR: Group Version Resource

      • Resource 是 Kind 的对象标识,一般来 Kind 和 Resource 是 1:1 的,但是有时候存在 1:n 的关系,不过对于 Operator 来说都是 1:1 的关系

    • apiVersion: apps/v1 # 这个是 GV,G 是 apps,V 是 v1 
      kind: Deployment    # 这个就是 Kind 
      sepc:               # 加上下放的 spec 就是 Resource了
         ...
      
    • 根据 GVK K8s 就能找到你到底要创建什么类型的资源,根据你定义的 Spec 创建好资源之后就成为了 Resource,也就是 GVR。GVK/GVR 就是 K8s 资源的坐标,是我们创建/删除/修改/读取资源的基础。

  • kubebuilder实践

    • 解压安装【注:目前kubebuilder仅支持linux】

    • 创建项目

      • 创建目录

      • 初始化命令:`kubebuilder init --domain my.domain`

      • . 
        ├── Dockerfile # 用于构建控制器镜像的 Dockerfile
        ├── Makefile # 用于控制器构建及部署的 Makefile。这里定义了很多脚本命令,例如运行测试,开始执行等 
        ├── PROJECT  # 用于生成组件的 kubebuilder 元数据 
        ├── config # 采用 Kustomize YAML 定义的配置
        │   ├── default    # 一些默认配置,控制器相关, 当 make deploy 将 apply 此目录 yaml 
        │   ├── manager    # 部署 crd 所需的 yaml 
        │   ├── prometheus # 监控指标数据采集配置 
        │   └── rbac # 部署所需的 rbac 授权 yaml 
        ├── go.mod # Go Mod 配置文件,记录依赖信息
        ├── go.sum 
        ├── hack 
        │   └── boilerplate.go.txt 
        └── main.go # 程序入口
        

    • 创建Api(CRD和Controller)

      • 创建GVK:kubebuilder create api --group apps --version v1 --kind Application

      • . 
        ├── api 
        │   └── v1 
        │       ├── application_types.go # 这里是定义 spec 的地方,API 类型文件, 主要关注 Spec 与 Status 结构体 
        │       ├── groupversion_info.go # GV 的定义,一般无需修改。此文件包含了 Group Version 的一些元信息
        │       └── zz_generated.deepcopy.go # 自动生成的 runtime.Object 实现
        ├── config 
        │   ├── crd # 自动生成的 crd 文件,不用修改这里,只需要修改了 v1 中的 go 文件之后执行 make generate 即可。当 make install 将 apply 此目录 yaml 
        │   ├── default 
        │   ├── manager 
        │   ├── prometheus 
        │   ├── rbac 
        │   └── samples # CR 样例,这里是 crd 示例文件,可以用来部署到集群当中 
        ├── controllers # 控制器逻辑所在目录
        │   ├── application_controller.go # 在这里实现 controller 的逻辑,控制器 reconcile 逻辑实现所在文件 
        │   └── suite_test.go # 这里写测试
        

    • 实现Controller

      • 编写 CRD 并将其部署至 K8S 集群中

        • ${ProjectName}/api/${Version}/${Kind}_types.go 文件(api/v1/application_types.go)中${Kind}Spec 与 ${Kind}Status 这两个结构体分别代表着一个 k8s 自定义资源所必须的两个部分specstatus

        • 我们需要将设计好的属性字段添加到所提及的上述结构体中,

        • 然后在此之后执行 make 将会更新 config/crd/ 内的 yaml 定义

      • 编写Controller

        • 原理:将自定义资源(CR)定义的预期状态与当前资源的实际状态进行比对, 若无差异则略过, 若有差异则需要将实际状态更新为预期的状态值,这个比对更新的过程在 Controller 中被称为调谐(Reconcile)

        • kubebuilder 已经帮我们实现了 Operator 所需的大部分逻辑,我们只需要在 ${ProjectName}/controllers/${Kind}_controller.go 文件的 Reconcile 中实现业务逻辑就行了

      • 测试运行

        • 执行如下命令将 config/crd 中 CRD 部署到 k8s 集群中: make install

        • 本地运行 controller: make run(go run ./main.go )

        • 安装自定义资源(CR):  kubectl apply -f config/samples/

      • 构建及部署(略)

        • 构建 docker 镜像及将镜像推送镜像仓库:

          • $ make docker-build docker-push IMG=<some-registry>/<project-name>:tag

        • 使用 docker 镜像, 部署 controller 到 k8s 集群:

          • $ make deploy IMG=<some-registry>/<project-name>:tag

        • 卸载 CRDs:

          • $ make uninstall

;