概述

在本文中,我们将会详细介绍如何使用 Operator SDK 来快速开发一个 Operator 服务,这个 Operator 服务的功能如下:

  1. 创建一个 Memcached Deployment
  2. 保证 Memcached 配置与在自定义资源中指定的规格一致
  3. 根据对应 Pod 的状态来更新 Memcached 自定义资源的状态

准备工作

  1. 完成 operator-sdk 的安装
  2. 拥有 K8s 集群的 cluster-admin 权限
  3. 能够登录并使用一个镜像仓库

创建一个项目

首先,我们需要使用 operator-sdk 这个命令行工具来创建一个 memcached-operator 项目:

  1. mkdir -p $GOPATH/src/projects/memcached-operator
  2. cd $GOPATH/src/projects/memcached-operator
  3. operator-sdk new memcached-operator
  4. cd memcached-operator
  5. go mod download github.com/operator-framework/operator-sdk
  6. go mod tidy -v
  7. go build ./...

关于项目的结构,可以参考文档:Kubebuilder项目结构

Manager

main.go 的主程序主要用于初始化和启动 Manager。有关manager如何为自定义资源API定义注册方案以及如何设置和运行controller和webhook的更多详细信息,请参阅Kubebuilder entrypoint文档
Manager在创建时,可以指定对应生效的 namespace 来监控相关的资源:

  1. mgr, err := ctrl.NewManager(cfg, manager.Options{Namespace: namespace})

默认情况下,Operator绑定的namespace是当前运行的namespace。如果想要监控全部namepace,那么需要修改Namespace字段为空字符串。

  1. mgr, err := ctrl.NewManager(cfg, manager.Options{Namespace: ""})

关于如何运行你们 Operator 在指定 namespace 或者 cluster 范围生效,可以参考:operator生效范围

创建一个新的 API 和 Controller

下面,我们来创建一个 Memcached 类型的自定义资源对象,它属于 cache Group下,version 为 v1aplha1。

  1. operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller

这将在 api/v1alpha1/memcached_types.go下得到一个Memcached资源API,并在controllers/memcached_controller.go处得到 controller。
Ps: 该命令得到的是单个API Group的场景,如果想要生成多APIs Group,可以参考相关文档
有关Kubernetes API和组版本种类模型的深入解释,请查看这些kubebuilder文档
通常,建议由一个控制器负责管理为项目创建的每个API,以正确遵循 controller-runtime 设置的设计模式。

定义 API

下面,我们需要来定义 Memcached 类型的 API 资源对象本身包含哪些字段。
首先,它应该有一个 MemcachedSpec.Size 字段来指定对应的 Memcached 的实例数量,同时,还需要一个 MemcachedStatus.Node 来存储对应的自定义资源的 Pod 名称。
我们来修改 api/v1alpha1/memcached_types.go 文件来定义 Memcached 自定义资源的格式和状态:

  1. // MemcachedSpec defines the desired state of Memcached
  2. type MemcachedSpec struct {
  3. //+kubebuilder:validation:Minimum=0
  4. // Size is the size of the memcached deployment
  5. Size int32 `json:"size"`
  6. }
  7. // MemcachedStatus defines the observed state of Memcached
  8. type MemcachedStatus struct {
  9. // Nodes are the names of the memcached pods
  10. Nodes []string `json:"nodes"`
  11. }

添加+kubebuilder:subresource:status标记,将状态子资源添加到CRD清单,以便controller可以在不更改CR对象其余部分的情况下更新CR状态:

  1. // Memcached is the Schema for the memcacheds API
  2. //+kubebuilder:subresource:status
  3. type Memcached struct {
  4. metav1.TypeMeta `json:",inline"`
  5. metav1.ObjectMeta `json:"metadata,omitempty"`
  6. Spec MemcachedSpec `json:"spec,omitempty"`
  7. Status MemcachedStatus `json:"status,omitempty"`
  8. }

修改*_types.go文件后,需要运行以下命令以更新该资源类型的生成代码:

  1. make generate

