blog/kubernetes/custom_resources/readme.md

12 KiB
Raw Blame History

定制Kubernetes资源

定制资源(Custom Resource) 是对 Kubernetes API 的扩展可以开发定制的资源及控制器这些资源仍旧可以通过kubernetes api来访问。

可以单独开发定制资源,也可以连定制同控制器一起开发。

准备

开发定制资源(CRD)需要安装controller-gen、kubebuilder、kustomize这三个工具。

这里所有工具都默认安装到${HOME}/go/bin/

controller-gen

controller-gen 用于生成crd controller的基础代码, 省去不少工作。

安装

git clone https://github.com/kubernetes-sigs/controller-tools.git
cd controller-tools/cmd/controller-gen/
go build -o ${HOME}/go/bin/controller-gen main.go

使用方法

# controller-gen crd --paths=[api code path]/... output:dir=[output dir]
controller-gen crd --paths=/git/demo/api/... output:dir=/git/demo/crd

kubebuilder

kubebuilder用于生成crd基础代码。

安装

wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.13.0/kubebuilder_linux_amd64 -O ${HOME}/go/bin/kubebuilder
chmod +x ${HOME}/go/bin/kubebuilder

方法2

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详细使用方法参见官方文档

创建一个项目

mkdir $GOPATH/src/example
cd $GOPATH/src/example
kubebuilder init --domain project.demo.io

创建一个api资源

kubebuilder create api --group project.demo.io --version v1 --kind Demo

这个api资源就是project.demo.io/v1/demos

kustomize

kustomize把简明的kustomization 模板转换成kubernetes的资源配置。kustomize 可以用于crd也可以用于内置资源(参见文档manage-kubernetes-objects)的管理。

安装

go get sigs.k8s.io/kustomize/kustomize/v5.3.0

使用方法参见官方指南

开始

安装好相关工具之后,就可以开始愉快的玩耍了。

初始化项目

PROJECT_DIR=demo #
API_DOMAIN=demo
GO_MODULE_DOMAIN=crd.io
OWNER=bing
mkdir ${PROJECT_DIR}
cd ${PROJECT_DIR}
kubebuilder init --plugins go/v3 --domain ${API_DOMAIN} --owner ${OWNER} --repo ${GO_MODULE_DOMAIN} --skip-go-version-check
  • PROJECT_DIR 是项目目录
  • GO_MODULE_DOMAIN 是go mod 的名称, 也是api resource group的后缀如果使用GOPATH则不需要配置
  • API_DOMAIN 是api resource的域名也是group名的一部分
  • OWNER 是项目所有人

最终apiVersion:${API_DOMAIN}.${GO_MODULE_DOMAIN}

现在,PROJECT_DIR 目录下文件(夹)如下

config  Dockerfile  go.mod  go.sum  hack  main.go  Makefile  PROJECT
  • config 存放kustomization配置
  • Dockerfile 生产docker镜像的配置这个文件包含了代码编译操作
  • hack 这个文件夹存放了一个名为boilerplate.go.txt的文件
  • main.go 程序入口文件,启动一个manager负责管理crd
  • Makefile make命令的配置文件
  • PROJECT 项目配置文件

main.go

main.go 是manager启动的入口文件。

处理启动参数
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
        "Enable leader election for controller manager. "+
                "Enabling this will ensure there is only one active controller manager.")
opts := zap.Options{
        Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
创建manager

创建了一个manager mgr

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:                 scheme,
    MetricsBindAddress:     metricsAddr,
    Port:                   9443,
    HealthProbeBindAddress: probeAddr,
    LeaderElection:         enableLeaderElection,
    LeaderElectionID:       "e71592d6.demo",
})
为manager添加healthy和ready检查
//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
        setupLog.Error(err, "unable to set up health check")
        os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
        setupLog.Error(err, "unable to set up ready check")
        os.Exit(1)
}

注意:注释//+kubebuilder:scaffold:builder也是代码的一部分。所有`//+kubebuilder:开头的注释都有他的特殊意义。

启动manager
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        setupLog.Error(err, "problem running manager")
        os.Exit(1)
}

创建一个CRD

kubebuilder create api --group ${API_DOMAIN} --version v1 --kind Demo

创建时会询问是否创建resource及controller

现在项目目录下多了几个文件夹

api  bin  config  controllers  Dockerfile  go.mod  go.sum  hack  main.go  Makefile  PROJECT
  • api api resource 的代码目录目录下是版本子目录resource的代码文件位于版本子目录内

  • bin controller-gen 可执行文件位于此处

  • controllers controller的代码目录

main.go

看一下main.go的新增部分

if err = (&controllers.DemoReconciler{
    Client: mgr.GetClient(),
    Log:    ctrl.Log.WithName("controllers").WithName("Demo"),
    Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "Demo")
    os.Exit(1)
}

创建一个对应的controller对象(DemoReconciler), 并设置这个controller的manager为mgr

api/v1/demo_types.go

定义Demo资源规约DemoSpec,

type DemoSpec 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 Demo. Edit demo_types.go to remove/update
        Foo string `json:"foo,omitempty"`
}

定义Demo状态

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

注册资源

func init() {
    SchemeBuilder.Register(&Demo{}, &DemoList{})
}

api/v1/groupversion_info.go

api 资源组配置。

