一文搞定Kubernetes operator

469 阅读11分钟

operator简介

Kubernetes Operator这一概念是由CoreOS的工程师于2016年提出的,这是一种原生的方式来构建和驱动Kubernetes集群上的每一个应用,它需要特定领域的知识。它提供了一种一致的方法,通过与Kubernetes API的紧密合作,自动处理所有应用操作过程,而不需要任何人工干预。

换句话说Operator是一种包装、运行和管理k8s应用的一种方式。 它涵盖了 CRD(CustomResourceDeftination) + AdmissionWebhook + Controller ,并以 Deployment 的形式部署到K8S中。

  • CRD 用来定义声明式API(yaml),程序会通过该定义一直让最小调度单元(POD)趋向该状态;
  • AdmissionWebhook 用来拦截请求做 mutate(修改)提交的声明(yaml)和 validate(校验)声明式的字段;
  • Controller 主要的控制器,监视资源的 创建 / 更新 / 删除 事件,并触发 Reconcile函数作为响应。整个调整过程被称作 Reconcile Loop(协调一致的循环),其实就是让 POD 趋向CRD定义所需的状态;

custom resource definitions

k8s中定义了一系列的资源对象,比如pod,deployment, svc等等,使用kubectl api-resources可以看到当前版本的k8s所支持的全部资源以及分组等信息。

下图是执行该命令后所展示的资源对象的一部分: image.png 在这些资源对象中,有一种名叫custom resource definitions的对象,简称crd。该资源的作用是让用户可以自定义自己的资源对象,从而方便用户的不同场景需求。我们kubectl  get crd -A就可以看到一些已经安装的crd

image.png

crd编程

---  
apiVersion: apiextensions.k8s.io/v1  
kind: CustomResourceDefinition  
metadata:  
  annotations:  
    controller-gen.kubebuilder.io/version: v0.9.2  
  creationTimestamp: null  
  # metadata.name的内容是由"plural.group"构成,如下,apps是复数名,myapp.woqutech.com是分组名,名称规则不可改变,改变即无法创建出来
  name: apps.myapp.woqutech.com  
spec:  
  # 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本/resources
  group: myapp.woqutech.com  
  names:  
    kind: App  
    listKind: AppList  
    plural: apps  
    shortNames:  
    - demo  
    singular: app  
  # 作用域,可用值为Cluster和Namespaced`
  scope: Namespaced  
  versions:
  # 命令行打印的数据列  
  - additionalPrinterColumns:  
    - description: name  
      jsonPath: .spec.name  
      name: Name  
      priority: 1  
      type: string  
    - jsonPath: .metadata.creationTimestamp  
      name: Age  
      type: date 
    # 形如vM[alphaN|betaN]格式的版本名称,例如v1或vlalpha2等 
    name: v1  
    # 该资源的数据格式(schema)定义,必选字段
    schema:  
      # 用于校验字段的schema对象,格式请参考相关手册
      openAPIV3Schema:  
        description: App is the Schema for the apps API  
        properties:  
          apiVersion:  
            type: string  
          kind:  
            type: string  
          metadata:  
            type: object  
          spec:  
            description: AppSpec AppSpec定义了应用程序所需的状态  
            properties:  
              foo:  
                default: test  
                description: 这是一个demo字段  
                type: string  
              name:  
                description: app spec的名字  
                type: string  
            required:  
            - name  
            type: object  
          status:  
            description: AppStatus AppStatus定义了App的观察状态  
            type: object  
        type: object 
    # 是否允许通过RESTful API调度该版本,必选字段 
    served: true  
    # 将自定义资源存储于etcd中时是不是使用该版本
    storage: true  
    subresources:  
      status: {}

这就是所谓的CRD编程,就是按照custom resource definitions规定的格式,自己写一个资源对象。

kubectl apply -f demo-crd.yaml

# 可以访问k8s的etcd上存储的数据,查看到crd的内容
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key get /registry/apiextensions.k8s.io/customresourcedefinitions/apps.myapp.woqutech.com --prefix

kubectl get crd

crd应用

echo "apiVersion: myapp.woqutech.com/v1  
kind: App  
metadata:  
  name: app-sample  
spec:  
  name: test  
  foo: bar " | kubectl -n qfusion apply -f -

应用cr后,发现只是将cr创建出来了,但是并没有做其它的变化,比如创建新的pod,那么如何将其他资源启动起来呢, 此时我们就要自己写一个控制器,控制器就是一个一直在工作的生产者消费者模型,监听着对象的变化,当对象发生改变时,就会根据对象的期望,去做相应的处理,最终达成对象的期望。例如deployment也是一个控制器,当我们将replicas改为2时,会给我们创建出两个pod。我们编写控制器的代码过程,就是operator开发。

