前言

Operator是1个控制器,负责在Kubernetes集群中管理应用的状态和生命周期。

operator使用自定义资源定义(CRD)来表示应用的配置。

operator使用控制器逻辑来自动化任务,如部署、扩展、备份、恢复、更新等。

Kubebuilder是1个用于在Kubernetes上构建自定义控制器+自定义资源(CRD)的框架。

自动化运维
Operator可以大幅度提高运维的自动化水平,减少手动操作降低人为错误风险

定制资源
通过定义自己的 CRD,用户可以在Kubernetes中创建和管理自定义资源,使得应用的管理变得更加灵活

跨平台性

由于 Operator 遵循 Kubernetes API 的标准,它可以在不同的 Kubernetes 发行版上运行,保持了跨平台性

增强K8S API
Operator 可以通过扩展 K8S API 来为应用程序添加更多自定义管理的能力,提供更强大的 API。

一、逻辑关系梳理

零零散散地听过很多关于K8s设计理念的词语,梳理出这些概念之间的关系,才能更深入理解K8s;

1.事件触发模式

在事件驱动系统中,水平触发边缘触发是2种事件通知的方式;

 

水平触发-注重事件造成的结果

Level-Triggered,也被称为条件触发LT:只要满足条件,就会一直触发事件。

状态=0时触发,状态=1时也触发,状态一直为0或状态一直为1时还会一直触发; 

边缘触发-注重事件演变的过程

Edge-TriggeredET,只有状态发生变化时,才会触发1次事件。

状态由0变为1时触发1次,或者状态由1变为0时触发1次; 

2.List-Watch

List-Watch机制中的Watch操作,实际上就是1种水平触发的实现方式。

Watch动作可以确保客户端(Controler)不会错过API-Server任何资源的状态变化。

当资源状态发生变化时,Watch操作会立即将事件推送给客户端,从而触发客户端的事件处理逻辑。

这种机制确保了Kubernetes能够及时响应集群中的资源变化,从而保持数据的同步和一致性。

在Kubernetes中,API-Server提供了list和watch两个方法,从API-Server获取资源对象信息;

在Kubernetes中,API-Server和控制器之间采用List-Watch机制即发布/订阅的模式进行通信;

List动作

通过向API-Server发送List请求,可以获取指定资源类型的所有资源对象列表

Watch动作

通过向API-Server发送Watch请求,创建1个长连接,持续、实时地监听指定来自API-Server的资源类型变化事件。

事件通知

控制器通过Watch动作与API-Server建立长连接之后,当指定资源类型的对象发生变化时,API-Server采用水平触发模式向订阅者发送事件通知

事件通知中包含了变化的资源对象的详细信息,例如创建、更新、删除等操作的类型和相关数据。

订阅者处理

订阅者可以通过监听事件通知来实现对资源对象变化的实时响应。

例如,可以通过解析事件通知中的数据,进行相应的处理,例如自动伸缩、服务发现、配置更新等操作

3.Informer

Informer基于List-Watch机制,用于监控K8s集群中资源变化的框架,

Informer通过本地缓存和事件处理机制,确保资源状态的实时同步,并简化了Kubernetes集群中资源管理的复杂性。

4.client-go

client-go封装了Informer,Informer是client-go库中的1个核心组件,其主要功能是监控Kubernetes API资源的变化,并在资源发生变化时通知用户。

client-go是Kubernetes的官方Go语言客户端库,用于与Kubernetes API服务器进行交互

它提供了访问 Kubernetes API的接口和实现,包括创建、更新、删除 Kubernetes 资源等功能

client-go提供了Informer机制,用于监听Kubernetes资源的变更事件,并实现资源的快速检索和本地缓存,减轻对API-Server的访问压力

使用client-go,开发者需要自己定义CRD类型、生成深拷贝函数、生成 Informer、Lister、Clientset,并编写控制循环和协调器逻辑

5.Controller-runtime

Controller-runtime是在client-go的基础上进一步封装,简化了编写和管理控制器的过程。

它提供了一些高层次的抽象,使得开发者可以更专注于业务逻辑而不是底层细节;

6.Kubebuilder

