本章参考operator-sdk的tutorial编写一个memcache的operator
1 功能介绍
memcache operator创建成功后,可以通过如下yaml来创建进一个memcache 服务
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
name: memcached-sample
spec:
size: 3
根据yaml创建memcached
kubectl create -f memcached.yaml
创建成功后,可以通过kubectl 查看memcached的状态信息
# 查看是否存在对应资源信息
tingshuai.yts@B-5BBCMD6M-2026 ~ % kubectl get memcached
NAME AGE
memcached-sample 34m
# 查看memcached详细信息
tingshuai.yts@B-5BBCMD6M-2026 ~ % kubectl get memcached memcached-sample -o yaml
apiVersion: cache.example.com/v1alpha1
kind: Memcached
metadata:
creationTimestamp: "2022-02-24T07:16:33Z"
generation: 2
name: memcached-sample
namespace: default
resourceVersion: "3385066472"
selfLink: /apis/cache.example.com/v1alpha1/namespaces/default/memcacheds/memcached-sample
uid: 12f26a9f-fc34-492e-a30f-bc17266052aa
spec:
size: 4
status:
nodes:
- memcached-sample-9b765dfc8-w6l26
- memcached-sample-9b765dfc8-7cwpl
- memcached-sample-9b765dfc8-kxnmd
- memcached-sample-9b765dfc8-hsrqd
memcached operator主要工作是根据yaml中的描述spec来创建deployment,如下所示:
tingshuai.yts@B-5BBCMD6M-2026 ~ % kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
memcached-sample 4/4 4 4 37m
2 整体流程
编写一个operator一般分为如下几个步骤:
- 开发环境搭建
- 通过operator sdk创建operator project
- 通过operator sdk创建API和controller 框架代码
- 编写API和controller业务代码
- 调试执行
3 环境搭建
开发controller依赖如下环境,由于篇幅有限,本文仅列举依赖的内容,安装方式读者自行百度即可
- golang
- operator-sdk
- kubectl 和k8s对应的config
4 创建operator project
通过operator-sdk init命令创建controller的project
mkdir -p $HOME/projects/memcached-operator
cd $HOME/projects/memcached-operator
# we'll use a domain of example.com
# so all API groups will be <group>.example.com
operator-sdk init --domain example.com --repo github.com/example/memcached-operator
operator-sdk init的帮助文档信息如下:
% operator-sdk init --help
Initialize a new project including the following files:
- a "go.mod" with project dependencies
- a "PROJECT" file that stores project configuration
- a "Makefile" with several useful make targets for the project
- several YAML files for project deployment under the "config" directory
- a "main.go" file that creates the manager that will run the project controllers
Usage:
operator-sdk init [flags]
Examples:
# Initialize a new project with your domain and name in copyright
operator-sdk init --plugins go/v3 --domain example.org --owner "Your name"
# Initialize a new project defining an specific project version
operator-sdk init --plugins go/v3 --project-version 3
Flags:
--component-config create a versioned ComponentConfig file, may be 'true' or 'false'
--domain string domain for groups (default "my.domain")
--fetch-deps ensure dependencies are downloaded (default true)
-h, --help help for init
--license string license to use to boilerplate, may be one of 'apache2', 'none' (default "apache2")
--owner string owner to add to the copyright
--project-name string name of this project
--project-version string project version (default "3")
--repo string name to use for go module (e.g., github.com/user/repo), defaults to the go package of the current working directory.
--skip-go-version-check if specified, skip checking the Go version
Global Flags:
--plugins strings plugin keys to be used for this subcommand execution
--verbose Enable verbose logging
5 创建API和controller框架代码
执行下面的命令会创建api和controller的框架代码,然后我们在框架代码的基础上按照需求填写业务逻辑代码即可:
- api代码会在api/v1alpha1/memcached_types.go
- controller代码会在controllers/memcached_controller.go
$ operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller
6 编写API
6.1 api 定义
创建完api后代码,operator sdk生成的主要代码如下:
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec 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 Memcached. Edit memcached_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus 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
// Memcached is the Schema for the memcacheds API
type Memcached struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MemcachedSpec `json:"spec,omitempty"`
Status MemcachedStatus `json:"status,omitempty"`
}
一般来说,我们需要修改spec和status的定义,来满足我们的业务需求。
spec更改如下:
- 添加Size字段,用来定义memcache 副本的个数
- +kubebuilder:validation:Minimum=0 是marker comments;operator-sdk会根据marker comments的描述来生成代码。本例中marker comments的作用是生成api定义的校验代码,如果spec的小于0,则validation检查会失败(下一节会对marker comments进一步介绍)
// MemcachedSpec defines the desired state of Memcached
type MemcachedSpec struct {
//+kubebuilder:validation:Minimum=0
// Size is the size of the memcached deployment
Size int32 `json:"size"`
}
status定义如下:
- status仅有一个Nodes的切片字段,用来存储memcached 对应的所有pod 名称
- controller负责对status字段进行更改
// MemcachedStatus defines the observed state of Memcached
type MemcachedStatus struct {
// Nodes are the names of the memcached pods
Nodes []string `json:"nodes"`
}
6.2 marker comments
在编写operator时,可以使用controller-gen来帮助我们生成golang代码和yaml文件。生成的规则是通过maker comments来描述的。
maker comments格式描述为:
一般来说maker comments可以分为三类:
- empty类型: 是boolen类型的flag,表示是否开启某项沟通能力
// +kubebuilder:validation:Optional
- Anonymous类型:接受单值类型的参数
// +kubebuilder:validation:MaxItems=2
- Multi-option类型:接受多个参数,并通过逗号隔开
// +kubebuilder:printcolumn:JSONPath=".status.replicas",name=Replicas,type=string
参考:
- Markers for Config/Code Generation
- controller-gen CLI
6.3 controller-gen
controller-gen在operator sdk创建project的bin目录下。使用方式如下所示:
tingshuai.yts@B-5BBCMD6M-2026 memcached-operator % ./bin/controller-gen -hhh
Usage:
controller-gen [flags]
Examples:
# Generate RBAC manifests and crds for all types under apis/,
# outputting crds to /tmp/crds and everything else to stdout
controller-gen rbac:roleName=<role name> crd paths=./apis/... output:crd:dir=/tmp/crds output:stdout
# Generate deepcopy/runtime.Object implementations for a particular file
controller-gen object paths=./apis/v1beta1/some_types.go
# Generate OpenAPI v3 schemas for API packages and merge them into existing CRD manifests
controller-gen schemapatch:manifests=./manifests output:dir=./manifests paths=./pkg/apis/...
# Run all the generators for a given project
controller-gen paths=./apis/...
# Explain the markers for generating CRDs, and their arguments
controller-gen crd -ww
Flags:
-h, --detailed-help count print out more detailed help
(up to -hhh for the most detailed output, or -hhhh for json output)
--help print out usage and a summary of options
--version show version
-w, --which-markers count print out all markers available with the requested generators
(up to -www for the most detailed output, or -wwww for json output)
Options
generators
+webhook package
generates (partial) {Mutating,Validating}WebhookConfiguration objects.
+schemapatch package
patches existing CRDs with new schemata.
It will generate output for each "CRD Version" (API version of the CRD type itself) , e.g. apiextensions/v1) available.
[generateEmbeddedObjectMeta=<bool>]
specifies if any embedded ObjectMeta in the CRD should be generated
manifests=<string>
contains the CustomResourceDefinition YAML files.
[maxDescLen=<int>]
specifies the maximum description length for fields in CRD's OpenAPI schema.
0 indicates drop the description for all fields completely. n indicates limit the description to at most n characters and truncate the description to closest sentence boundary if it exceeds n characters.
+rbac package
generates ClusterRole objects.
roleName=<string>
sets the name of the generated ClusterRole.
+object package
generates code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
[headerFile=<string>]
specifies the header text (e.g. license) to prepend to generated files.
[year=<string>]
specifies the year to substitute for " YEAR" in the header file.
+crd package
generates CustomResourceDefinition objects.
[allowDangerousTypes=<bool>]
allows types which are usually omitted from CRD generation because they are not recommended.
Currently the following additional types are allowed when this is true: float32 float64
Left unspecified, the default is false
[crdVersions=<[]string>]
specifies the target API versions of the CRD type itself to generate. Defaults to v1.
Currently, the only supported value is v1.
The first version listed will be assumed to be the "default" version and will not get a version suffix in the output filename.
You'll need to use "v1" to get support for features like defaulting, along with an API server that supports it (Kubernetes 1.16+).
[generateEmbeddedObjectMeta=<bool>]
specifies if any embedded ObjectMeta in the CRD should be generated
[maxDescLen=<int>]
specifies the maximum description length for fields in CRD's OpenAPI schema.
0 indicates drop the description for all fields completely. n indicates limit the description to at most n characters and truncate the description to closest sentence boundary if it exceeds n characters.
generic
+paths package
=<[]string> represents paths and go-style path patterns to use as package roots.
output rules (optionally as output:<generator>:...)
+output:artifacts package
outputs artifacts to different locations, depending on whether they're package-associated or not.
Non-package associated artifacts are output to the Config directory, while package-associated ones are output to their package's source files' directory, unless an alternate path is specified in Code.
[code=<string>]
overrides the directory in which to write new code (defaults to where the existing code lives).
config=<string>
points to the directory to which to write configuration.
+output:dir package
=<string> outputs each artifact to the given directory, regardless of if it's package-associated or not.
+output:none package
skips outputting anything.
+output:stdout package
outputs everything to standard-out, with no separation.
Generally useful for single-artifact outputs.
operator sdk在创建工程时,已经为我们创建了make file文件,因此,我们使用make 命令即可以根据memcached type的定义来创建代码。
tingshuai.yts@B-5BBCMD6M-2026 memcached-operator % make generate
/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..." # 真实执行的命令
命令分析
- paths描述resource type定义的位置。"./..." 是查看当前目录和所有子目录的含义。
- object描述生成的代码中包含DeepCopy, DeepCopyInto, and DeepCopyObject method 的实现
执行完上述命令后,会在api/v1alpha1下面生成zz_generated.deepcopy.go 代码,代码内容如下所示:
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 2022.
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.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Memcached) DeepCopyInto(out *Memcached) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached.
func (in *Memcached) DeepCopy() *Memcached {
if in == nil {
return nil
}
out := new(Memcached)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Memcached) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MemcachedList) DeepCopyInto(out *MemcachedList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Memcached, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList.
func (in *MemcachedList) DeepCopy() *MemcachedList {
if in == nil {
return nil
}
out := new(MemcachedList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *MemcachedList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec.
func (in *MemcachedSpec) DeepCopy() *MemcachedSpec {
if in == nil {
return nil
}
out := new(MemcachedSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) {
*out = *in
if in.Nodes != nil {
in, out := &in.Nodes, &out.Nodes
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus.
func (in *MemcachedStatus) DeepCopy() *MemcachedStatus {
if in == nil {
return nil
}
out := new(MemcachedStatus)
in.DeepCopyInto(out)
return out
}
再次执行make manifests来生成api定义对应的yaml文件
tingshuai.yts@B-5BBCMD6M-2026 memcached-operator % make manifests
./bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
会生成config/crd/bases/cache.example.com_memcacheds.yaml文件,文件中描述了crd的定义。文件内容如下:
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.7.0
creationTimestamp: null
name: memcacheds.cache.example.com
spec:
group: cache.example.com
names:
kind: Memcached
listKind: MemcachedList
plural: memcacheds
singular: memcached
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Memcached is the Schema for the memcacheds API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: MemcachedSpec defines the desired state of Memcached
properties:
size:
description: Size is the size of the memcached deployment
format: int32
minimum: 0
type: integer
required:
- size
type: object
status:
description: MemcachedStatus defines the observed state of Memcached
properties:
nodes:
description: Nodes are the names of the memcached pods
items:
type: string
type: array
required:
- nodes
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
7 编写controller业务代码
7.1 controller框架代码解析
框架生成的controller代码位于controllers/memcached_controller.go ,初始内容如下:
package controllers
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
cachev1alpha1 "github.com/example/memcached-operator/api/v1alpha1"
)
// MemcachedReconciler reconciles a Memcached object
type MemcachedReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/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 Memcached 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.10.0/pkg/reconcile
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
// your logic here
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cachev1alpha1.Memcached{}).
Complete(r)
}
框架代码中首先定义了MemcachedReconciler类型
- client.Client为interface,定义了kubernetes object的CURD相关操作
- runtime.Scheme 定义了序列化和反序列化api object的method
type MemcachedReconciler struct {
client.Client
Scheme *runtime.Scheme
}
框架代码中通过mark comment定义了operator对memcached的权限信息
- 当前operator对memcacheds具有get;list;watch;create;update;patch;delete权限
- 当前operator对memcacheds/status具有get;update;patch权限
- 当前operator对memcacheds/finalizers具有update权限
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update
框架代码为MemcachedReconciler生成了Reconcile function,该function是controller的主体
- Reconcile的作用是保证memcached的status同spec中描述的保持一致;
- 每当有memcached operator监控的resource发生变更时,Reconcile function就会被调用;
- 参数含义:
- context.Context: A Context carries a deadline, a cancellation signal, and other values across API boundaries;
- ctrl.Request: 包含出发Reconcile的kubernetes object的name和namespace
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
// your logic here
return ctrl.Result{}, nil
}
框架代码生成的SetUpWithManager function用来说明当前operator watch哪些resource:
- NewControllerManagedBy()创建一个configuration,用来配置当前manager
- For(&cachev1alpha1.Memcached{}) 设置operator 监听 Memcached的变更,所有Memcached的Add/Delete/Update都会
// SetupWithManager sets up the controller with the Manager.
func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cachev1alpha1.Memcached{}).
Complete(r)
}
7.2 权限编写
根据memcached operator的业务逻辑,需要添加deployment和pod的操作权限
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
7.3 Reconcile Loop编写
reconcile loop中主要为如下流程:
- 获取event相关的memcached object;
- 判断memcached相关联的deployment是否存在;若不存在则执行deployment的create
- 判断deployment的size同memcached中spec的size是否相同,如果不同则执行deployment的update
- 判断memcached的status字段pod内容是否符合预期,如果不符合预期则更新status字段内容
完整代码见:https://github.com/operator-framework/operator-sdk/blob/latest/testdata/go/v3/memcached-operator/controllers/memcached_controller.go
7.3.1 获取memcached object
通过req.NamespacedName来找到对应的memcached
memcached := &cachev1alpha1.Memcached{}
err := r.Get(ctx, req.NamespacedName, memcached)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
log.Info("Memcached resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
// Error reading the object - requeue the request.
log.Error(err, "Failed to get Memcached")
return ctrl.Result{}, err
}
7.3.2 判断deployment 存在性
通过memcache的name和namespace查找对应的deployment;若找不到则调用自定义的deploymentForMemcached来创建deployment
found := &appsv1.Deployment{}
err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)
if err != nil && errors.IsNotFound(err) {
// Define a new deployment
dep := r.deploymentForMemcached(memcached)
log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
err = r.Create(ctx, dep)
if err != nil {
log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)
return ctrl.Result{}, err
}
// Deployment created successfully - return and requeue
return ctrl.Result{Requeue: true}, nil
} else if err != nil {
log.Error(err, "Failed to get Deployment")
return ctrl.Result{}, err
}
7.3.3 判断memcached size
若size与spec中的不一致,则调用自定义的update接口进行更新
size := memcached.Spec.Size
if *found.Spec.Replicas != size {
found.Spec.Replicas = &size
err = r.Update(ctx, found)
if err != nil {
log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)
return ctrl.Result{}, err
}
// Ask to requeue after 1 minute in order to give enough time for the
// pods be created on the cluster side and the operand be able
// to do the next update step accurately.
return ctrl.Result{RequeueAfter: time.Minute}, nil
}
7.3.4 判断memcached status
memcached的status字段描述了pod的name信息,因此每个loop都要判断status字段是否要更新:
- 首先获取podlist,然后同memcached中记录的进行比较
- 如果不同则更新status
// Update the Memcached status with the pod names
// List the pods for this memcached's deployment
podList := &corev1.PodList{}
listOpts := []client.ListOption{
client.InNamespace(memcached.Namespace),
client.MatchingLabels(labelsForMemcached(memcached.Name)),
}
if err = r.List(ctx, podList, listOpts...); err != nil {
log.Error(err, "Failed to list pods", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name)
return ctrl.Result{}, err
}
podNames := getPodNames(podList.Items)
// Update status.Nodes if needed
if !reflect.DeepEqual(podNames, memcached.Status.Nodes) {
memcached.Status.Nodes = podNames
err := r.Status().Update(ctx, memcached)
if err != nil {
log.Error(err, "Failed to update Memcached status")
return ctrl.Result{}, err
}
}
8 编译调试
调试operator一般有两种方式
- in cluster:将operator发布到k8s集群中
- out cluster:在k8s 环境外部启动一个golang的operator进程;本文采用这种方式
operator sdk中通过out cluster的方式的命令为make install run:
- 首先、调用controller-gen创建rbac 、crd 、webhook的代码和yaml (注:这一步是冗余操作,其实可以不执行)
- 然后、调用kustomize将crd创建
- 最后、执行main.go
memcached-operator % make install run
bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.example.com created
bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go run ./main.go
2022-02-24T15:13:19.997+0800 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"}
2022-02-24T15:13:19.997+0800 INFO setup starting manager
2022-02-24T15:13:19.998+0800 INFO starting metrics server {"path": "/metrics"}
2022-02-24T15:13:19.998+0800 INFO controller.memcached Starting EventSource {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "source": "kind source: /, Kind="}
2022-02-24T15:13:19.998+0800 INFO controller.memcached Starting Controller {"reconciler group": "cache.example.com", "reconciler kind": "Memcached"}
2022-02-24T15:13:20.098+0800 INFO controller.memcached Starting workers {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "worker count": 1}
2022-02-24T15:16:33.317+0800 INFO controller.memcached Creating a new Deployment {"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "name": "memcached-sample", "namespace": "default", "Deployment.Namespace": "default", "Deployment.Name": "memcached-sample"}