上述makefile目标将调用controller-gen可执行程序来更新 api/v1alpha1/zz_generated.deepcopy.go 文件,以确保我们的api的go类型定义实现所有类型都必须实现的runtime.Object接口。

生成 CRD 资源文件

使用spec/status字段和CRD maker 标记定义API后,可以使用以下命令生成和更新CRD清单:

  1. make manifests

上述 make 命令将会调用 controller-gen 可执行程序来生成 CRD 资源,生成资源的位置位于: config/crd/bases/cache.example.com_memcacheds.yaml

OpenAPI验证

CRD中定义的OpenAPI验证确保基于一组声明性规则对CRs进行验证。
所有CRD都应进行验证。有关详细信息,请参阅OpenAPI验证文档

实现一个 Controller

对于本例,将生成的控制器文件controllers/memcached_controller.go替换为示例memcached_controller.go实现。接下来的两小节将解释Controller如何监视资源以及如何触发Reconcile循环。

Controller监控资源变更

在 controllers/memcached_controller.go 文件中,SetupWithManager() 函数指定了如何构建Controller以监视CR以及由该Controller拥有和管理的其他资源。

  1. import (
  2. ...
  3. appsv1 "k8s.io/api/apps/v1"
  4. ...
  5. )
  6. func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
  7. return ctrl.NewControllerManagedBy(mgr).
  8. For(&cachev1alpha1.Memcached{}).
  9. Owns(&appsv1.Deployment{}).
  10. Complete(r)
  11. }

NewControllerManagedBy() 提供了一个控制器 Builder 用于生成对应不同配置的 Controller。
For(&cachev1alpha1.Memcached{}) 指定了主要监控的资源类型是 Memcached 类型的资源。对于每个Memcached类型的Add/Update/Delete事件,Reconcile循环将为该Memcached对象发送一个reconcile请求(namespace/name)。
Owns(&appsv1.Deployment{}) 指定了 Deployments 作为监控的第二种资源类型。对了每个 Deployment 类型的Add/Update/Delete事件,事件处理程序将每个事件映射到部署所有者的reconcile请求,在本例中,Deployment 是由 Memcached 对象创建得到的。

Controller配置

初始化Controller时,可以进行许多相关的配置。有关这些配置的更多详细信息,请咨询上游buildercontroller godocs。

  • 通过MaxConcurrentReconciles选项设置控制器的最大并发数。默认值为1。

    1. func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
    2. return ctrl.NewControllerManagedBy(mgr).
    3. For(&cachev1alpha1.Memcached{}).
    4. Owns(&appsv1.Deployment{}).
    5. WithOptions(controller.Options{MaxConcurrentReconciles: 2}).
    6. Complete(r)
    7. }
  • 使用 predicates 来过滤监控事件。

  • 选择EventHandler的类型以更改监视事件转换为协调循环的协调请求的方式。对于比主资源和辅助资源更复杂的Operator而言,可以使用EnqueueRequestsFromMapFunc处理程序将监视事件转换为任意一组reconcile 请求。

Reconcile循环

reconcile 函数主要用于将系统中真实的资源状态转化为自定义资源的期望状态。每次监视的CR或资源上发生变化事件时,它都会运行,并根据这些状态是否匹配返回一些值。
这样,每个 Controller 都有一个对应的 reconcile 对象,它包含 Reconcile() 方法,这个方法实现了 reconcile 循环。reconcile 循环接收的请求参数是 namespace/name 键组成的,该参数是用于从缓存中查找主资源对象Memcached。

  1. import (
  2. ctrl "sigs.k8s.io/controller-runtime"
  3. cachev1alpha1 "github.com/example/memcached-operator/api/v1alpha1"
  4. ...
  5. )
  6. func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  7. // Lookup the Memcached instance for this reconcile request
  8. memcached := &cachev1alpha1.Memcached{}
  9. err := r.Get(ctx, req.NamespacedName, memcached)
  10. ...
  11. }

