为 Memcached 构建基于 Go 的 Operator 示例

Operator SDK 中的 Go 编程语言支持可以利用 Operator SDK 中的 Go 编程语言支持,为 Memcached 构
建基于 Go 的 Operator 示例、分布式键值存储并管理其生命周期。

  • 安装 Docker Desktop,并启动内置的 Kubernetes 集群
  • 注册一个hub.docker.com 账户,需要将本地构建好的镜像推送至公开仓库中
  • 安装 operator SDK CLI: brew install operator-sdk
  • 安装 Go: brew install go

使用 Operator SDK CLI 创建名为 memcached-operator 的项目。

mkdir -p $HOME/projects/memcached-operator
cd $HOME/projects/memcached-operator
go env -w GOPROXY=https://goproxy.cn,direct

operator-sdk init \
--domain=jxlwqq.github.io \
--repo=github.com/jxlwqq/memcached-operator \
--skip-go-version-check

使用 Operator SDK CLI 创建自定义资源定义(CRD)API 和控制器。

运行以下命令创建带有组 cache、版本 v1alpha1 和种类 Memcached 的 API:

operator-sdk create api \
--resource=true \
--controller=true \
--group=cache \
--version=v1alpha1 \
--kind=Memcached

定义 Memcached 自定义资源(CR)的 API。

修改 api/v1alpha1/memcached_types.go 中的 Go 类型定义,使其具有以下 spec 和 status

type MemcachedSpec struct {
    // +kubebuilder:validation:Minimum=0
    // Size is the size of the memcached deployment
    Size int32 json:"size"
}

type MemcachedStatus struct {
    // Nodes are the names of the memcached pods
    Nodes []string json:"nodes"
}

为资源类型更新生成的代码:

make generate

运行以下命令以生成和更新 CRD 清单:

make manifests

在本例中,将生成的控制器文件 controllers/memcached_controller.go 替换为以下示例实现:

