重要说明
  1. 先看控制器组件的原理以及自定义控制器
  2. operator 其实就是自定义控制器, 可能一般会将 编写自定义资源和自定以控制器 叫做编写operator

参考学习 k8s源码中staging/src/k8s.io/sample-controller/这个例子
这个时候 再看看 里面的controller.go文件, 和我们之前写的自定义控制器差不多.

1 自定义资源

自定义资源

crd-crontab.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cron:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
            status:
              type: object
              properties:
                availableReplicas:
                  type: integer
---
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object
spec:
  cron: "* * * * */5"
  image: my-awesome-cron-image
k apply -f crd-crontab.yaml

2 定义类型

Tip

参考k8s源码中`staging/src/k8s.io/sample-controller/ 相关文件复制过来修改修改

tree crd-operator
crd-operator
├── go.mod
├── hack
   ├── boilerplate.go.txt
   ├── tools.go
   └── update-codegen.sh
├── main.go
└── pkg
    └── apis # 自定义资源是非核心api, 所以是apis
        └── stable.example.com # group
            ├── register.go
            └── v1 # version
                ├── doc.go
                ├── register.go
                ├── types.go
package stableexamplecom

const (
    GroupName = "stable.example.com"
)
package v1

import (
    stableexamplecom "crd-operator/pkg/apis/stable.example.com"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: stableexamplecom.GroupName, Version: "v1"}

// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
    return SchemeGroupVersion.WithKind(kind).GroupKind()
}

// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
    return SchemeGroupVersion.WithResource(resource).GroupResource()
}

var (
    // SchemeBuilder initializes a scheme builder
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    // AddToScheme is a global function that registers this API group & version to a scheme
    AddToScheme = SchemeBuilder.AddToScheme
)

// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
    scheme.AddKnownTypes(SchemeGroupVersion,
        &CronTab{},
        &CronTabList{},
    )
    metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    return nil
}
// +k8s:deepcopy-gen=package
// +groupName=stable.example.com

// Package v1 is the v1 version of the API.
package v1
package v1

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

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// CronTab is a specification for a CronTab resource
// 就像内置资源对象一样, 4个大属性.
type CronTab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   CronTabSpec   `json:"spec"`
    Status CronTabStatus `json:"status"`
}

// CronTabSpec is the spec for a CronTab resource
type CronTabSpec struct {
    Cron     string `json:"cron"` // 注意和你的crd里定义的一样
    Replicas *int32 `json:"replicas"`
}

// CronTabStatus is the status for a CronTab resource
type CronTabStatus struct {
    AvailableReplicas int32 `json:"availableReplicas"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// CronTabList is a list of CronTab resources
type CronTabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata"`

    Items []CronTab `json:"items"`
}
//go:build tools
// +build tools

/*
Copyright 2019 The Kubernetes Authors.

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 tools

// 因为我们代码中没有使用这个库,但是我们又要使用代码生成器
// 所以用这个方式将这个依赖下载下来 (你当然可以在任何.go文件里写上这样的import,也是ok的.)
// 然后 使用 go mod vendor 后将下载依赖到项目目录下的vendor 目录,里面就会有依赖(code-generator)
// 接着 我们在代码生成脚本中指定code-generator的路径 (update-codegen.sh)
import _ "k8s.io/code-generator"
#!/bin/bash
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
# code-generator 目录
# 一般情况下我们创建operator 写代码, 是在任何一个地方创建项目目录的
# 所以可能最好是 在项目目录 将依赖生成到vendor 目录(go mod vendor)
# 然后从这个 vendor 目录下找到 代码生成器脚本
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null)}
if [ -z ${CODEGEN_PKG} ];then
    echo "请指定代码生成器脚本路径!!!或使用 go mod vendor后再执行脚本!"
    exit
fi
GROUP_NAME="stable.example.com"
VERSION="v1"
MOD_NAME="crd-operator" # go mod init 创建的名字
echo $ROOT_DIR
# generate-groups.sh <generators> <output-package> <apis-package> <groups-versions> ...
# output-package:
#       设置为当前项目根目录的父目录
#       项目目录的名字要与  MOD_NAME 一样
# --go-header-file  生成的文件前面的注释,必须指定,否则报错
# bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \
bash "${CODEGEN_PKG}"/generate-groups.sh all \
  ${MOD_NAME}/pkg/generated ${MOD_NAME}/pkg/apis \
  ${GROUP_NAME}:${VERSION} \
  --output-base "${SCRIPT_ROOT}/../" \
  --go-header-file "${SCRIPT_ROOT}"/hack/boilerplate.go.txt

3 代码生成器生成所需代码

生成代码后, 可以像 操作内置资源对象那样

go get k8s.io/client-go@v0.23.17
go get k8s.io/api@v0.23.17
go get k8s.io/apimachinery@v0.23.17
go get k8s.io/code-generator@v0.23.17
go mod tidy
go mod vendor
./hack/update-codegen.sh

tree crd-operator
├── pkg
   ├── apis
   │   └── stable.example.com
   └── generated # 自动生成的目录
       ├── clientset # 这样我们能像使用pod 一样, 用clientset.CoreV1().Pods("").List()
       ├── informers # 同理
       └── listers
main.go
package main

import (
    "context"
    "flag"
    "fmt"
    "path/filepath"

    clientset "crd-operator/pkg/generated/clientset/versioned"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
)

func main() {
    var kubeconfig *string
    if home := homedir.HomeDir(); home != "" {
        // ~/.kube/config 确保你家目录下有k8s的配置文件
        // 你本地可以用kubectl get po 进行查询.
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    } else {
        kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    }
    flag.Parse()

    // 使用指定的kubeconfig文件创建一个Config对象
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }

    // 创建一个新的Kubernetes客户端
    client, err := clientset.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
    crontabs, err := client.StableV1().CronTabs("default").
        List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        panic(err.Error())
    }
    for _, item := range crontabs.Items {
        fmt.Println(item.Name, ":", item.Spec.Cron)
    }
}
Back to top