关于 Reconcilers、Clients 以及资源事件的交互的指南,请参阅客户端API文档
以下是 Reconciler 的几个可能的返回选项:

  • 返回错误信息

    1. return ctrl.Result{}, err
  • 无错误信息的返回

    1. return ctrl.Result{Requeue: true}, nil
  • 停止Reconcile

    1. return ctrl.Result{}, nil
  • 在一段时间后,重新执行 Reconcile 操作

    1. return ctrl.Result{RequeueAfter: nextRun.Sub(r.Now())}, nil

    关于更多的细节,可以参考 Reconcile 文档

指定权限并生成RBAC资源文件

Controller 需要特定的RBAC权限才能与其管理的资源进行交互。
这些是通过RBAC marker指定的,如下所示:

  1. //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete
  2. //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch
  3. //+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update
  4. //+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
  5. //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;
  6. func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  7. ...
  8. }

其中,ClusterRole 配置文件位于 config/rbac/role.yaml,它是通过 controller-gen 二进制命令行工作来生成的:

  1. make manifests

配置 Operator 的镜像仓库

下面,我们就需要来构建 Operator 的镜像并将其推送到对应的镜像仓库中。
在构建镜像之前,请确保 Dockerfile 中引用的 Base 镜像是我们期望的,你可以根据你的需求修改基础镜像。
Makefile根据项目初始化时配置的值或CLI中设置的值组合镜像tag。其中,Makefile中比较重要的参数包括:

  1. IMAGE_TAG_BASE 可以让你配置一个通用的镜像仓库;
  2. IMG 完整镜像名称,对 IMG 可以进行如下修改来配置:
    1. -IMG ?= controller:latest
    2. +IMG ?= $(IMAGE_TAG_BASE):$(VERSION)
    完成后,不必在CLI中设置IMG或任何其他图像配置信息。以下命令将生成并将标记为example.com/memcached operator:v0.0.1的操作员映像推送到Docker Hub:
    1. make docker-build docker-push

运行 Operator

允许 Operator 有如下三种方式:

  1. 作为一个 Go 程序独立在集群外部执行
  2. 作为一个 Deployment 部署在 K8s 集群内部
  3. 通过 Operator Lifecycle Manager(OLM)使用 bundle 格式来进行管理

本地运行

想要在本地运行 operator 其实是很简单的,只需要执行如下命令即可:

  1. make install run

集群内部deployment模式部署

默认情况下,会创建一个新的 namespace 用于创建部署对象,namespace 的名称是 ${project-name}-system,例如 memcached-operator-system。
运行如下命令可以部署 Operator,同时,它还会安装 RBAC 配置文件从 config/rbac 目录中。

  1. make deploy

我们可以通过如下命令来验证 Operator 是否正常运行:

  1. kubectl get deployment -n memcached-operator-system

使用 OLM 来部署 Operator

首先,我们需要安装 OLM

  1. operator-sdk olm install

打包你的 Operator,然后构建打包镜像并推送镜像,它会在 bundle 目录下生成一个 bundle 包:

  1. make bundle bundle-build bundle-push

最后,我们可以通过如下命令来运行 bundle:

  1. operator-sdk run bundle <some-registry>/memcached-operator-bundle:v0.0.1

创建一个 Memached 自定义资源

修改 config/samples/cache_v1alpha1_memcached.yaml 文件如下定义:

  1. apiVersion: cache.example.com/v1alpha1
  2. kind: Memcached
  3. metadata:
  4. name: memcached-sample
  5. spec:
  6. size: 3

创建一个 CR:

  1. kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml

确保memcached Operator 为示例CR创建大小正确的Deployment:

  1. kubectl get deployment

检查pod和CR状态,确认状态已更新为memcached pod名称:

  1. kubectl get pods
  2. kubectl get memcached/memcached-sample -o yaml

修改size

下面,我们来修改一下 size 的配置:

  1. kubectl patch memcached memcached-sample -p '{"spec":{"size": 5}}' --type=merge

下面,我们来验证一下:

  1. kubectl get deployment

大功告成啦!

删除

最后,我们再来把相关的资源清理一下吧:

  1. kubectl delete -f config/samples/cache_v1alpha1_memcached.yaml
  2. make undeploy