webhook

Operator中的webhook,外部对CRD资源的变更,在Controller处理之前都会交给webhook提前处理,流程如下图,该图来自《Getting Started with Kubernetes | Operator and Operator Framework》

image.png 再来看看webhook具体做了哪些事情,如下图,kubernetes官方博客明确指出webhook可以做两件事:修改(mutating)和验证(validating)

image.png

两种准入控制

  1. 变更准入控制 MutatingAdmissionWebhook: 变更性质的准入 Webhook
  2. 验证准入控制 ValidatingAdmissionWebhook: 验证性质的准入 Webhook 执行的顺序是先执行 MutatingAdmissionWebhook 再执行 ValidatingAdmissionWebhook。

operator整体运行流程图

image.png controller能够追踪到的change events

  1. add event
  2. update event
  3. delete event

operator开发脚手架

在开始之前,首先介绍一下 operator framework。 编写Kubernetes operator涉及到处理Kubernetes API,如创建、观察、列出对象等。 为了解决这个问题,您可以使用像client-go和controller-runtime这样的抽象库在Kubernetes集群上执行CRUD操作。但是,即使从头开始使用它们来编写一个成熟的operator,最终也会涉及大量的复杂性、学习曲线和样板代码来处理。

因此,为了避免这种麻烦,有多种 operator framework可以帮助我们简化和加快编写operator的过程。它的主要意义在于帮助开发者屏蔽了一些通用的底层细节,不需要开发者再去实现消息通知触发、失败重新入队等,只需关注被管理应用的运维逻辑实现即可。

主流的 operator framework 主要有两个:

  1. kubebuilder 
  2. operator-sdk

两者实际上并没有本质的区别,它们的核心都是使用官方的 controller-tools 和 controller-runtime。不过细节上稍有不同,比如 kubebuilder 有着更为完善的测试与部署以及代码生成的脚手架等;而 operator-sdk 对 ansible operator 这类上层操作的支持更好一些。

kubebuilder

kubebuilder是什么

宗上所述Kubebuilder 它是一个开发Operator的脚手架, 可以快速生成 CRD、Webhook、Controller的代码与配置,并且提供了k8s go-client

kubebuilder安装

# download kubebuilder and install locally.
curl -L -o kubebuilder "https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)"
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

kubebuilder使用

创建项目

创建目录 不允许中文、空格、特殊符号,下划线,只允许中划线“-”

mkdir -p ./demo-api-operator
cd ./demo-api-operator

init 项目,创建域(domain)

go mod init gitlab.woqutech.com/demo-api-operator
kubebuilder init --domain woqutech.com

目录结构

image.png

api 存放crd定义的结构体,webhook中的准入控制函数实现

bin 存放一些帮助生成k8s yaml的二进制工具

config 存放生成好的crd yaml, controller yaml,controller rbac yaml, webhook yaml等等

controllers 存放所有的controller程序

hack 存放一些侵入式的脚本

创建api项目

创建 API,创建group、version 和 kind

kubebuilder create api --group myapp --version v1 --kind App
定义crd字段

通过注释标记添加crd字段的普通参数校验

image.png 所有参数校验的地址:

实现controller-Reconcile逻辑

{project}/controller/{kind}_controller.go

image.png

finalizers属性,处理cr删除前的一些额外操作

创建webhook项目

创建webhook

# --defaulting: 开启MutatingAdmissionWebhook
# --programmatic-validation: 开启ValidatingAdmissionWebhook
kubebuilder create webhook --group myapp --version v1 --kind App --defaulting --programmatic-validation
实现变更准入控制逻辑

{project}/api/{version}/{kind}_webhook.go

image.png

实现验证准入控制逻辑

{project}/api/{version}/{kind}_webhook.go

image.png

启动项目

# 生成crd定义的yaml并安装到远端机器
make installCluster

# 本地运行operator
go run main.go --kubeconfig=kube-config

operator原理

controller-runtime

controller-runtime 是基于 client-go 的 K8s 控制器开发框架, 是 Kubernetes 社区提供可供快速搭建一套 实现了controller 功能的工具,无需自行实现Controller的功能了;在 Kubebuilder 与 Operator SDK 也是使用 controller-runtime

image.png

主要组成

controller-runtime 主要组成是需要用户创建的 Manager 和 Reconciler 以及 Controller Runtime 自己启动的 Cache 和 Controller

  • Manager:是用户在初始化时创建的,用于启动 Controller Runtime 组件
  • Reconciler:是用户需要提供来处理自己的业务逻辑的组件(即在通过 code-generator 生成的api-like而实现的controller中的业务处理部分)。
  • Cache:一个缓存,用来建立 Informer 到 ApiServer 的连接来监听资源并将被监听的对象推送到queue中。
  • Controller: 一方面向 Informer 注册 eventHandler,另一方面从队列中获取数据。controller 将从队列中获取数据并执行用户自定义的 Reconciler 功能。