/*
Copyright 2021.

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 controllers

import (
    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/types"
    "reflect"
    "time"

    "context"

    "k8s.io/apimachinery/pkg/runtime"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    ctrllog "sigs.k8s.io/controller-runtime/pkg/log"

    cachev1alpha1 "github.com/jxlwqq/memcached-operator/api/v1alpha1"
)

// MemcachedReconciler reconciles a Memcached object
type MemcachedReconciler struct {
    client.Client
    Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,resources=memcacheds/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,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

// 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.9.2/pkg/reconcile
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := ctrllog.FromContext(ctx)

    // Fetch the Memcached instance
    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
    }

    // Check if the deployment already exists, if not create a new one
    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
    }

    // Ensure the deployment size is the same as the spec
    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
    }

    // 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
        }
    }

    return ctrl.Result{}, nil
}

// deploymentForMemcached returns a memcached Deployment object
func (r *MemcachedReconciler) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment {
    ls := labelsForMemcached(m.Name)
    replicas := m.Spec.Size

    dep := &appsv1.Deployment{
        ObjectMeta: metav1.ObjectMeta{
            Name:      m.Name,
            Namespace: m.Namespace,
        },
        Spec: appsv1.DeploymentSpec{
            Replicas: &replicas,
            Selector: &metav1.LabelSelector{
                MatchLabels: ls,
            },
            Template: corev1.PodTemplateSpec{
                ObjectMeta: metav1.ObjectMeta{
                    Labels: ls,
                },
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{{
                        Image:   "memcached:1.4.36-alpine",
                        Name:    "memcached",
                        Command: []string{"memcached", "-m=64", "-o", "modern", "-v"},
                        Ports: []corev1.ContainerPort{{
                            ContainerPort: 11211,
                            Name:          "memcached",
                        }},
                    }},
                },
            },
        },
    }
    // Set Memcached instance as the owner and controller
    ctrl.SetControllerReference(m, dep, r.Scheme)
    return dep
}

// labelsForMemcached returns the labels for selecting the resources
// belonging to the given memcached CR name.

func labelsForMemcached(name string) map[string]string {
    return map[string]string{"app": "memcached", "memcached_cr": name}
}

// getPodNames returns the pod names of the array of pods passed in
func getPodNames(pods []corev1.Pod) []string {
    var podNames []string
    for _, pod := range pods {
        podNames = append(podNames, pod.Name)
    }
    return podNames
}

// SetupWithManager sets up the controller with the Manager.

func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&cachev1alpha1.Memcached{}).
        Owns(&appsv1.Deployment{}).
        Complete(r)
}

运行以下命令以生成和更新 CRD 清单:

make manifests

捆绑 Operator,并使用 Operator Lifecycle Manager(OLM)在集群中部署。

修改 Makefile 中 IMAGE_TAG_BASE 和 IMG:

IMAGE_TAG_BASE ?= docker.io/jxlwqq/memcached-operator
IMG ?= $(IMAGE_TAG_BASE):latest

构建镜像:

make docker-build

将镜像推送到镜像仓库:

make docker-push

运行 make bundle 命令创建 Operator 捆绑包清单,并依次填入名称、作者等必要信息:

make bundle

构建捆绑包镜像:

make bundle-build

推送捆绑包镜像:

make bundle-push

使用 Operator Lifecycle Manager 部署 Operator:

切换至本地集群
kubectl config use-context docker-desktop
安装 olm
operator-sdk olm install
使用 Operator SDK 中的 OLM 集成在集群中运行 Operator
operator-sdk run bundle docker.io/jxlwqq/memcached-operator-bundle:v0.0.1

查看 Pod:

NAME                                                              READY   STATUS      RESTARTS   AGE
7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          67s
docker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          84s
memcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          41s

编辑 config/samples/cache_v1alpha1_memcached.yaml 上的 Memcached CR 清单示例,使其包含
以下规格:

apiVersion: cache.jxlwqq.github.io/v1alpha1
kind: Memcached
metadata:
  name: memcached-sample
spec:
  size: 2

创建 CR:

kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml

查看 Pod:

NAME                                                              READY   STATUS      RESTARTS   AGE
7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          4m14s
docker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          4m31s
memcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          3m48s
memcached-sample-6c765df685-d8ppn                                 1/1     Running     0          29s
memcached-sample-6c765df685-dtw9l                                 1/1     Running     0          29s

更新 CR:

查看 Pod:

NAME                                                              READY   STATUS      RESTARTS   AGE
7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          6m52s
docker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          7m9s
memcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          6m26s
memcached-sample-6c765df685-d8ppn                                 1/1     Running     0          3m7s
memcached-sample-6c765df685-dtw9l                                 1/1     Running     0          3m7s
memcached-sample-6c765df685-n7ctq                                 1/1     Running     0          6s
operator-sdk cleanup memcached-operator
operator-sdk olm uninstall

Original: https://www.cnblogs.com/jxlwqq/p/15243706.html
Author: jxlwqq
Title: 为 Memcached 构建基于 Go 的 Operator 示例

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/516612/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Go之Logrus用法入门

    Logrus是Go (golang)的结构化日志程序,完全兼容标准库的API日志程序。Logrus is a structured logger for Go (golang), …

    Go语言 2023年5月25日
    057
  • Golang Zap日志

    Zap日志解析 Config.yaml zap: level: ‘info’ #日志级别 format: ‘console’ #输出的级别,有console和json prefix…

    Go语言 2023年5月25日
    063
  • Golang开源流媒体服务器(RTMP/RTSP/HLS/FLV等协议)

    一. lal 简介 lal是开源直播流媒体网络传输项目,主要由三部分组成: lalserver:流媒体转发服务器。类似于 nginx-rtmp-module等服务,但支持更多的协议…

    Go语言 2023年5月25日
    060
  • Go语言之高级篇beego框架之controller调用model

    一、controller调用model 开发规范,就该把对数据库的操作写在model文件夹中。 示例: views/main.go routers/router.go models…

    Go语言 2023年5月29日
    057
  • golang常用库包:Go依赖注入(DI)工具-wire使用

    google 出品的依赖注入库 wire:https://github.com/google/wire 什么是依赖注入 依赖注入 ,英文全名是 dependency injecti…

    Go语言 2023年5月25日
    056
  • 解决go-micro与其它gRPC框架之间的通信问题

    在之前的文章中分别介绍了使用gRPC官方插件和go-micro插件开发gRPC应用程序的方式,都能正常走通。不过当两者混合使用的时候,互相访问就成了问题。比如使用go-micro插…

    Go语言 2023年5月25日
    067
  • golang多版本管理工具

    前言 以往我安装 go环境都是去网站上下载对应文件,然后本地解压。每次发布新版本都这样做太麻烦了,所以我在寻找多版本管理工具。 [En] It’s too troubl…

    Go语言 2023年5月25日
    054
  • Go Micro Dashboard – 实现细节(一)

    前言 Go Micro Dashboard是基于go-micro和ng-alain开发的, 它既是go-micro开发过程中的工具,也可以作为学习go-micro的实际案例。接下来…

    Go语言 2023年5月25日
    069
  • Golang实现set

    Golang语言本身未实现set,但是实现了map golang的map是一种无序的键值对的集合,其中键是唯一的 而set是键的不重复的集合,因此可以用map来实现set 由于ma…

    Go语言 2023年5月25日
    049
  • Go基础知识梳理(四)

    Go基础知识梳理(四) GO的哲学是”不要通过共享内存来通信,而是通过通信来共享内存”,通道是GO通过通信来共享内存的载体。 rumtime包常用方法 ru…

    Go语言 2023年5月25日
    036
  • Go语言:包管理基础知识

    起因是,遇到一个问题: 经查阅资料,很可能跟包管理有关,之前有了解过忘了就再学一遍顺便解决问题。 学习资料: GO111MODULE 是个啥? – 知乎 (zhihu….

    Go语言 2023年5月25日
    055
  • 自己实现一个Controller——标准型

    标准Controller 上一篇通过一个简单的例子,编写了一个controller-manager,以及一个极简单的controller。从而对controller的开发有个最基本…

    Go语言 2023年5月25日
    077
  • RSA.js_公钥加密_NoPadding_golang实现_n_e_pem格式互转

    转载注明来源:本文链接 来自osnosn的博客,写于 2021-09-13. 参考 PKCS1【Golang 实现RSA加密解密】 PKCS1,密钥格式转换(需第三方包)【RSA非…

    Go语言 2023年5月25日
    054
  • DDIA 学习笔记

    第一章 可靠性、可扩展性、可维护性 ​ 可靠性: 系统在 困境(adversity)(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准。 ​ 可靠…

    Go语言 2023年5月25日
    066
  • croncli 定时器命令(golang)

    定时器是执行任务时的常用功能,配置系统的定时任务太麻烦,所以就想用golang简单实现一个定时器命令。 定时器的参数包括: $ croncli -h 定时器命令 Usage: cr…

    Go语言 2023年5月25日
    050
  • go语言调用everything的SDK接口

    介绍 官方SDK地址 本项目会将官方dll编译到可执行程序中,运行时无需考虑dll问题。 根据官方介绍,使用SDK前需要运行 everything程序。 执行 go build -…

    Go语言 2023年5月25日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球