前言

这篇博客是属于对《Kubernetes Operator 进阶开发》的初步章节一个笔记总结,作为 2023 第一篇博文,也作为重新深入梳理 K8s 的基础部分,往后会逐步将之前的坑给填上

Operator的基本概念

作为最入门的篇章,我们将从写一个 Demo 开始,在这里将默认已经存在了一个可用的集群环境,当然如果在开始学习之前,你能有一些 Client-Go 基础那就再好不过了

控制器模式

控制器模式在日常生活中的应用非常广泛,也是根据原书的例子进行举例,当然这里缩减了非常多,也加入了些个人的想法

夏天我们需要调整空调制冷,现在过程如下:

  1. 空调启动后设置一个温度,例如 25 度
  2. 空调检测室温,确定室温是否高于目标值
  3. 如果高于目标阈值,例如室温 27 度,则开始制冷操作
  4. 如果低于目标阈值,保持静默到下一个探测周期

这其实就是控制器模式典型的工作流程,外部输入一个 “期望值”,期间一个 “控制器” 不断按照 “周期” 观测 “环境状态” 的 “实际值” 和 “期望值” 之间的差异,然后不断调整两者,以便维持平衡,这个过程则被称为 “调谐”

Kubernetes中的控制器与CRD

Kubernetes 中通过 “声明式API” 定义了一系列的资源对象,然后通过许多的 Controllers 来 “调谐” 这些资源对象的实际状态以向期待状态靠拢,从而实现整个集群 “尽可能” 靠拢配置中声明的期望状态,注意 CRD (Custom Resource Definition)是 Kubernetes 提供的一种机制,允许用户定义新的资源类型,通过定义 CRD,你可以扩展 Kubernetes 的 API,创建和管理自定义资源(Custom Resource,CR),Controller 则是一个自定义的控制器程序,它监视并响应 Kubernetes 集群中的资源变化。对于自定义资源,Controller 用于实现资源的创建、更新和删除逻辑

还是上面空调的例子,我们可以定义一个新的资源类型 AirConditioner,它包含空调的目标温度和当前状态等信息,CR 是基于 CRD 实例化的具体资源。每个空调的具体配置实例就是一个 CR,其包含了目标温度和当前温度等具体值,同样给空调 A (my-airconditioner-A) 和空调 B (my-airconditioner-B) 分别创建了两个温度,每个温度集合的格式都需要符合 CRD 的声明,实例中 Controller 是用于监控 CR 并执行相应操作的程序,它负责监控 AirConditioner 资源,并根据当前温度和目标温度来决定是否启动制冷

一个Deployment的创建

我们都知道集群中有个组件,叫做 Kube-Controller-Manager,这个组件就是一系列控制器的集合,现在可以重新梳理一下这个面试中经常被问到的问题了

在编辑好一个 Deployment YAML 配置文件并使用 Kubectl 提交后,这个资源声明就被传递给了 Kube-ApiServer,接着 Kube-Controller-Manager 中的 Deployment 控制器监听到了消息,注意 K8s 中的组件传递原则 — 是上层组件先将自身置于被修改后的状态,即先假设资源已经被创建,而下层组件监听到了上游变化后,将自身或者环境状态 “调谐” 为上游一致,在这里 Deployment Controller 根据 Spec 字段的定义创建对应的 ReplicaSet 资源,而 RS Controller 监听到了 RS 的变化,并也根据 Spec 字段中声明创建了 Pod,这里其实还有一个 Kubelet 的环节在中间,只是这里被我们抽象掉了

Operator模式

Operator 模式能让用户通过自定义资源来管理自己的应用。这种模式的本质是将一个领域运维人员的运维经验,也就是把他们所维护的应用该怎么部署、出现异常后怎么去恢复等一系列过程在 Kubernetes 上的操作程序化,并交给自定义控制器去实现


下文为 2024 年 06 月重写,正好拿最近要写的一个项目当成记录

该项目会对于每一次集群资源的修改进行备份,如果出现了误操作例如删除等,可以从备份路径选择对应的 YAML 进行手动恢复,也是集群运维操作一道保险