Kubebuilder封装了底层的Kubernetes Go 客户端(包括 client-go 和 controller-runtime)使得从零开始开发CRDs 和Controllers 更加容易;

二、Operator开发

Operator=自定义资源+控制器

CRD:Custom Resource Definition,数据库表定义

CR:Custom Resource   数据库表中记录

CRO:Custom Resource Object:定义在yaml文件中的CR被API-Server创建之后,保存在etcd数据库;

CRD-Controller:1种自定义的控制器,自定义控制器循环watch着API-server发来的CRD变更事件, 达成CR期望的状态;

Operator:CRD+CR+CRO+CRD控制器,该自定义控制器可以实现复杂业务的自动化部署

Operator实现方式有Kubebuilder和Operator-SDK

1.CRD和CR的关系

CRD:就像是1个类,定义了对象的属性和行为。

CR 就像是该类的1个实例,它包含了实际的数据。

root@localhost /]# kubectl get crd -o wide
NAME                      CREATED AT
myresources.example.com   2020-11-14T03:33:37Z
scalers.api.example.com   2020-11-08T09:08:46Z
[root@localhost /]# 

2.CR创建流程

  1. 定义 CRD:你首先需要创建1个CRD,指定资源类型的API 组、版本、字段等。安装 CRD 后,Kubernetes API会开始理解该自定义资源类型。

  2. 创建 CR:基于已经安装的 CRD,你可以创建 CR 实例。例如,创建1个MyResource类型的自定义资源,它会根据 CRD中定义的spec来存储数据。

  3. 管理 CR:创建CR 后,你可以使用Kubernetes控制器来管理CR实例,执行相关操作(如扩缩容、滚动更新等)。

1.minikube安装

安装minikube的前置条件是安装docker和kubectl

下载minikube-linux-amd64

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
install minikube-linux-amd64 /usr/local/bin/minikube && rm -rf minikube-linux-amd64

快速启动minikube

[root@localhost ~]# minikube start --vm-driver=docker --kubernetes-version=v1.23.3 --base-image="registry.cn-hangzhou.aliyuncs.com/google_containers/kicbase:v0.0.45" --image-mirror-country='cn' --force
* minikube v1.34.0 on Centos 7.6.1810
! minikube skips various validations when --force is supplied; this may lead to unexpected behavior
* Using the docker driver based on user configuration
* The "docker" driver should not be used with root privileges. If you wish to continue as root, use --force.
* If you are running minikube within a VM, consider using --driver=none:
*   https://minikube.sigs.k8s.io/docs/reference/drivers/none/
* Using image repository registry.cn-hangzhou.aliyuncs.com/google_containers
* Using Docker driver with root privileges
* Starting "minikube" primary control-plane node in "minikube" cluster
* Pulling base image v0.0.45 ...
    > registry.cn-hangzhou.aliyun...:  487.89 MiB / 487.90 MiB  100.00% 5.31 Mi
* Creating docker container (CPUs=2, Memory=2200MB) ...
! Failing to connect to https://registry.cn-hangzhou.aliyuncs.com/google_containers/ from inside the minikube container
* To pull new external images, you may need to configure a proxy: https://minikube.sigs.k8s.io/docs/reference/networking/proxy/
    > kubeadm.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubelet.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubeadm:  43.12 MiB / 43.12 MiB [-------------] 100.00% 5.24 MiB p/s 8.4s
    > kubelet:  118.75 MiB / 118.75 MiB [------------] 100.00% 7.15 MiB p/s 17s                                                                                          
  - Generating certificates and keys ...
  - Booting up control plane ...
  - Configuring RBAC rules ...
* Verifying Kubernetes components...
  - Using image registry.cn-hangzhou.aliyuncs.com/google_containers/storage-provisioner:v5
* Enabled addons: storage-provisioner, default-storageclass

! /usr/local/bin/kubectl is version 1.31.2, which may have incompatibilities with Kubernetes 1.23.3.
  - Want kubectl v1.23.3? Try 'minikube kubectl -- get pods -A'
* Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

清理

如果重复重启minikube,需要清理之前历史数据;

minikube stop
minikube delete --all
rm -rf ~/.minikube

2.kubebuilder安装

kubebuilder安装的前置条件是安装Golang;

kubebuilder封装了client-go是operator的开发框架;

curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)

3.kubebuilder框架创建

类似Django的django-admin startproject和django-admin startapp,生成框架代码

3.1.创建脚手架工程

kubebuilder init --domain zhanggen.com --repo github.com/martin6/deplay-scale

3.2.创建API

以下术语都与Kubernetes的API资源有关,主要是用来描述和区分不同的资源类型

GV:表示该资源所属组/版本(没有具体的资源名),比如 apps/v1

GVR:表示该资源的 API 路径,包括组/版本/资源名,比如 apps/v1.deployments

GVK:表示一个资源的完整描述,包括组/版本/资源种类,比如 apps/v1.Deployment

kubebuilder create api --group apps --version v1alph --kind Application

4.kubebuilder业务逻辑开发

这是1个简单demo:每天下午17-18点业务高峰时期,扩展特定deployment的副本数量;

api

定义CRD的结构体

/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.

// ScalerSpec defines the desired state of Scaler.

//自定义
type NamespaceName struct {
    Name       string `json:"name"`
    Namespacee string `json:"namespace"`
}

type ScalerSpec 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 Scaler. Edit scaler_types.go to remove/update
    //Foo string `json:"foo,omitempty"`
    Start       int             `json:"start"`
    End         int             `json:"end"`
    Replicas    *int32             `json:"replicas"`
    Deployments []NamespaceName `json:"deployments"`
}

// ScalerStatus defines the observed state of Scaler.
type ScalerStatus struct {
    // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
    // Important: Run "make" to regenerate code after modifying this file
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// Scaler is the Schema for the scalers API.
type Scaler struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   ScalerSpec   `json:"spec,omitempty"`
    Status ScalerStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ScalerList contains a list of Scaler.
type ScalerList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Scaler `json:"items"`
}

func init() {
    SchemeBuilder.Register(&Scaler{}, &ScalerList{})
}
scaler_types.go

Scheme

Scheme 是1个全局注册表,它将资源的GVK和资源的Go类型(如结构体)进行绑定映射,并负责处理该资源的序列化和反序列化。

controller

控制器器是1个运行在Kubernetes集群中的进程,它监控自定义资源并确保它们处于期望的状态。

控制器通常使用客户端库与 Kubernetes API通信;

代码中的 Reconcile方法设置了RequeueAfter: time.Duration(10 * time.Second),这意味着控制器期望每10秒重新调度1次。

然而,实际上 Reconcile 方法的执行频率并不完全由这个值决定。以下是一些影响 Reconcile 方法执行频率的因素:

/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controller

import (
    "context"
    "fmt"
    v1 "k8s.io/api/apps/v1"
    "k8s.io/apimachinery/pkg/types"
    "time"

    apiv1alpha1 "github.com/martin6/deplay-scale/api/v1alpha1"
    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
)

// ScalerReconciler reconciles a Scaler object
type ScalerReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

// +kubebuilder:rbac:groups=api.example.com,resources=scalers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=api.example.com,resources=scalers/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=api.example.com,resources=scalers/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Scaler object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile
func (r *ScalerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    fmt.Println("========Reconcile called=========", ctx)
    //创建1个空的scaler实例,查询
    scaler := &apiv1alpha1.Scaler{}
    //从当前k8s集群中查询到1个Scaler实例
    err := r.Get(ctx, req.NamespacedName, scaler)
    if err != nil {
        //如果发现没有当前scaler实例,使用client.IgnoreNotFound(err)忽略错误,让进程不中断
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    startTime := scaler.Spec.Start
    endTime := scaler.Spec.End
    replicas := scaler.Spec.Replicas
    currentHour := time.Now().Local().Hour()
    fmt.Println("当前小时:", currentHour)
    //startTime--endTime
    if currentHour >= startTime && currentHour < endTime {
        //从scaler对象的spec字段获取需要扩缩容的deployment信息
        for _, deploy := range scaler.Spec.Deployments {
            deployment := &v1.Deployment{}
            //创建1个新的deploy实例
            err := r.Get(ctx, types.NamespacedName{
                Name:      deploy.Name,
                Namespace: deploy.Namespacee,
            }, deployment)
            if err != nil {
                fmt.Println(err)
                return ctrl.Result{}, nil
            }
            //判断当前K8s集群中查询到的deployment副本数是否等于指定副本数
            if deployment.Spec.Replicas != replicas {
                //扩容 deployment副本数量
                deployment.Spec.Replicas = replicas
                err := r.Update(ctx, deployment)
                if err != nil {
                    fmt.Println(err)
                    return ctrl.Result{}, nil
                }

            }

        }

    }
    //Reconcile循环每10秒钟执行1次
    return ctrl.Result{RequeueAfter: time.Duration(10 * time.Second)}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ScalerReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&apiv1alpha1.Scaler{}).
        Named("scaler").
        Complete(r)
}
scaler_controller.go

