{"id":15729274,"url":"https://github.com/jxlwqq/memcached-operator","last_synced_at":"2025-10-07T14:20:27.792Z","repository":{"id":43051749,"uuid":"404261779","full_name":"jxlwqq/memcached-operator","owner":"jxlwqq","description":"memcached operator","archived":false,"fork":false,"pushed_at":"2021-09-13T03:04:28.000Z","size":92,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-30T04:41:19.580Z","etag":null,"topics":["kubernetes","olm","operator-framework","operator-sdk"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jxlwqq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-08T08:01:16.000Z","updated_at":"2022-09-18T08:46:44.000Z","dependencies_parsed_at":"2022-07-09T08:46:20.123Z","dependency_job_id":null,"html_url":"https://github.com/jxlwqq/memcached-operator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxlwqq%2Fmemcached-operator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxlwqq%2Fmemcached-operator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxlwqq%2Fmemcached-operator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jxlwqq%2Fmemcached-operator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jxlwqq","download_url":"https://codeload.github.com/jxlwqq/memcached-operator/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250530689,"owners_count":21445837,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kubernetes","olm","operator-framework","operator-sdk"],"created_at":"2024-10-03T23:20:58.025Z","updated_at":"2025-10-07T14:20:22.756Z","avatar_url":"https://github.com/jxlwqq.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# memcached-operator\n\nOperator SDK 中的 Go 编程语言支持可以利用 Operator SDK 中的 Go 编程语言支持，为 Memcached 构\n建基于 Go 的 Operator 示例、分布式键值存储并管理其生命周期。\n\n### 前置条件\n\n* 安装 Docker Desktop，并启动内置的 Kubernetes 集群\n* 注册一个 [hub.docker.com](https://hub.docker.com/) 账户，需要将本地构建好的镜像推送至公开仓库中\n* 安装 operator SDK CLI: `brew install operator-sdk`\n* 安装 Go: `brew install go`\n\n本示例推荐的依赖版本：\n\n* Docker Desktop: \u003e= 4.0.0\n* Kubernetes: \u003e= 1.21.4\n* Operator-SDK: \u003e= 1.11.0\n* Go: \u003e= 1.17\n\n\n\u003e jxlwqq 为笔者的 ID，命令行和代码中涉及的个人 ID，均需要替换为读者自己的，包括\n\u003e * `--domain=`\n\u003e * `--repo=`\n\u003e * `//+kubebuilder:rbac:groups=`\n\u003e * `IMAGE_TAG_BASE ?=`\n\n\n### 创建项目\n\n使用 Operator SDK CLI 创建名为 memcached-operator 的项目。\n```shell\nmkdir -p $HOME/projects/memcached-operator\ncd $HOME/projects/memcached-operator\ngo env -w GOPROXY=https://goproxy.cn,direct\n\noperator-sdk init \\\n--domain=jxlwqq.github.io \\\n--repo=github.com/jxlwqq/memcached-operator \\\n--skip-go-version-check\n```\n\n### 创建 API 和控制器\n\n使用 Operator SDK CLI 创建自定义资源定义（CRD）API 和控制器。\n\n运行以下命令创建带有组 cache、版本 v1alpha1 和种类 Memcached 的 API：\n\n```shell\noperator-sdk create api \\\n--resource=true \\\n--controller=true \\\n--group=cache \\\n--version=v1alpha1 \\\n--kind=Memcached\n```\n\n定义 Memcached 自定义资源（CR）的 API。\n\n修改 api/v1alpha1/memcached_types.go 中的 Go 类型定义，使其具有以下 spec 和 status\n\n```go\ntype MemcachedSpec struct {\n\t// +kubebuilder:validation:Minimum=0\n\t// Size is the size of the memcached deployment\n\tSize int32 `json:\"size\"`\n}\n\ntype MemcachedStatus struct {\n    // Nodes are the names of the memcached pods\n\tNodes []string `json:\"nodes\"`\n}\n```\n\n为资源类型更新生成的代码：\n```shell\nmake generate\n```\n\n运行以下命令以生成和更新 CRD 清单：\n```shell\nmake manifests\n```\n\n### 实现控制器\n\n在本例中，将生成的控制器文件 controllers/memcached_controller.go 替换为以下示例实现：\n\n```go\n/*\nCopyright 2021.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"context\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\tctrllog \"sigs.k8s.io/controller-runtime/pkg/log\"\n\n\tcachev1alpha1 \"github.com/jxlwqq/memcached-operator/api/v1alpha1\"\n)\n\n// MemcachedReconciler reconciles a Memcached object\ntype MemcachedReconciler struct {\n\tclient.Client\n\tScheme *runtime.Scheme\n}\n\n//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete\n//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,resources=memcacheds/status,verbs=get;update;patch\n//+kubebuilder:rbac:groups=cache.jxlwqq.github.io,resources=memcacheds/finalizers,verbs=update\n//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete\n//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch\n\n// Reconcile is part of the main kubernetes reconciliation loop which aims to\n// move the current state of the cluster closer to the desired state.\n// TODO(user): Modify the Reconcile function to compare the state specified by\n// the Memcached object against the actual cluster state, and then\n// perform operations to make the cluster state reflect the state specified by\n// the user.\n//\n// For more details, check Reconcile and its Result here:\n// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile\nfunc (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := ctrllog.FromContext(ctx)\n\n\t// Fetch the Memcached instance\n\tmemcached := \u0026cachev1alpha1.Memcached{}\n\terr := r.Get(ctx, req.NamespacedName, memcached)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\tlog.Info(\"Memcached resource not found. Ignoring since object must be deleted\")\n\t\t\treturn ctrl.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlog.Error(err, \"Failed to get Memcached\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Check if the deployment already exists, if not create a new one\n\tfound := \u0026appsv1.Deployment{}\n\terr = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)\n\tif err != nil \u0026\u0026 errors.IsNotFound(err) {\n\t\t// Define a new deployment\n\t\tdep := r.deploymentForMemcached(memcached)\n\t\tlog.Info(\"Creating a new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\terr = r.Create(ctx, dep)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to create new Deployment\", \"Deployment.Namespace\", dep.Namespace, \"Deployment.Name\", dep.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t\t// Deployment created successfully - return and requeue\n\t\treturn ctrl.Result{Requeue: true}, nil\n\t} else if err != nil {\n\t\tlog.Error(err, \"Failed to get Deployment\")\n\t\treturn ctrl.Result{}, err\n\t}\n\n\t// Ensure the deployment size is the same as the spec\n\tsize := memcached.Spec.Size\n\tif *found.Spec.Replicas != size {\n\t\tfound.Spec.Replicas = \u0026size\n\t\terr = r.Update(ctx, found)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to update Deployment\", \"Deployment.Namespace\", found.Namespace, \"Deployment.Name\", found.Name)\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t\t// Ask to requeue after 1 minute in order to give enough time for the\n\t\t// pods be created on the cluster side and the operand be able\n\t\t// to do the next update step accurately.\n\t\treturn ctrl.Result{RequeueAfter: time.Minute}, nil\n\t}\n\n\t// Update the Memcached status with the pod names\n\t// List the pods for this memcached's deployment\n\tpodList := \u0026corev1.PodList{}\n\tlistOpts := []client.ListOption{\n\t\tclient.InNamespace(memcached.Namespace),\n\t\tclient.MatchingLabels(labelsForMemcached(memcached.Name)),\n\t}\n\tif err = r.List(ctx, podList, listOpts...); err != nil {\n\t\tlog.Error(err, \"Failed to list pods\", \"Memcached.Namespace\", memcached.Namespace, \"Memcached.Name\", memcached.Name)\n\t\treturn ctrl.Result{}, err\n\t}\n\tpodNames := getPodNames(podList.Items)\n\n\t// Update status.Nodes if needed\n\tif !reflect.DeepEqual(podNames, memcached.Status.Nodes) {\n\t\tmemcached.Status.Nodes = podNames\n\t\terr := r.Status().Update(ctx, memcached)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Failed to update Memcached status\")\n\t\t\treturn ctrl.Result{}, err\n\t\t}\n\t}\n\n\treturn ctrl.Result{}, nil\n}\n\n// deploymentForMemcached returns a memcached Deployment object\nfunc (r *MemcachedReconciler) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment {\n\tls := labelsForMemcached(m.Name)\n\treplicas := m.Spec.Size\n\n\tdep := \u0026appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      m.Name,\n\t\t\tNamespace: m.Namespace,\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: \u0026replicas,\n\t\t\tSelector: \u0026metav1.LabelSelector{\n\t\t\t\tMatchLabels: ls,\n\t\t\t},\n\t\t\tTemplate: corev1.PodTemplateSpec{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tLabels: ls,\n\t\t\t\t},\n\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\tContainers: []corev1.Container{{\n\t\t\t\t\t\tImage:   \"memcached:1.4.36-alpine\",\n\t\t\t\t\t\tName:    \"memcached\",\n\t\t\t\t\t\tCommand: []string{\"memcached\", \"-m=64\", \"-o\", \"modern\", \"-v\"},\n\t\t\t\t\t\tPorts: []corev1.ContainerPort{{\n\t\t\t\t\t\t\tContainerPort: 11211,\n\t\t\t\t\t\t\tName:          \"memcached\",\n\t\t\t\t\t\t}},\n\t\t\t\t\t}},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\t// Set Memcached instance as the owner and controller\n\tctrl.SetControllerReference(m, dep, r.Scheme)\n\treturn dep\n}\n\n// labelsForMemcached returns the labels for selecting the resources\n// belonging to the given memcached CR name.\nfunc labelsForMemcached(name string) map[string]string {\n\treturn map[string]string{\"app\": \"memcached\", \"memcached_cr\": name}\n}\n\n// getPodNames returns the pod names of the array of pods passed in\nfunc getPodNames(pods []corev1.Pod) []string {\n\tvar podNames []string\n\tfor _, pod := range pods {\n\t\tpodNames = append(podNames, pod.Name)\n\t}\n\treturn podNames\n}\n\n// SetupWithManager sets up the controller with the Manager.\nfunc (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(\u0026cachev1alpha1.Memcached{}).\n\t\tOwns(\u0026appsv1.Deployment{}).\n\t\tComplete(r)\n}\n```\n\n运行以下命令以生成和更新 CRD 清单：\n```shell\nmake manifests\n```\n\n### 运行 Operator\n\n捆绑 Operator，并使用 Operator Lifecycle Manager（OLM）在集群中部署。\n\n修改 Makefile 中 IMAGE_TAG_BASE 和 IMG：\n\n```makefile\nIMAGE_TAG_BASE ?= docker.io/jxlwqq/memcached-operator\nIMG ?= $(IMAGE_TAG_BASE):latest\n```\n\n构建镜像：\n\n```shell\nmake docker-build\n```\n\n将镜像推送到镜像仓库：\n```shell\nmake docker-push\n```\n\n成功后访问：https://hub.docker.com/r/jxlwqq/memcached-operator\n\n运行 make bundle 命令创建 Operator 捆绑包清单，并依次填入名称、作者等必要信息:\n```shell\nmake bundle\n```\n\n构建捆绑包镜像：\n```shell\nmake bundle-build\n```\n\n推送捆绑包镜像：\n```shell\nmake bundle-push\n```\n\n成功后访问：https://hub.docker.com/r/jxlwqq/memcached-operator-bundle\n\n使用 Operator Lifecycle Manager 部署 Operator:\n\n```shell\n# 切换至本地集群\nkubectl config use-context docker-desktop\n# 安装 olm\noperator-sdk olm install\n# 使用 Operator SDK 中的 OLM 集成在集群中运行 Operator\noperator-sdk run bundle docker.io/jxlwqq/memcached-operator-bundle:v0.0.1\n```\n\n查看 Pod：\n```shell\nNAME                                                              READY   STATUS      RESTARTS   AGE\n7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          67s\ndocker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          84s\nmemcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          41s\n```\n\n\n\n### 创建自定义资源\n\n编辑 config/samples/cache_v1alpha1_memcached.yaml 上的 Memcached CR 清单示例，使其包含\n以下规格：\n\n```yaml\napiVersion: cache.jxlwqq.github.io/v1alpha1\nkind: Memcached\nmetadata:\n  name: memcached-sample\nspec:\n  size: 2\n```\n\n创建 CR：\n```shell\nkubectl apply -f config/samples/cache_v1alpha1_memcached.yaml\n```\n\n查看 Pod：\n```shell\nNAME                                                              READY   STATUS      RESTARTS   AGE\n7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          4m14s\ndocker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          4m31s\nmemcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          3m48s\nmemcached-sample-6c765df685-d8ppn                                 1/1     Running     0          29s\nmemcached-sample-6c765df685-dtw9l                                 1/1     Running     0          29s\n```\n\n更新 CR：\n```shell\nkubectl patch memcached memcached-sample -p '{\"spec\":{\"size\": 3}}' --type=merge\n```\n\n查看 Pod：\n```shell\nNAME                                                              READY   STATUS      RESTARTS   AGE\n7bb60cfcec4a426a75d6ca01153f5e6d3061e175d035791255372bdd7ebrvmd   0/1     Completed   0          6m52s\ndocker-io-jxlwqq-memcached-operator-bundle-v0-0-1                 1/1     Running     0          7m9s\nmemcached-operator-controller-manager-666cfff6c-fxhrk             2/2     Running     0          6m26s\nmemcached-sample-6c765df685-d8ppn                                 1/1     Running     0          3m7s\nmemcached-sample-6c765df685-dtw9l                                 1/1     Running     0          3m7s\nmemcached-sample-6c765df685-n7ctq                                 1/1     Running     0          6s\n```\n\n### 做好清理\n\n```shell\noperator-sdk cleanup memcached-operator\noperator-sdk olm uninstall\n```\n\n### 更多\n\n更多经典示例请参考：https://github.com/jxlwqq/kubernetes-examples\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxlwqq%2Fmemcached-operator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjxlwqq%2Fmemcached-operator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjxlwqq%2Fmemcached-operator/lists"}