client-go

client-go是对K8s集群的二次开发工具,所以client-go是k8s开发者的必备工具之一。client-go实现对kubernetes集群中资源对象(包括deployment、service、ingress、replicaSet、pod、namespace、node等)的增删改查等操作。大部分对kubernetes进行前置API封装的二次开发都通过client-go这个第三方包来实现。

主要组成

client client-go 支持4种 Client 客户端对象与 Kubernetes API Server 进行交互,Client 交互对象如下所示:

image.png RESTClient 是最基础的客户端。ClientSet、DynamicClient、DiscoveryClient 客户端都是基于 RESTClient 进行实现的。

infomer Informer是Kubernetes API客户端中一种重要的机制,它可以实现对资源对象的监视和事件通知。当Kubernetes集群中的资源对象发生变化时,Informer可以及时地获取到这些变化,并将这些变化以事件的形式通知给相关的监听器。Informer通过调用API Server提供的REST接口,以及Kubernetes中定义的watch机制,实现了对集群资源对象的全面监视。

indexer Indexer是client-go中用于本地缓存资源对象的一种方式。它支持多种索引方式,并且可以使用函数: func(obj interface{}) ([]string, error) 进行索引。在检索时,需要使用相同的indexName参数。借助informer,indexer就可以维护一个特定资源的本地缓存,例如pod、namespace等。这种方法省去了每次get pod都要访问api-server的过程,从而减小了api-server的压力。

lister Lister是对Indexer的封装,提供了一种方便的方式来获取已经索引的Kubernetes资源对象列表。 具体而言,Lister是一个接口,包含了获取所有已索引对象的列表以及根据名称获取单个对象的方法。这些方法可以帮助在应用程序中快速访问已经缓存的资源对象,而无需直接与Indexer交互。

Lister的主要功能包括:

  1. 提供方便的接口:Lister接口的方法定义清晰简洁,使用起来非常方便,可以快速地获取已经索引的资源对象列表。
  2. 提高代码可读性:通过使用Lister接口,代码可读性得到提高。开发者可以更加专注于业务逻辑,而无需关注底层的Indexer实现细节。
  3. 提高代码复用性:由于Lister接口已经提供了通用的方法,因此可以更容易地在不同的代码模块中重用相同的逻辑,减少代码重复。

总之,Lister作为client-go包中的一个重要组件,可以帮助开发者更加高效地处理Kubernetes资源对象,提高代码的可读性和可重用性。

code-generator

GitHub - kubernetes/code-generator: Generators for kube-like API types

code-generator 就是 Kubernetes 提供的一个用于代码生成的项目,它提供了以下工具为 Kubernetes 中的资源生成代码:

  • deepcopy-gen: 生成深度拷贝方法,为每个 T 类型生成 func (t* T) DeepCopy() *T 方法,API 类型都需要实现深拷贝
  • client-gen: 为资源生成标准的 clientset
  • informer-gen: 生成 informer,提供事件机制来响应资源的事件
  • lister-gen: 生成 Lister,为 get 和 list 请求提供只读缓存层(通过 indexer 获取)
./generate-groups.sh all raw-demo-operator/pkg/generated raw-demo-operator/pkg/apis myapp.woqutech.com:v1 --go-header-file=./hack/boilerplate.go.txt --output-base ../  -v 10

参数说明

  1. all: 其代表着4种标准代码生成器deepcopy-gen,client-gen,informer-gen,lister-gen
  2. raw-demo-operator/pkg/generated: 包名+路径, lister和informer代码框架的包名;
  3. raw-demo-operator/pkg/apis: 包名+路径, API组的基础包名
  4. myapp.woqutech.com:v1: API group与版本号;
  5. --go-header-file: 自定义代码所用到的版权信息头
  6. --output-base: 用于代码生成器查找包输出的基础路径;
  7. -v 10: 日志输出等级

自定义controller运行流程

image.png 上图的流程解析:

  1. Reflector(反射器) 通过 http trunk 协议监听k8s apiserver 服务的资源变更事件, 事件主要分为三个动作 ADDUPDATEDELETE;
  2. Reflector(反射器) 将事件添加到 Delta 队列中等待;
  3. Informer 从队列获取新的事件;
  4. Informer 调用 Indexer (索引器, 该索引器内包含Store对象), 默认索引器是以namespace 和 name 作为每种资源的索引名;
  5. Indexer 通过调用 Store 存储对象按资源分类存储;