samples目录下CRD

定义应用程序的自定义资源定义(CRDs),这些定义描述了应用程序期望的状态。

apiVersion: api.example.com/v1alpha1
kind: Scaler
metadata:
  labels:
    app.kubernetes.io/name: deplay-scaler
    app.kubernetes.io/managed-by: kustomize
  name: scaler-sample
spec:
  start: 17
  end: 18
  replicas: 3
  deployments:
    - name: abc
      namespace: default
    - name: def
      namespace: default

5.kubebuilder开发流程

定义CRD和资源结构:

创建1个自定义资源,你通常会通过CRD来定义该资源的模型。

例如,你可能会定义一个MyApp资源,描述自定义资源的API schema。

api/v1/myapp_types.go文件中,你定义了 Go 结构体来描述你的自定义资源。Kubebuilder 会自动生成对应的 CRD YAML 文件,像这样

type MyAppSpec struct {
    // spec fields
}

type MyAppStatus struct {
    // status fields
}

type MyApp struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              MyAppSpec   `json:"spec,omitempty"`
    Status            MyAppStatus `json:"status,omitempty"`
}

type MyAppList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []MyApp `json:"items"`
}
View Code

生成CRD的YAML文件

运行make manifests命令,会基于这些结构体生成对应的CRD 源定义文件。

该文件会描述 CRD 的 GroupVersionKind(GVK),以及每个字段的类型、验证规则等信息。

生成的 CRD 文件通常位于config/crd/smaples/目录下,像这样:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myapps.myapp.example.com
spec:
  group: myapp.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                # spec fields
            status:
              type: object
              properties:
                # status fields
  scope: Namespaced
  names:
    plural: myapps
    singular: myapp
    kind: MyApp
    shortNames:
    - ma
View Code

部署CRD到集群

Kubebuilder提供了1个make install命令来安装CRD。

在执行该命令时,它会使用 kubectl apply将CRD文件apply到当前Kubernetes集群中,从而在Kubernetes集群中创建CRD资源。

make install
kubectl apply -f config/crd/bases/myapp.example.com_myapps.yaml

CRD存储到etcd数据库

当CRD被apply到Kubernetes集群中时,Kubernetes会将这些CRD资源存储到etcd数据库中。

CRD 资源是Kubernetes 集群中的1部分,就像Pod、Service、Deployment 等其他资源一样,存储在etcd中的API对象数据。

CRD是Kubernetes的资源类型之一,它描述了你自定义的资源种类,并通过 API 实现对这些资源的访问。

存储CRD的数据结构在etcd中看起来类似于其他Kubernetes资源的存储格式。

当你通过 Kubernetes APIkubectl与这些 CRD 资源交互时,Kubernetes 会从 etcd 中读取和写入数据。

创建CR

基于以上CRD的规范创建1个CR到K8s

apiVersion: myapp.example.com/v1
kind: MyApp
metadata:
  name: myapp-instance
spec:
  # 自定义资源的 spec 配置

自定义控制器处理CR(自定义资源)创建/更新/删除时间

自定义控制器通过client-go与Kubernetes API 交互,管理 CR(自定义资源)的生命周期。

控制器本身并不直接操作etcd数据库,而是通过 Kubernetes API 进行资源的创建、更新和删除;

6.kubebuilder启动

make manifests
make install
make run
kubectl apply -f config/samples/api_v1alpha1_scaler.yaml 

 

 

参考

posted on 2024-03-22 13:22  Martin8866  阅读(44)  评论(0编辑  收藏  举报