前言

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

Operator的基本概念

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

控制器模式

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

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

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

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

Kubernetes中的控制器

Kubernetes 中通过 “声明式API” 定义了一系列的资源对象,然后通过许多的 Controllers 来 “调谐” 这些资源对象的实际状态以向期待状态靠拢,从而实现整个集群 “尽可能” 靠拢配置中声明的期望状态

一个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 上的操作程序化,并交给自定义控制器去实现

还是按照书中的例子,让我们来了解一下 Operator 的工作过程:

  1. 定义一个名为 PodController 的 Custom Resource(CRD)
  2. 通过 Deployment 方式部署一个 Operator 的 Controller 部分
  3. 在 Controller 的代码逻辑中查询 ApiServer,从而获得 PodController 的具体内容
  4. 和写 Deployment 的 YAML 一样,写一个资源声明提交,只是 Kind 为 PodController 而已,Operator 中的核心逻辑是告诉 ApiServer 怎么让集群的实际状态和自定义资源中的声明状态保持一致
  5. 在 Pod 被手动删除后,自动伸缩重建到声明相等的数量

这里的资源就是 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"

我们要通过一个 Application 类型来定义一个自己的资源对象,然后在控制器中获取这个资源对象的详细配置,接着根据它的配置去创建相应数量的 Pod,就像 Deployment 那样工作,因为是 Demo,所以这里不会考虑异常或者程序健硕等问题。

创建项目

我们直接使用 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/operator --owner sxueck
$ ls -al
total 112
drwxrwxr-x 4 ubuntu ubuntu  4096 Jan 28 14:52 .
drwxr-x--- 8 ubuntu ubuntu  4096 Jan 29 14:41 ..
drwx------ 6 ubuntu ubuntu  4096 Jan 28 14:51 config
-rw------- 1 ubuntu ubuntu  1250 Jan 28 14:51 Dockerfile
-rw------- 1 ubuntu ubuntu   129 Jan 28 14:49 .dockerignore
-rw------- 1 ubuntu ubuntu   384 Jan 28 14:49 .gitignore
-rw------- 1 ubuntu ubuntu  2932 Jan 28 14:52 go.mod
-rw-rw-r-- 1 ubuntu ubuntu 60004 Jan 28 14:52 go.sum
drwx------ 2 ubuntu ubuntu  4096 Jan 28 14:51 hack
-rw------- 1 ubuntu ubuntu  3491 Jan 28 14:51 main.go
-rw------- 1 ubuntu ubuntu  7184 Jan 28 14:51 Makefile
-rw------- 1 ubuntu ubuntu   335 Jan 28 14:52 PROJECT
-rw------- 1 ubuntu ubuntu  2724 Jan 28 14:51 README.md

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

$ kubebuilder create api --group apps --version v1 --kind PodController
INFO[0000] Create Resource [y/n]
y
INFO[0001] Create Controller [y/n]
y
INFO[0003] Writing kustomize manifests for you to edit...
INFO[0003] Writing scaffold for you to edit...
INFO[0003] api/v1/podcontroller_types.go
INFO[0003] api/v1/groupversion_info.go
INFO[0003] internal/controller/suite_test.go
INFO[0003] internal/controller/podcontroller_controller.go
INFO[0003] Update dependencies:
$ go mod tidy
go: downloading golang.org/x/mod v0.10.0

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

$ cat config/samples/apps_v1_podcontroller.yaml

apiVersion: apps.sxueck.com/v1
kind: PodController
metadata:
  labels:
    app.kubernetes.io/name: podcontroller
    app.kubernetes.io/instance: podcontroller-sample
    app.kubernetes.io/part-of: operator
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: operator
  name: podcontroller-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 一直显示找不到方法就是环境配置错误)

项目首页

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

CRD的实现

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