×

Operator开发者可以利用Operator SDK中的Go编程语言支持来构建一个基于Go的Memcached(一个分布式键值存储)Operator示例,并管理其生命周期。

Red Hat支持的Operator SDK CLI工具版本(包括相关的脚手架和Operator项目的测试工具)已弃用,并计划在未来版本的Red Hat OpenShift Service on AWS中删除。Red Hat将在当前版本生命周期内为此功能提供错误修复和支持,但此功能将不再接收增强功能,并将从未来的Red Hat OpenShift Service on AWS版本中移除。

不建议使用Red Hat支持的Operator SDK版本创建新的Operator项目。拥有现有Operator项目的Operator作者可以使用随Red Hat OpenShift Service on AWS发布的Operator SDK CLI工具版本来维护其项目并创建面向更新版本的Red Hat OpenShift Service on AWS的Operator版本。

以下与Operator项目相关的基础镜像被弃用。这些基础镜像的运行时功能和配置API仍然支持错误修复和解决CVE。

  • 基于Ansible的Operator项目的基镜像

  • 基于Helm的Operator项目的基镜像

有关不受支持的社区维护版本的Operator SDK的信息,请参阅Operator SDK (Operator Framework)

此过程是使用Operator Framework的两个核心组件完成的

Operator SDK

operator-sdk CLI工具和controller-runtime库API

Operator Lifecycle Manager (OLM)

在集群上安装、升级和基于角色的访问控制 (RBAC) Operator

本教程比OpenShift Container Platform文档中的面向基于Go的Operator的Operator SDK入门更详细。

先决条件

  • 已安装Operator SDK CLI

  • 已安装OpenShift CLI (oc)

  • Go 1.21+

  • 使用具有dedicated-admin权限的帐户通过oc登录到Red Hat OpenShift Service on AWS集群

  • 为了允许集群拉取镜像,您推送镜像的仓库必须设置为公开,或者您必须配置镜像拉取密钥

创建项目

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

步骤
  1. 为项目创建一个目录

    $ mkdir -p $HOME/projects/memcached-operator
  2. 切换到该目录

    $ cd $HOME/projects/memcached-operator
  3. 激活Go模块的支持

    $ export GO111MODULE=on
  4. 运行operator-sdk init命令来初始化项目

    $ operator-sdk init \
        --domain=example.com \
        --repo=github.com/example-inc/memcached-operator

    operator-sdk init命令默认使用Go插件。

    operator-sdk init命令生成一个go.mod文件,用于与Go模块一起使用。在$GOPATH/src/之外创建项目时,需要使用--repo标志,因为生成的文 件需要一个有效的模块路径。

PROJECT文件

operator-sdk init命令生成的 文件中包含一个Kubebuilder PROJECT文件。从项目根目录运行的后续operator-sdk命令以及help输出都会读取此文件,并知道项目类型是Go。例如

domain: example.com
layout:
- go.kubebuilder.io/v3
projectName: memcached-operator
repo: github.com/example-inc/memcached-operator
version: "3"
plugins:
  manifests.sdk.operatorframework.io/v2: {}
  scorecard.sdk.operatorframework.io/v2: {}
  sdk.x-openshift.io/v1: {}

关于管理器

Operator 的主程序是main.go文件,它初始化并运行管理器。管理器会自动为所有自定义资源 (CR) API 定义注册 Scheme,并设置和运行控制器和 Webhook。

管理器可以限制所有控制器监视资源的命名空间

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

默认情况下,管理器监视Operator运行的命名空间。要监视所有命名空间,您可以将namespace选项留空

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

您还可以使用MultiNamespacedCacheBuilder函数来监视一组特定的命名空间

var namespaces []string (1)
mgr, err := ctrl.NewManager(cfg, manager.Options{ (2)
   NewCache: cache.MultiNamespacedCacheBuilder(namespaces),
})
1 命名空间列表。
2 创建一个Cmd结构体来提供共享依赖项并启动组件。

关于多组API

在创建API和控制器之前,请考虑您的Operator是否需要多个API组。本教程涵盖了单个组API的默认情况,但要更改项目的布局以支持多组API,您可以运行以下命令

$ operator-sdk edit --multigroup=true

此命令会更新PROJECT文件,它应该如下例所示

domain: example.com
layout: go.kubebuilder.io/v3
multigroup: true
...

对于多组项目,API Go类型文件创建在apis/<group>/<version>/目录中,控制器创建在controllers/<group>/目录中。然后相应地更新Dockerfile。