让我们来了解一下整个 Operator 的实现过程:

  1. 定义一个名为 OptionsRecordController 的 Custom Resource(CRD),里面定义了我们需要监控的 Namespace 等参数
  2. 在 Controller 的代码逻辑中实现主要的逻辑,即 Watch Kube-ApiServer 的同时记录每次被修改前的资源声明
  3. 通过 Deployment 方式部署该 Operator 的 Controller 部分
  4. 编写一个 Kind 为 OptionsRecordController 的 CR 资源声明提交, 需要符合我们定义的 CRD 参数格式
  5. 测试 Controller 逻辑

这里的资源就是 Kubernetes API 的一个端点,包含一组特定类型对象的集合,比如 Pods 资源包含了 Pod 对象的集合。资源可以做 CURD 操作,对应了 ApiServer 代码中定义的某个结构体,如内存中的一个对象 ETCD 中的一组数据。而所谓自定义资源则是用户提交的一个类似 Deployment 声明的结构定义给 Kubernetes,并执行类似的操作逻辑(当然还是要看 Operator Controller 代码的实现)

从写一个Demo开始

Kubebuilder安装配置

Kubebuilder 是一个用于 Operator 程序构建和发布的工具,我们先将其安装到本地,然后利用 Kubebuilder 快速开发一个 Demo Operator。关于安装的方式,直接参考二进制安装即可,注意只能使用 Linux / Mac 平台。

Github Kubebuilder

$ kubebuilder
CLI tool for building Kubernetes extensions and tools.

Usage:
  kubebuilder [flags]
  kubebuilder [command]

Examples:
The first step is to initialize your project:
    kubebuilder init [--plugins=<PLUGIN KEYS> [--project-version=<PROJECT VERSION>]]

<PLUGIN KEYS> is a comma-separated list of plugin keys from the following table
and <PROJECT VERSION> a supported project version for these plugins.

                              Plugin keys | Supported project versions
------------------------------------------+----------------------------
                base.go.kubebuilder.io/v3 |                          3
          base.go.kubebuilder.io/v4-alpha |                          3
         declarative.go.kubebuilder.io/v1 |                       2, 3
  deploy-image.go.kubebuilder.io/v1-alpha |                          3
                     go.kubebuilder.io/v2 |                       2, 3
                     go.kubebuilder.io/v3 |                          3
               go.kubebuilder.io/v4-alpha |                          3
          grafana.kubebuilder.io/v1-alpha |                          3
       kustomize.common.kubebuilder.io/v1 |                          3
 kustomize.common.kubebuilder.io/v2-alpha |                          3

For more specific help for the init command of a certain plugins and project version
configuration please run:
    kubebuilder init --help --plugins=<PLUGIN KEYS> [--project-version=<PROJECT VERSION>]

Default plugin keys: "go.kubebuilder.io/v3"
Default project version: "3"

# 一些基础的配置信息
$ kubectl get nodes
NAME       STATUS   ROLES                         AGE   VERSION
hyper-01   Ready    control-plane,master,worker   62d   v1.23.15

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            30Gi       1.9Gi        10Gi        21Mi        18Gi        28Gi
Swap:             0B 

创建项目

我们直接使用 Kubebuilder 初始化一个新项目,注意需要配置好 go 环境与代理,并且 make / gcc / build-essential 等基础工具也是必备的

$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
$ mkdir operator && cd operator

$ kubebuilder init --domain=sxueck.com --repo=github.com/sxueck/options-record --owner sxueck
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO Get controller runtime:
...

