K8s 的 Operator 分享

写在前面

这篇分享是我在公司内部给其他不太熟悉K8s的新同事做分享的文章,因为面向的基本都是硕士刚毕业对K8s不熟悉的校招生,因此内容比较浅显,不涉及太底层的东西,但是看了这篇文章以后基本能对K8s的Opeator做一个基本的了解,但是如果想要掌握一种非常专业的技术,光看几篇分享的文章肯定是不行的,因此建议大家还是尽量看官方文档

什么是 K8s 的 CRD 和 Operator?

Kubernetes 的 CRD,全称 Custom Resource Definition,是 Kubernetes 的一种资源,允许用户自定义新的资源类型。除了 CRD 本身,用户还需要提供一个 Controller,以实现自己的逻辑。CRD 允许用户基于已有的 Kubernetes 资源(例如 Deployment、Configmap 等)拓展集群能力。它是一种自定义资源的定义,用于描述用户定义的资源是什么样子。

Operator=CRD+Controller.

怎么理解这个CRD+Controller呢,就是你通过编写yaml文件,然后apply这个yaml文件的方式可以生成一个CRD,这个yaml文件要满足一定的规则,接下来我会详细讲到CRD的yaml的编写,在apply生成一个CRD以后,你可以再基于这个CRD来创建一个CR(也是apply一个yaml的方式),在CR创建成功以后你的具体业务逻辑就可以开始执行了,业务逻辑的执行是依赖于CRD对应的Controller(这里还涉及到K8s的list-watch机制等等,后续我会写一些文章尽量简单的说清楚这个list-watch机制),这个Controller会监测对应CRD,如果有基于这个CRD的CR创建,那么Controller就会去执行预先在代码中定义好的逻辑(Controller都是基于Golang实现)

CRD 怎么使用

在这里插入图片描述
我们以一个经典(k8s 官网的例子)的 CRD 为例

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
  name: crontabs.stable.example.com
spec:
  # 组名称,用于 REST API: /apis/<组>/<版本>
  group: stable.example.com
  # 列举此 CustomResourceDefinition 所支持的版本
  versions:
    - name: v1
      # 每个版本都可以通过 served 标志来独立启用或禁止
      served: true
      # 其中一个且只有一个版本必需被标记为存储版本
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
    plural: crontabs
    # 名称的单数形式,作为命令行使用时和显示时的别名
    singular: crontab
    # kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
    kind: CronTab
    # shortNames 允许你在命令行使用较短的字符串来匹配资源
    shortNames:
    - ct

现在我们将它保存到 resourcedefinition.yaml,并且执行:

kubectl apply -f resourcedefinition.yaml