// Package v1 contains API Schema definitions for the demo.io v1 API group
//+kubebuilder:object:generate=true
//+groupName=demo.crd.io
package v1

import (
        "k8s.io/apimachinery/pkg/runtime/schema"
        "sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
        // GroupVersion is group version used to register these objects
        GroupVersion = schema.GroupVersion{Group: "demo.crd.io", Version: "v1"}

        // SchemeBuilder is used to add go types to the GroupVersionKind scheme
        SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

        // AddToScheme adds the types in this group-version to the given scheme.
        AddToScheme = SchemeBuilder.AddToScheme
)

注意:注释+groupName=demo.crd.io不可删除,与+kubebuilder:object:generate=true一样,有他的特殊意义。后续还能看到用于设定的很多注释

api/v1/zz_generated.deepcopy.go

这是一个自动生成的深度复制的文件不要随意修改此文件除非明确知道要做什么。kubebuilder会自动配置及更新该文件。

controllers/demo_controller.go

资源demo的控制器文件。

DemoReconciler

DemoReconciler对象定义。一般来说,无需为其添加任何内容,除非有特别需求


// DemoReconciler reconciles a Demo object
type DemoReconciler struct {
    client.Client
    Log    logr.Logger
    Scheme *runtime.Scheme
}
  • DemoReconciler对象有三个成员,client.Client 是kubernetes client接口提供如Get、Update、Create、Delete等方法。

  • Log 统一的log处理

  • Scheme 这个功能比较多,比如序列化与反序列化对象

Reconcile

处理程序。每一次CRD的变更都会调用此方法。


//+kubebuilder:rbac:groups=demo.demo,resources=demoes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=demo.demo,resources=demoes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=demo.demo,resources=demoes/finalizers,verbs=update

func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        _ = r.Log.WithValues("demo", req.NamespacedName)

        // your logic here

        return ctrl.Result{}, nil
}

注意:+kubebuilder:rbac是用于授权的注释,只有在此授权了的资源,才能够使用对应权限访问。kustomize会依据此配置生成config/rbac配置。

SetupWithManager

设置controller的manager通常不需要修改这部分代码就可以正常工作但有时需要进行改动比如watch其他资源的时候。

// SetupWithManager sets up the controller with the Manager.
func (r *DemoReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&demov1.Demo{}).
        Complete(r)
}

为controller添加功能

添加字段

首先为crd添加两个字段。一个规约字段Bar和一个状态字段UpdatedAt

type DemoSpec struct {
    Foo string `json:"foo,omitempty"`
    Bar string `json:"Bar,omitempty"`
}

// DemoStatus defines the observed state of Demo
type DemoStatus struct {
    UpdatedAt *metav1.Time `json:"UpdatedAt,omitempty"`
}

修改Reconcile

import apiv1 "demo.crd.io/api/v1"

func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        _ = r.Log.WithValues("demo", req.NamespacedName)

        // your logic here
        demo := apiv1.Demo{}
        err := r.Get(context.Background(), req.NamespacedName, &demo)
        if err != nil {
            r.Log.Error(err, "get demo resource error")
            return ctrl.Result{}, nil
        }
        //更新操作
        if demo.ObjectMeta.DeletionTimestamp.IsZero() {
            //更新
            err = r.processUpdate(&demo)
        }
        return ctrl.Result{}, nil
}

//处理更新
func (r *DemoReconciler)processUpdate(demo *apiv1.Demo)error{
    r.Log.Info("the bar of %s is %s", demo.GetName(), demo.Spec.Bar)
    now := metav1.Now()
    demo.Status.UpdatedAt = &now
    err := r.Status().Update(context.Background(), demo)
    return err
}

这里首先获取到触发此次reconcile的对象demo, 然后通过demo.ObjectMeta.DeletionTimestamp.IsZero()来判断是删除操作还是更新操作(创建对象也是更新操作)。对于更新操作,执行方法processUpdate

processUpdate中,先将demo.Spec.Bar打印到日志:r.Log.Info("the bar of %s is %s", demo.GetName(), demo.Spec.Bar) 之后更新demo.Status.UpdatedAt字段。

这里使用了r.Status().Update(context.Background(), demo),这需要有注释// +kubebuilder:subresource:status 默认已经添加了该注释,如果误删除了,需要添加上。

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

// Demo is the Schema for the demoes API
type Demo struct {

注意这里注释的位置和空行。

多group

初始化

PROJECT_DIR=demo #
GO_MODULE_DOMAIN=crd.io
OWNER=bing
CRD_VERSION=v1beta1


echo "init crd env"
kubebuilder init --plugins go/v3 --owner ${OWNER} --repo ${GO_MODULE_DOMAIN} --skip-go-version-check
echo "open multi group..."
kubebuilder edit --multigroup

kubebuilder edit --multigroup 开启多组支持

创建CRD

kubebuilder create api --group group1 --version ${CRD_VERSION} --kind Demo --crd-version ${CRD_VERSION} --controller --resource
kubebuilder create api --group group2 --version ${CRD_VERSION} --kind Demo --crd-version ${CRD_VERSION} --controller --resource

创建clientset

client-gen --clientset-name="slmclient" --input-dirs="./pkg/api/v1alpha1"  -h hack/boilerplate.go.txt --output-package pkg/slmclient