ls -al
total 84
drwxrwxr-x  6 sxueck sxueck  4096 Jun 26 14:41 .
drwxr-x--- 21 sxueck sxueck  4096 Jun 26 09:50 ..
drwx------  2 sxueck sxueck  4096 Jun 26 14:41 cmd
drwx------  6 sxueck sxueck  4096 Jun 26 14:41 config
-rw-------  1 sxueck sxueck  1278 Jun 26 14:41 Dockerfile
-rw-------  1 sxueck sxueck   120 Jun 26 14:41 .dockerignore
-rw-------  1 sxueck sxueck   411 Jun 26 14:41 .gitignore
-rw-------  1 sxueck sxueck   710 Jun 26 14:41 .golangci.yml
-rw-------  1 sxueck sxueck  3150 Jun 26 14:42 go.mod
-rw-rw-r--  1 sxueck sxueck 18187 Jun 26 14:42 go.sum
drwx------  2 sxueck sxueck  4096 Jun 26 14:41 hack
-rw-------  1 sxueck sxueck  8537 Jun 26 14:41 Makefile
-rw-------  1 sxueck sxueck   341 Jun 26 14:41 PROJECT
-rw-------  1 sxueck sxueck  3103 Jun 26 14:41 README.md
drwx------  4 sxueck sxueck  4096 Jun 26 14:41 test

接着我们在目录下面继续执行命令,用以添加 API

$ kubebuilder create api --group apps --version v1 --kind OptionsRecordController
INFO Create Resource [y/n]
y
INFO Create Controller [y/n]
y
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO api/v1/optionsrecordcontroller_types.go
INFO api/v1/groupversion_info.go
INFO internal/controller/suite_test.go
INFO internal/controller/optionsrecordcontroller_controller.go
INFO internal/controller/optionsrecordcontroller_controller_test.go
INFO Update dependencies:
...

关于上面具体的参数,我们可以找到 Kubebuilder 生成的文件进行解读,从下面的文件名和文件内容,结合我们的 Deployment 的日常使用经验,这里引申出来了一个参数:GVK,在具体编码中我们常常会用到它们进行方法的初始化,这也是我在工程中踩了不少坑的经验

$ cat config/samples/apps_v1_optionsrecordcontroller.yaml
apiVersion: apps.sxueck.com/v1
kind: OptionsRecordController
metadata:
  labels:
    app.kubernetes.io/name: operator
    app.kubernetes.io/managed-by: kustomize
  name: optionsrecordcontroller-sample
spec:
  # TODO(user): Add fields here

在 Kubernetes 中,GVK 是 Group, Version, Kind 的缩写,这三者共同构成了 Kubernetes API 中资源类型的唯一标识(即必须是唯一且确切的指定标识,这个资源坐标可以正确调用资源操作)。Kubernetes 的 API 是围绕这些资源类型设计的,每种资源类型对应一组可以对资源执行的操作(如创建、获取、更新、删除等)

  • Group:资源所在的 API 组。例如,Deployment 资源位于 “apps” 组中,而 Pod 资源位于 “core” 组中
  • Version:资源的 API 版本。例如,v1、v1beta1 等
  • Kind:资源的类型。例如,Pod、Deployment、Service 等

开始编码

在成功创建初始的脚手架项目之后,就可以立即使用 IDE 进行编程了。由于 Kubebuilder 和 Kubernetes 相关的依赖库只能在 Linux 环境下运行,如果我们希望在 Windows 系统上进行代码编写,可以选择将运行环境移至 WSL 或者使用远程开发。同时,为了确保代码的兼容性,我们应将 IDE 的代码检查设置调整为 Linux 模式(一般你认为依赖正确导入但是 IDE 一直显示找不到方法就是环境配置错误)

远程开发配置 远程开发配置-2 远程开发配置-2

这个是我们使用了 JenBrains Gateway 进行远程开发的一些截图,它的主要原理,是在客户端(即本地计算机)启动一个轻量级的 IDE,这个 IDE 叫 Gateway,它通过 ssh 等方式连接到服务器(也就是开发机)上,在服务器上安装一个服务端版本的IDE( Goland/Pycharm/Webstorm 等)来保存代码,使用服务器的原生 Linux 环境来运行代码和开发工具链。因为我购买的是 JenBrains 全家桶,需要集合管理远程连接才需要单独使用远程网关,否则单独 IDE 已经集成了对应的功能。

上面也是脚手架展开的结构,受限于篇幅和文章重点,我认为它在后面的编码中自然而然就能理解了,这里就不像其他入门书籍一样每个文件都讲解一遍。

CRD的实现

还是对照 Deployment 资源,我们可以想象一下在进行创建该类型的时候,会按照格式进行声明,我们的 CRD 资源自然而然