为 Memcached 构建基于 Go 的 Operator 示例
Operator SDK 中的 Go 编程语言支持可以利用 Operator SDK 中的 Go 编程语言支持,为 Memcached 构
建基于 Go 的 Operator 示例、分布式键值存储并管理其生命周期。
github: https://github.com/jxlwqq/memcached-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
创建 API 和控制器
使用 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,并使用 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
成功后访问:https://hub.docker.com/r/jxlwqq/memcached-operator
运行 make bundle 命令创建 Operator 捆绑包清单,并依次填入名称、作者等必要信息:
make bundle
构建捆绑包镜像:
make bundle-build
推送捆绑包镜像:
make bundle-push
成功后访问:https://hub.docker.com/r/jxlwqq/memcached-operator-bundle
使用 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:
kubectl patch memcached memcached-sample -p '{"spec":{"size": 3}}' --type=merge
查看 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
为 Memcached 构建基于 Go 的 Operator 示例的更多相关文章
- SSM框架整合环境构建——基于Spring4和Mybatis3
目录 环境 配置说明 所需jar包 配置db.properties 配置log4j.properties 配置spring.xml 配置mybatis-spring.xml 配置springmvc.x ...
- [WCF安全3]使用wsHttpBinding构建基于SSL与UserName授权的WCF应用程序
上一篇文章中介绍了如何使用wsHttpBinding构建UserName授权的WCF应用程序,本文将为您介绍如何使用wsHttpBinding构建基于SSL的UserName安全授权的WCF应用程序. ...
- 【转】nginx+memcached构建页面缓存应用
如需转载请注明出处: http://www.ttlsa.com/html/2418.html nginx的memcached_module模块可以直接从memcached服务器中读取内容后输出,后续的 ...
- css013 构建基于浮动的布局
css013 构建基于浮动的布局 基于浮动的布局时利用float属性是网页上的元素并排,并创建列 float有三个值:left .right .none 1.假设要把一张图片浮动到网页的左侧 .flo ...
- 构建基于WCF Restful Service的服务
前言 传统的Asmx服务,由于遵循SOAP协议,所以返回内容以xml方式组织.并且客户端需要添加服务端引用才能使用(虽然看到网络上已经提供了这方面的Dynamic Proxy,但是没有这种方式简便), ...
- memcached构建集群分析之一
memcached本身是不支持集群的,集群所关注的容灾.容错.宕机恢复机制统统都没有,实战中需要自己实现容灾机制. memcached集群相比memcached的优势: 巨量数据分布到集群的多台应用主 ...
- maven-bundle-plugin插件, 用maven构建基于osgi的web应用
maven-bundle-plugin 2.4.0以下版本导出META-INF中的内容到MANIFEST.MF中 今天终于把maven-bundle-plugin不能导出META-INF中的内容到Ex ...
- 最简单的基于Flash的流媒体示例:网页播放器(HTTP,RTMP,HLS)
http://blog.csdn.net/leixiaohua1020/article/details/43936415 ======================================= ...
- 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数
整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...
随机推荐
- Centos LInux 7.0 内核3.1 升级简化流程
Centos LInux 7.0 内核3.1 升级建华流程 1)#导入ELRepo软件仓库的公共秘钥rpm --import https://www.elrepo.org/RPM-GPG-KEY-el ...
- CentOS 8.0 安装docker 报错:Problem: package docker-ce-3:19.03.4-3.el7.x86_64 requires containerd.io >= 1
1.错误内容 package docker-ce-3:19.03.2-3.el7.x86_64 requires containerd.io >= 1.2.2-3, but none of th ...
- Android全新UI编程 - Jetpack Compose 超详细教程
1. 简介 Jetpack Compose是在2019Google i/O大会上发布的新的库.Compose库是用响应式编程的方式对View进行构建,可以用更少更直观的代码,更强大的功能,能提高开发速 ...
- 案例分享:Qt政务标签设计器,标签排版软件定制与打印
需求 1.标签设计器: 2.具备文字排版功能: 3.支持六种排版格式: 4.排版后可以输出打印(demo中不包含): 5.排版后可以输出标签的指定协议文本FBD格式: 6.可以调整对应标 ...
- WPF自定义控件二:Border控件与TextBlock控件轮播动画
需求:实现Border轮播动画与TextBlock动画 XAML代码如下: <Window.Resources> <Storyboard x:Key="OnLoaded1& ...
- Java之JSTL标签与JavaBean
Java之JSTL标签与JavaBean JSP.JSTL标签及 EL表达式 <!-- jstl表达式--> <dependency> <groupId>javax ...
- Linux修改Ip简单知识了解
1. 在终端输入:vim /etc/sysconfig/network-scripts/ifcfg-etho(etho是指的安装centos的产生的网卡) 2.按i开始编辑,填写ip地址.子网掩码.网 ...
- Spring中的@Transactional必须要了解的概念
spring中的@Transactional基于动态代理的机制,提供了一种透明的事务管理机制,方便快捷解决在开发中碰到的问题. 一般使用是通过如下代码对方法或接口或类注释: 1 @Transactio ...
- mongoTemplate 条件查询
构建条件方法 @Override public Query getQuery(ReportParam param){ //check MeenoAssert.hasLength(param.getUu ...
- 怎样在自己的 Web 中加入强大的日志系统系统?slf4j 的日志插件必须要知道!
对于程序猿来讲,一个应用程序的日志管理是极为重要的.因为,它可以帮助我们随时查看应用程序的运行状态.执行效果等信息,从而监控软件系统.或是根据日志信息解决一些重要的问题. 但是在 Java 应用程序中 ...