附加资源

创建API和控制器

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

步骤
  1. 运行以下命令以创建具有组cache、版本v1和类型Memcached的API

    $ operator-sdk create api \
        --group=cache \
        --version=v1 \
        --kind=Memcached
  2. 出现提示时,输入y以创建资源和控制器。

    Create Resource [y/n]
    y
    Create Controller [y/n]
    y
    示例输出
    Writing scaffold for you to edit...
    api/v1/memcached_types.go
    controllers/memcached_controller.go
    ...

此过程会在api/v1/memcached_types.go生成Memcached资源API,并在controllers/memcached_controller.go生成控制器。

定义API

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

步骤
  1. 修改api/v1/memcached_types.go中的Go类型定义,使其具有以下specstatus

    // 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"`
    }
    
    // MemcachedStatus defines the observed state of Memcached
    type MemcachedStatus struct {
    	// Nodes are the names of the memcached pods
    	Nodes []string `json:"nodes"`
    }
  2. 更新资源类型的生成代码

    $ make generate

    修改*_types.go文件后,必须运行make generate命令来更新该资源类型的生成代码。

    上述Makefile目标调用controller-gen实用程序来更新api/v1/zz_generated.deepcopy.go文件。这确保您的API Go类型定义实现了所有Kind类型必须实现的runtime.Object接口。

生成CRD清单

定义了具有specstatus字段以及自定义资源定义 (CRD) 验证标记的API后,您可以生成CRD清单。

步骤
  • 运行以下命令以生成和更新CRD清单

    $ make manifests

    此 Makefile 目标调用controller-gen实用程序,以在config/crd/bases/cache.example.com_memcacheds.yaml文件中生成CRD清单。

关于 OpenAPI 验证

在生成清单时,OpenAPIv3 模式会添加到spec.validation块中的 CRD 清单中。此验证块允许 Kubernetes 在创建或更新 Memcached 自定义资源 (CR) 时验证其属性。

可以使用标记(或注释)来配置 API 的验证。这些标记始终具有+kubebuilder:validation前缀。

附加资源

实现控制器

创建新的 API 和控制器后,您可以实现控制器逻辑。