这个时候 K8S 会分析 CRD 定义,检查其有效性。包括 API 版本、元数据、名称、定义规范等,如果通过的话 API Server 会将 CRD 对象存储起来,并持久化在 Etcd 中。接下来你会得到这样的反馈:
在这里插入图片描述
这个时候一个新的受namespace约束的 RESTful API 端点会被创建在:
/apis/stable.example.com/v1/namespaces/*/crontabs/…
这个时候就代表我们在我们的 k8s 集群中创建了一个名为 crontabs.stable.example.com 的 CRD,当我们使用

kubectl get crds

的时候可以看到下图的结果,注意:CRD 是不区分namespace的
在这里插入图片描述
现在我们已经成功在自己的 K8s 集群中创建了一个 CRD,现在我们可以基于这个 CRD 创建一个自定义对象 CR,现在我们创建一个 my-crontab.yaml 的文件,内容是:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  # 这个字段的内容需要满足上面CRD的规约
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image

apply 这个 yaml(注意:这个时候就是区分 ns 的了),接着执行就可以看到相应内容:

kubectl get ct #这个ct是crontab的short-name

在这里插入图片描述

到这里我们就创建了一个 CRD 和基于这个 CRD 创建一个 CR 了,那这样的 CR 有什么用呢,上面这个例子不明显,我们这里以 ArgoWorkflow 为例:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
  name: workflowtemplates.argoproj.io
spec:
  # 组名称,用于 REST API: /apis/<组>/<版本>
  group: argoproj.io
  names:
    # kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
    kind: WorkflowTemplate
    listKind: WorkflowTemplateList
    # 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
    plural: workflowtemplates
    shortNames:
    - wftmpl
    # 名称的单数形式,作为命令行使用时和显示时的别名
    singular: workflowtemplate
  # 这里可以是ns级别或者cluster级别
  scope: Namespaced
  # 列举此 CustomResourceDefinition 所支持的版本
  versions:
  - name: v1alpha1
    schema:
      openAPIV3Schema:
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            x-kubernetes-map-type: atomic
            x-kubernetes-preserve-unknown-fields: true
        required:
        - metadata
        - spec
        type: object
    # 每个版本都可以通过 served 标志来独立启用或禁止
    served: true
    # 其中一个且只有一个版本必需被标记为存储版本
    storage: true

现在我们将它写入 wftmpl-crd.yaml,并且执行

kubectl apply -f wftmpl-crd.yaml -n argo

我们创建了一个名为 workflowtemplates.argoproj.io 的 CRD 资源,基于这个 CRD 资源我们就可以实现 一个Workflow的工作流程,具体是通过创建一个 CR 来实现,当然,不是单独这样一个 yaml 文件就可以实现复杂的函数包构建功能。实际功能的实现是通过后面的逻辑代码来完成的,具体如何实现,接着往下看。

Operator 是怎么使用的

暂时无法在飞书文档外展示此内容

一个 Operator 的安装和使用有以下几个步骤:

  • 创建一个 ServiceAccount,创建一个 Role,创建一个 RoleBinding 将 sa 和 Role 绑定在一起
  • 通过 apply 一个 CRD 的 yaml 文件部署 CRD
  • 通过apply一个CR的yaml文件安装一个 CR
  • 安装 Controller。通过 apply 一个 Deployment.yaml,这个 Deployment 中定义好了 Controller 的镜像,最终 Controller 会以 Pod 的形式在 K8s 集群中运行
  • 修改 CR 的内容并重新 apply
  • Controller 监控到 CR 的变化,修改集群状态(即「调谐」)
    针对这几个步骤,我下面有个Mysql的Operator的小例子,这里先简单介绍一下例子的流程:apply一个CRD的yaml创建Mysql的CRD、apply一个CR的yaml创建对应的CR、apply一个yaml创建一个Controller(Controller的代码是自己实现的)、修改CR的yaml中的replicas把mysql容器的数量从2变成3再重新apply,这个时候Controller监测到CR变化,开始调谐,把Mysql的pod数量从2变为3

如何自己创建一个 Operator

使用 kubebuilder

kubebuilder 是一个快速实现 kubernetes Operator 的框架,通过 kubebuilder 我们能够快速定义 CRD 资源和实现 Controller 逻辑
kubebuilder 自带两个工具:controller-runtime与controller-tools,我们可以通过controller-tools来生成大部分的代码,从而我们可以将我们的主要精力放置在 CRD 的定义和编写 controller 的 reconcile 逻辑上。reconcile 翻译成汉语是「调谐」,意思是指将资源的 status 通过一系列的操作调整至与定义的 spec 一致。
下图是一个 Controller 的调谐图示意:
这里涉及到list-watch,关于list-watch可以看看这篇文章
在这里插入图片描述

这里简单介绍一下一次「调谐」的逻辑:

  1. reflector 通过ListAndWatch方法去监听指定的 Object
  2. reflector 将所监听到的 event,包括对 object 的Add,Update,Delete的操作 Push 到 DeltaFIFO 这个 Queue 中
  3. Informer 首先会解析 event 中的 action 和 object
  4. Informer 将解析的 object 更新到 local store,也就是本地 cache 中的数据更新
  5. 然后 Informer 会执行 Controller 在初始化 Infromer 时注册的 ResourceEventHandler
  6. ResourceEventHandler 中注册的 callback 会将对应变化的 object 的 key 存入其初始化的一个 workQueue
  7. 最终 controller 会循环进行 reconcile,就是从 workQueue 不停地 pop key,然后去 local store 中取到对应的 object,然后进行处理,最终多数情况会再通过 client 去更新这个 object
    如果不理解调谐的过程也不要紧,通俗一点来说就是修改了 yaml 文件的内容再重新 apply(比如把 replicas 的值从 2 改成了 3),controller 就会开始调谐,新增一个 pod(把 pod 数从 2 变成 3)

最佳实践

现在我们演示使用 kubebuilder 来创建 mysql 的 operator,期望这个 mysql-operator 可以实现 deployment 的创建和更新,以及 mysql 实例数的扩缩容。
5.1 环境准备

  • 有一个 K8s 集群
  • 安装 kubectl 并且可以连接到 K8s 集群上
  • 安装 kubebuilder:brew install kubebuilder(windows自行百度一下)

5.2 初始化项目

kubebuilder init --domain tutorial.kubebuilder.io
kubebuilder create api --group batch --version v1 --kind Mysql # 接下来连续点击两次y

5.3 编写 CRD 资源
编写 CRD 资源的定义主要是在mysql_types.go文件中,编写 controller 逻辑主要是在mysql_controller.go文件中
在这里插入图片描述

我们先要编写一个 CR 的定义,写个 yaml 来描述我们的期望
在这里插入图片描述

定义一下 spec,MysqlSpec 这个结构体中的字段需要跟上面 batch_v1_mysql.yaml 文件中的 spec 对应(可以多不可以少)
在这里插入图片描述

定义一下 status
在这里插入图片描述
编写 Controller 的逻辑
kubebuilder 为我们在mysql_controller.go文件中预先生成了两个函数,分别是 Reconcile()和 SetupWithManager()

  1. 先编写 Reconcile 函数
    在这里插入图片描述

我在代码里面把同步逻辑抽出来写成了 syncMysql,这个函数实现了:

  • 查询 deployment
  • 如果 deployment 不存在,则根据 mysql 的 spec 创建 deployment
  • 如果 deployment 存在,则更新其 spec 使其与 mysql 的 spec 一致
  • 更新 mysql 的 status
    (syncMysql函数内部又调用了其他函数,代码太长这儿就不贴图了,想看的可以:https://github.com/helin0815/learncrds/tree/main/mysqsl-crd)
  1. 修改一些 SetUpWithManager 函数
    这儿如果只是测试验证的话其实不修改也行。修改后的代码使用了 WithOptions 方法来设置控制器的选项,其中 MaxConcurrentReconciles: 3 表示控制器的最大并发调谐(reconciliation)数量为 3
    在这里插入图片描述

  2. 执行make install 安装 CRD。每次修改mysql_types.go以后都要重新执行一下make install
    在这里插入图片描述

  3. 执行make run运行 Controller。每次修改mysql_controller.go以后都要重新执行make run

  4. 通过 apply -f yaml 的方式部署 CR 到集群里面
    在这里插入图片描述

我以前已经执行过了,所以下面执行的时候报 unchanged
[图片]

  1. 查看 CRD 是否安装成功
    [图片]

  2. 修改 CR 的 yaml 中的内容,把 replicas 改成 2 再 apply 一下
    [图片]

  3. 现在再查看 pod 数量,就已经变成两个了
    [图片]

如果这里发现你的 pod 数量没变化,大概率是你忘记执行 make run 了,导致 controller 没启动,调谐没进行

  1. 部署到生产。到这里就已经体验了 kubebuilder 的基本功能,不过实际生产环境中 controller 一般都会运行在 kubernetes 环境内,像我这种运行在自己 macbook 的方式就不行了,现在尝试将其做成 docker 镜像.
    这里有个要求,需要有个 kubernetes 可以访问的镜像仓库,例如局域网内的 Harbor,或者公共的 hub.docker.com
    执行下面命令就可以将 controller 构建成镜像上传到自己的镜像仓库,以后就可以通过 image 来安装了
    make docker-build docker-push IMG=test.example.com/mysql:v0.1

这就是一个K8s的Operator的简单介绍,如果有不懂的可以评论区问我(我写这些文章的大部分原因是为了加深我自己的印象,但是别人有遇到问题的话问我我也会尽力解答)

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值