环境说明


1. Operator 是什么?

Kubernetes Operator 是一种封装、部署和管理 Kubernetes 应用的方法。

如欲了解更多细节,请阅读:

简单来讲:

常用的 Operator 开发框架:

下面介绍如何使用 kubebuilder 开发 Operator。

说明:

  1. kubebuilder book:https://book.kubebuilder.io/quick-start.html
  2. kubebuilder Github:https://github.com/kubernetes-sigs/kubebuilder

2. 安装 kubebuilder

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 版本
kubebuilder version
# 输出:
# Version: main.version{KubeBuilderVersion:"3.7.0", KubernetesVendor:"1.24.1", GitCommit:"3bfc84ec8767fa760d1771ce7a0cb05a9a8f6286", BuildDate:"2022-09-20T17:21:57Z", GoOs:"darwin", GoArch:"amd64"}

3. 创建测试项目

mkdir kubebuilder-operator-demo
cd kubebuilder-operator-demo/
go mod init kubebuilder-operator-demo

4. 初始化项目

kubebuilder init --plugins go/v3 --domain timd.cn --owner "Tim Chow"

当看到如下输出时,表明初始化成功:

...

$ go mod tidy
Next: define a resource with:
$ kubebuilder create api

说明:

  1. config/default/kustomization.yaml 中的 namespace 字段用于为所有资源添加命名空间。在这里它是 kubebuilder-operator-demo-system

5. 创建 API

kubebuilder create api --group testapp --version v1 --kind Redis

创建成功后,项目结构如下:

.
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── api
│   └── v1
│       ├── groupversion_info.go
│       ├── redis_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── controller-gen
├── config
│   ├── crd
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   │       ├── cainjection_in_redis.yaml
│   │       └── webhook_in_redis.yaml
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   ├── rbac
│   │   ├── auth_proxy_client_clusterrole.yaml
│   │   ├── auth_proxy_role.yaml
│   │   ├── auth_proxy_role_binding.yaml
│   │   ├── auth_proxy_service.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── redis_editor_role.yaml
│   │   ├── redis_viewer_role.yaml
│   │   ├── role_binding.yaml
│   │   └── service_account.yaml
│   └── samples
│       └── testapp_v1_redis.yaml
├── controllers
│   ├── redis_controller.go
│   └── suite_test.go
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── main.go

说明:

  1. apiVersion 为 testapp.timd.cn/v1
  2. kind 为 Redis

6. 创建测试 CRD

api/v1/redis_types.goRedisSpec 结构体的定义改为:

// RedisSpec defines the desired state of Redis
type RedisSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file

    // Foo is an example field of Redis. Edit redis_types.go to remove/update
    //Foo string `json:"foo,omitempty"`

    Port int `json:"port,omitempty"`
}

创建 test/redis.yaml 文件,其内容为:

apiVersion: testapp.timd.cn/v1
kind: Redis
metadata:
  name: testapp
spec:
  port: 80

安装:

make install

如果通过在线脚本下载 kustomize 失败,可以使用代理;或自己下载 kustomize,然后放到 bin/ 目录下

查看刚刚创建的 CRD:

kubectl describe crd redis.testapp.timd.cn

7. 在本地运行

controllers/redis_controller.goReconcile() 方法的定义改为:

func (r *RedisReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    // TODO(user): your logic here
    redis := &testappv1.Redis{}
    if err := r.Get(ctx, req.NamespacedName, redis); err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("object:", redis)
    }

    return ctrl.Result{}, nil
}

注意:

  1. 导入 fmt

在一个窗口运行:

make run

在另一个窗口运行:

kubectl apply -f test/redis.yaml

查看第一个窗口的日志;用 kubectl 查看刚刚创建的 CR:

kubectl get redis

8. 添加验证

CRD 通过在 validation 区域使用 OpenAPI v3 模式的方式支持声明式验证(declarative validation)。

通常,验证标记(validation markers)被附加到字段或类型,如果定义复杂验证,必须重用验证,或必须验证切片元素,最好定义新类型来描述验证。

比如:

type ToySpec struct {
    // +kubebuilder:validation:MaxLength=15
    // +kubebuilder:validation:MinLength=1
    Name string `json:"name,omitempty"`

    // +kubebuilder:validation:MaxItems=500
    // +kubebuilder:validation:MinItems=1
    // +kubebuilder:validation:UniqueItems=true
    Knights []string `json:"knights,omitempty"`

    Alias   Alias   `json:"alias,omitempty"`
    Rank    Rank    `json:"rank"`
}

// +kubebuilder:validation:Enum=Lion;Wolf;Dragon
type Alias string

// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=3
// +kubebuilder:validation:ExclusiveMaximum=false
type Rank int32

如欲了解更多关于验证的细节,请查看 https://book.kubebuilder.io/reference/markers/crd-validation.html

api/v1/redis_types.goRedisSpec 的定义修改为:

// RedisSpec defines the desired state of Redis
type RedisSpec struct {
    // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
    // Important: Run "make" to regenerate code after modifying this file

    // +kubebuilder:validation:Maximum:=6380
    // +kubebuilder:validation:Minimum:=6370
    Port int `json:"port,omitempty"`
}

安装:

make install

查看 CRD:

kubectl get crd redis.testapp.timd.cn -o yaml

可以看到类似下面的输出:

          spec:
            description: RedisSpec defines the desired state of Redis
            properties:
              port:
                description: Foo is an example field of Redis. Edit redis_types.go
                  to remove/update
                maximum: 6380
                minimum: 6370
                type: integer
            type: object

测试验证:

kubectl delete -f test/redis.yaml
kubectl apply -f test/redis.yaml

可以看到类似下面的错误:

The Redis "testapp" is invalid: spec.port: Invalid value: 80: spec.port in body should be greater than or equal to 6370

9. 将 Operator 部署到 Kubernetes 集群

将前面测试过程中创建的 CR、CRD 清理掉:

然后重新安装:

9.1. 构建镜像

make docker-build IMG=<镜像名称>:<标签>

9.2. 部署

make deploy IMG=<镜像名称>:<标签>

通过如下命令查看部署状态:

kubectl -n kubebuilder-operator-demo-system get all

通过如下命令查看 Pod 输出的日志(注意替换 Pod 的名称):

kubectl -n kubebuilder-operator-demo-system logs -f kubebuilder-operator-demo-controller-manager-74fb7bf75-522tg

创建 CR,同时观察 Pod 的输出:

kubectl apply -f test/redis.yaml

多改几次 .spec.port 的值,观察输出。


参考文档

  1. kubebuilder 实战之七:Webhook