步骤
  • 对于此示例,请将生成的控制器文件controllers/memcached_controller.go替换为以下示例实现

    示例memcached_controller.go
    /*
    Copyright 2020.
    
    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://apache.ac.cn/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"
    
            "context"
    
            "github.com/go-logr/logr"
            "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"
    
            cachev1 "github.com/example-inc/memcached-operator/api/v1"
    )
    
    // MemcachedReconciler reconciles a Memcached object
    type MemcachedReconciler struct {
            client.Client
            Log    logr.Logger
            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
    // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
    // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;
    
    // 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/[email protected]/pkg/reconcile
    func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
            //log := r.Log.WithValues("memcached", req.NamespacedName)
            log := ctrllog.FromContext(ctx)
            // Fetch the Memcached instance
            memcached := &cachev1.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
                    }
                    // Spec updated - return and requeue
                    return ctrl.Result{Requeue: true}, 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 *cachev1.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(&cachev1.Memcached{}).
                    Owns(&appsv1.Deployment{}).
                    Complete(r)
    }

    示例控制器为每个Memcached自定义资源 (CR) 运行以下协调逻辑

    • 如果不存在,则创建 Memcached 部署。

    • 确保部署大小与Memcached CR规范中指定的大小相同。

    • 使用memcached Pod 的名称更新Memcached CR 状态。

接下来的小节解释了示例实现中的控制器如何监视资源以及如何触发协调循环。您可以跳过这些小节,直接转到运行 Operator

控制器监视的资源

controllers/memcached_controller.go中的SetupWithManager()函数指定了如何构建控制器以监视 CR 和该控制器拥有和管理的其他资源。

import (
	...
	appsv1 "k8s.io/api/apps/v1"
	...
)

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

NewControllerManagedBy()提供了一个控制器构建器,允许各种控制器配置。

For(&cachev1.Memcached{})指定Memcached类型为要监视的主要资源。对于Memcached类型的每个添加、更新或删除事件,协调循环都会收到一个协调Request参数,该参数包含该Memcached对象的命名空间和名称键。

Owns(&appsv1.Deployment{})指定Deployment类型为要监视的次要资源。对于每个Deployment类型的添加、更新或删除事件,事件处理程序会将每个事件映射到对其所有者的协调请求。在本例中,所有者是为其创建部署的Memcached对象。

控制器配置

您可以使用许多其他有用的配置来初始化控制器。例如

  • 使用MaxConcurrentReconciles选项设置控制器的最大并发协调次数,默认为1

    func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error {
        return ctrl.NewControllerManagedBy(mgr).
            For(&cachev1.Memcached{}).
            Owns(&appsv1.Deployment{}).
            WithOptions(controller.Options{
                MaxConcurrentReconciles: 2,
            }).
            Complete(r)
    }
  • 使用谓词过滤监视事件。

  • 选择EventHandler的类型来更改监视事件如何转换为协调循环的协调请求。对于比主要资源和次要资源更复杂的 Operator 关系,您可以使用EnqueueRequestsFromMapFunc处理程序将监视事件转换为任意一组协调请求。

有关这些配置和其他配置的更多详细信息,请参阅上游BuilderController GoDocs。

协调循环

每个控制器都有一个具有Reconcile()方法的协调器对象,该方法实现协调循环。协调循环传递Request参数,这是一个命名空间和名称键,用于从缓存中查找主要资源对象Memcached

import (
	ctrl "sigs.k8s.io/controller-runtime"

	cachev1 "github.com/example-inc/memcached-operator/api/v1"
	...
)

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  // Lookup the Memcached instance for this reconcile request
  memcached := &cachev1.Memcached{}
  err := r.Get(ctx, req.NamespacedName, memcached)
  ...
}

根据返回值、结果和错误,请求可能会重新排队,并且可能会再次触发协调循环

// Reconcile successful - don't requeue
return ctrl.Result{}, nil
// Reconcile failed due to error - requeue
return ctrl.Result{}, err
// Requeue for any reason other than an error
return ctrl.Result{Requeue: true}, nil

您还可以设置Result.RequeueAfter以在宽限期后重新排队请求

import "time"

// Reconcile for any reason other than an error after 5 seconds
return ctrl.Result{RequeueAfter: time.Second*5}, nil

您可以返回设置了RequeueAfterResult来定期协调 CR。

有关协调器、客户端以及与资源事件交互的更多信息,请参阅Controller Runtime Client API文档。

权限和 RBAC 清单

控制器需要某些 RBAC 权限才能与它管理的资源进行交互。这些权限使用 RBAC 标记指定,例如:

// +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;

func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
  ...
}

每次运行make manifests命令时,config/rbac/role.yaml处的ClusterRole对象清单都会使用controller-gen实用程序从之前的标记生成。

启用代理支持

Operator 作者可以开发支持网络代理的 Operator。具有dedicated-admin角色的管理员可以为 Operator Lifecycle Manager (OLM) 处理的环境变量配置代理支持。为了支持代理集群,您的 Operator 必须检查环境中是否存在以下标准代理变量并将值传递给操作数

  • HTTP_PROXY

  • HTTPS_PROXY

  • NO_PROXY

本教程使用HTTP_PROXY作为示例环境变量。

先决条件
  • 启用了集群范围出站代理的集群。

步骤
  1. 编辑controllers/memcached_controller.go文件以包含以下内容

    1. operator-lib库导入proxy

      import (
        ...
         "github.com/operator-framework/operator-lib/proxy"
      )
    2. proxy.ReadProxyVarsFromEnv辅助函数添加到协调循环中,并将结果附加到操作数环境

      for i, container := range dep.Spec.Template.Spec.Containers {
      		dep.Spec.Template.Spec.Containers[i].Env = append(container.Env, proxy.ReadProxyVarsFromEnv()...)
      }
      ...
  2. 通过将以下内容添加到config/manager/manager.yaml文件来设置 Operator 部署上的环境变量

    containers:
     - args:
       - --leader-elect
       - --leader-election-id=ansible-proxy-demo
       image: controller:latest
       name: manager
       env:
         - name: "HTTP_PROXY"
           value: "http_proxy_test"

运行 Operator

要构建和运行您的 Operator,请使用 Operator SDK CLI 来捆绑您的 Operator,然后使用 Operator Lifecycle Manager (OLM) 在集群上部署。

如果您希望在 OpenShift Container Platform 集群而不是 AWS 上的 Red Hat OpenShift Service 集群上部署您的 Operator,则可以使用另外两种部署选项

  • 在集群外部作为 Go 程序本地运行。

  • 在集群上作为部署运行。

在将基于 Go 的 Operator 作为使用 OLM 的捆绑包运行之前,请确保您的项目已更新为使用受支持的镜像。

附加资源

捆绑 Operator 并使用 Operator Lifecycle Manager 部署

捆绑 Operator

Operator 捆绑包格式是 Operator SDK 和 Operator Lifecycle Manager (OLM) 的默认打包方法。您可以使用 Operator SDK 将 Operator 项目构建并推送为捆绑包镜像,从而使您的 Operator 准备好用于 OLM。

先决条件
  • 在开发工作站上安装了 Operator SDK CLI

  • 安装了 OpenShift CLI (oc) v+

  • 使用 Operator SDK 初始化了 Operator 项目

  • 如果您的 Operator 是基于 Go 的,则必须更新您的项目以使用受支持的镜像才能在 AWS 上的 Red Hat OpenShift Service 上运行

步骤
  1. 在您的 Operator 项目目录中运行以下make命令以构建和推送您的 Operator 镜像。修改以下步骤中的IMG参数以引用您有权访问的存储库。您可以在 Quay.io 等存储库站点获得用于存储容器的帐户。

    1. 构建镜像

      $ make docker-build IMG=<registry>/<user>/<operator_image_name>:<tag>

      SDK 为 Operator 生成的 Dockerfile 显式引用了 `GOARCH=amd64` 用于 `go build`。这可以修改为 `GOARCH=$TARGETARCH` 以支持非 AMD64 架构。Docker 会自动将环境变量设置为 `–platform` 指定的值。对于 Buildah,则需要使用 `–build-arg`。更多信息,请参见 多架构支持

    2. 将镜像推送到仓库

      $ make docker-push IMG=<registry>/<user>/<operator_image_name>:<tag>
  2. 运行 `make bundle` 命令创建 Operator 捆绑清单,该命令会调用多个命令,包括 Operator SDK 的 `generate bundle` 和 `bundle validate` 子命令。

    $ make bundle IMG=<registry>/<user>/<operator_image_name>:<tag>

    Operator 的捆绑清单描述了如何显示、创建和管理应用程序。`make bundle` 命令会在 Operator 项目中创建以下文件和目录:

    • 一个名为 `bundle/manifests` 的捆绑清单目录,其中包含一个 `ClusterServiceVersion` 对象。

    • 一个名为 `bundle/metadata` 的捆绑元数据目录。

    • 位于 `config/crd` 目录下的所有自定义资源定义 (CRD)。

    • 一个 Dockerfile `bundle.Dockerfile`。

    然后,这些文件会使用 `operator-sdk bundle validate` 自动进行验证,以确保磁盘上的捆绑表示正确。

  3. 运行以下命令构建并推送捆绑镜像。OLM 使用索引镜像来使用 Operator 捆绑包,该索引镜像引用一个或多个捆绑镜像。

    1. 构建捆绑镜像。使用注册表、用户命名空间和镜像标签的详细信息设置 `BUNDLE_IMG`,这些信息指定您打算将镜像推送到哪里。

      $ make bundle-build BUNDLE_IMG=<registry>/<user>/<bundle_image_name>:<tag>
    2. 推送捆绑镜像

      $ docker push <registry>/<user>/<bundle_image_name>:<tag>

使用 Operator Lifecycle Manager 部署 Operator

Operator Lifecycle Manager (OLM) 可帮助您在 Kubernetes 集群上安装、更新和管理 Operator 及其关联服务的生命周期。OLM 在 Red Hat OpenShift Service on AWS 上默认安装,并作为 Kubernetes 扩展运行,因此您可以使用 Web 控制台和 OpenShift CLI (`oc`) 完成所有 Operator 生命周期管理功能,无需任何其他工具。

Operator 捆绑包格式是 Operator SDK 和 OLM 的默认打包方法。您可以使用 Operator SDK 快速在 OLM 上运行捆绑镜像,以确保其正常运行。

先决条件
  • 在开发工作站上安装了 Operator SDK CLI

  • 已构建并推送到注册表的 Operator 捆绑镜像

  • 安装在基于 Kubernetes 的集群上的 OLM(如果您使用 `apiextensions.k8s.io/v1` CRD,则版本为 1.16.0 或更高版本,例如 Red Hat OpenShift Service on AWS)

  • 使用具有 `dedicated-admin` 权限的帐户使用 `oc` 登录到集群

  • 如果您的 Operator 是基于 Go 的,则必须更新您的项目以使用受支持的镜像才能在 AWS 上的 Red Hat OpenShift Service 上运行

步骤
  • 输入以下命令在集群上运行 Operator

    $ operator-sdk run bundle \(1)
        -n <namespace> \(2)
        <registry>/<user>/<bundle_image_name>:<tag> (3)
    1 `run bundle` 命令会创建一个有效的基于文件的目录,并使用 OLM 在您的集群上安装 Operator 捆绑包。
    2 可选:默认情况下,该命令会将 Operator 安装到 `~/.kube/config` 文件中当前活动的项目中。您可以添加 `-n` 标志来为安装设置不同的命名空间范围。
    3 如果您没有指定镜像,则该命令使用 `quay.io/operator-framework/opm:latest` 作为默认索引镜像。如果您指定了镜像,则该命令使用捆绑镜像本身作为索引镜像。

    从 Red Hat OpenShift Service on AWS 4.11 开始,`run bundle` 命令默认支持 Operator 目录的基于文件的目录格式。Operator 目录的已弃用的 SQLite 数据库格式仍然受支持;但是,它将在未来的版本中删除。建议 Operator 作者将其工作流迁移到基于文件的目录格式。

    此命令执行以下操作:

    • 创建一个引用您的捆绑镜像的索引镜像。索引镜像是不透明且短暂的,但它准确地反映了如何在生产环境中将捆绑包添加到目录中。

    • 创建一个指向新的索引镜像的目录源,这使得 OperatorHub 能够发现您的 Operator。

    • 通过创建 `OperatorGroup`、`Subscription`、`InstallPlan` 和所有其他必需的资源(包括 RBAC)将您的 Operator 部署到您的集群。

创建自定义资源

安装 Operator 后,您可以通过创建现在由 Operator 在集群上提供的自定义资源 (CR) 来对其进行测试。

先决条件
  • 在集群上安装的示例 Memcached Operator,它提供 `Memcached` CR。

步骤
  1. 切换到已安装 Operator 的命名空间。例如,如果您使用 `make deploy` 命令部署了 Operator

    $ oc project memcached-operator-system
  2. 编辑位于 `config/samples/cache_v1_memcached.yaml` 的示例 `Memcached` CR 清单,使其包含以下规范:

    apiVersion: cache.example.com/v1
    kind: Memcached
    metadata:
      name: memcached-sample
    ...
    spec:
    ...
      size: 3
  3. 创建 CR

    $ oc apply -f config/samples/cache_v1_memcached.yaml
  4. 确保 `Memcached` Operator 使用正确的规模为示例 CR 创建部署。

    $ oc get deployments
    示例输出
    NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
    memcached-operator-controller-manager   1/1     1            1           8m
    memcached-sample                        3/3     3            3           1m
  5. 检查 Pod 和 CR 状态以确认状态已更新为 Memcached Pod 名称。

    1. 检查 Pod

      $ oc get pods
      示例输出
      NAME                                  READY     STATUS    RESTARTS   AGE
      memcached-sample-6fd7c98d8-7dqdr      1/1       Running   0          1m
      memcached-sample-6fd7c98d8-g5k7v      1/1       Running   0          1m
      memcached-sample-6fd7c98d8-m7vn7      1/1       Running   0          1m
    2. 检查 CR 状态

      $ oc get memcached/memcached-sample -o yaml
      示例输出
      apiVersion: cache.example.com/v1
      kind: Memcached
      metadata:
      ...
        name: memcached-sample
      ...
      spec:
        size: 3
      status:
        nodes:
        - memcached-sample-6fd7c98d8-7dqdr
        - memcached-sample-6fd7c98d8-g5k7v
        - memcached-sample-6fd7c98d8-m7vn7
  6. 更新部署规模。

    1. 更新 `config/samples/cache_v1_memcached.yaml` 文件,将 `Memcached` CR 中的 `spec.size` 字段从 `3` 更改为 `5`。

      $ oc patch memcached memcached-sample \
          -p '{"spec":{"size": 5}}' \
          --type=merge
    2. 确认 Operator 更改了部署规模。

      $ oc get deployments
      示例输出
      NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
      memcached-operator-controller-manager   1/1     1            1           10m
      memcached-sample                        5/5     5            5           3m
  7. 运行以下命令删除 CR。

    $ oc delete -f config/samples/cache_v1_memcached.yaml
  8. 清理本教程中创建的资源。

    • 如果您使用 `make deploy` 命令测试了 Operator,请运行以下命令:

      $ make undeploy
    • 如果您使用 `operator-sdk run bundle` 命令测试了 Operator,请运行以下命令:

      $ operator-sdk cleanup <project_name>

其他资源