5.4. 基于 Go 的 Operator

5.4.1. 基于 Go 的 Operator 开始使用 Operator SDK

如需演示使用 Operator SDK 提供的工具和库来设置和运行基于 Go 的 Operator 的基本知识,Operator 开发人员可以为 Memcached 构建 Go-based Operator 示例,一个分布式键值存储,并将它部署到集群中。

5.4.1.1. 先决条件

5.4.1.2. 创建并部署基于 Go 的 Operator

您可以使用 Operator SDK 为 Memcached 构建和部署简单的 Go-based Operator。

流程

  1. 创建一个项目。

    1. 创建您的项目目录:

      $ mkdir memcached-operator
    2. 切换到项目所在的目录:

      $ cd memcached-operator
    3. 运行 operator-sdk init 命令以初始化项目:

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

      命令默认使用 Go 插件。

  2. 创建 API。

    创建简单的 Memcached API:

    $ operator-sdk create api \
        --resource=true \
        --controller=true \
        --group cache \
        --version v1 \
        --kind Memcached
  3. 构建并推送 Operator 镜像。

    使用默认的 Makefile 目标来构建和推送 Operator。使用镜像的 pull spec 设置 IMG,该 spec 使用您可推送到的 registry:

    $ make docker-build docker-push IMG=<registry>/<user>/<image_name>:<tag>
  4. 运行 Operator。

    1. 安装 CRD:

      $ make install
    2. 将项目部署到集群中。将 IMG 设置为您推送的镜像:

      $ make deploy IMG=<registry>/<user>/<image_name>:<tag>
  5. 创建示例自定义资源(CR)。

    1. 创建一个示例 CR:

      $ oc apply -f config/samples/cache_v1_memcached.yaml \
          -n memcached-operator-system
    2. 查看 CR 协调 Operator:

      $ oc logs deployment.apps/memcached-operator-controller-manager \
          -c manager \
          -n memcached-operator-system
  6. 删除 CR

    运行以下命令来删除 CR:

    $ oc delete -f config/samples/cache_v1_memcached -n memcached-operator-system
  7. 清理。

    运行以下命令清理在此流程中创建的资源:

    $ make undeploy

5.4.1.3. 后续步骤

5.4.2. 基于 Go 的 Operator 的 operator SDK 指南

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

通过以下两个 Operator Framework 核心组件来完成此过程:

Operator SDK
operator-sdk CLI 工具和 controller-runtime 库 API
Operator Lifecycle Manager (OLM)
集群中 Operator 的安装、升级和基于角色的访问控制(RBAC)
注意

本教程的内容比基于 Go 的 Operator 开始使用 Operator SDK 更详细。

5.4.2.1. 先决条件

5.4.2.2. 创建一个项目

使用 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 标志,因为生成的文件需要有效的模块路径。

5.4.2.2.1. PROJECT 文件

operator-sdk init 命令生成的文件中是一个 Kubebuilder PROJECT 文件。从项目 root 运行的后续 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: {}
5.4.2.2.2. 关于 Manager

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

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

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

默认情况下, Manager 会监视 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 指令以提供共享依赖关系和启动组件。
5.4.2.2.3. 关于多组 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。

其他资源

5.4.2.3. 创建 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.gocontrollers/memcached_controller.go 中生成 Memcached 资源 API。

5.4.2.3.1. 定义 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 类型定义实现了 runtime.Object 接口,所有 Kind 类型都必须实现。

5.4.2.3.2. 生成 CRD 清单

在使用 specstatus 字段和自定义资源定义(CRD)验证标记定义后,您可以生成 CRD 清单。

流程

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

    $ make manifests

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

5.4.2.3.2.1. 关于 OpenAPI 验证

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

标记或注解可用于为您的 API 配置验证。这些标记始终具有 +kubebuilder:validation 前缀。

其他资源

5.4.2.4. 实现控制器

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

流程

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

    例 5.1. 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://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"
    
            "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/controller-runtime@v0.7.0/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 spec 指定的大小相同。
    • 使用 memcached Pod 的名称更新 Memcached CR 状态。

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

5.4.2.4.1. 控制器监视的资源

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 类型的每个 Add、Update 或 Delete 事件,协调循环都会为该 Memcached 对象发送一个协调 Request 参数,其中包括命名空间和名称键。

owns(&appsv1.Deployment{})Deployment 类型指定为要监视的辅助资源。对于 Deployment 类型的每个 Add、Update 或 Delete 事件,事件处理程序会将每个事件映射到部署所有者的协调请求。在本例中,所有者是创建部署的 Memcached 对象。

5.4.2.4.2. 控制器配置

您可以不同的配置来初始化控制器。例如:

  • 使用 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)
    }
  • 使用 predicates 过滤监视事件。
  • 选择 EventHandler 类型来更改监视事件转换方式以协调协调循环的请求。对于比主和从属资源更复杂的 Operator 关系,您可以使用 EnqueueRequestsFromMapFunc 处理程序将监控事件转换为一组任意协调请求。

有关这些配置和其他配置的详情,请参阅上游 BuilderController GoDocs。

5.4.2.4.3. 协调循环

每个控制器都有一个协调器对象,它带有实现了协调循环的 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
注意

您可以返回带有 RequeueAfter 设置的 Result 来定期协调一个 CR。

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

5.4.2.4.4. 权限和 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) {
  ...
}

config/rbac/role.yaml 中的 ClusterRole 对象清单通过在每次运行 manifests 命令时使用 controller-gen 实用程序的以前的标记生成。

5.4.2.5. 启用代理支持

Operator 作者可开发支持网络代理的 Operator。集群管理员配置对 Operator Lifecycle Manager (OLM) 处理的环境变量的代理支持。要支持代理集群,Operator 必须检查以下标准代理变量的环境,并将值传递给 Operands:

  • 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 helper 功能添加到协调循环中,并将结果附加到 Operand 环境:

      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"

5.4.2.6. 运行 Operator

您可以使用 Operator SDK CLI 构建和运行 Operator:

  • 作为 Go 程序在集群外本地运行。
  • 作为集群的部署运行。
  • 捆绑 Operator,并使用 Operator Lifecycle Manager(OLM)在集群中部署。
注意

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

5.4.2.6.1. 在集群外本地运行

您可以作为集群外的 Go 程序运行您的 Operator 项目。这可以加快部署和测试的速度,对于开发非常有用。

流程

  • 运行以下命令,以在 ~/.kube/config 文件中配置的集群中安装自定义资源定义(CRD),并在本地运行 Operator:

    $ make install run

    输出示例

    ...
    2021-01-10T21:09:29.016-0700	INFO	controller-runtime.metrics	metrics server is starting to listen	{"addr": ":8080"}
    2021-01-10T21:09:29.017-0700	INFO	setup	starting manager
    2021-01-10T21:09:29.017-0700	INFO	controller-runtime.manager	starting metrics server	{"path": "/metrics"}
    2021-01-10T21:09:29.018-0700	INFO	controller-runtime.manager.controller.memcached	Starting EventSource	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "source": "kind source: /, Kind="}
    2021-01-10T21:09:29.218-0700	INFO	controller-runtime.manager.controller.memcached	Starting Controller	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached"}
    2021-01-10T21:09:29.218-0700	INFO	controller-runtime.manager.controller.memcached	Starting workers	{"reconciler group": "cache.example.com", "reconciler kind": "Memcached", "worker count": 1}

5.4.2.6.2. 作为集群的部署运行

您可以作为一个部署在集群中运行 Operator 项目。

先决条件

  • 通过更新项目以使用支持的镜像,准备基于 Go 的 Operator 在 OpenShift Container Platform 上运行

流程

  1. 运行以下 make 命令来构建和推送 Operator 镜像。在以下步骤中修改 IMG 参数来引用您可访问的库。您可以获取在存储库站点(如 Quay.io)存储容器的帐户。

    1. 构建镜像:

      $ make docker-build IMG=<registry>/<user>/<image_name>:<tag>
      注意

      由 SDK 为 Operator 生成的 Dockerfile 需要为 go build 明确引用 GOARCH=amd64。这可以在非 AMD64 构架中使用 GOARCH=$TARGETARCH。Docker 自动将环境变量设置为 -platform 指定的值。对于 Buildah,需要使用 -build-arg 来实现这一目的。如需更多信息,请参阅多个架构

    2. 将镜像推送到存储库:

      $ make docker-push IMG=<registry>/<user>/<image_name>:<tag>
      注意

      镜像的名称和标签,如 IMG=<registry> /<user> /<image_name>:<tag>,在两个命令中都可在您的 Makefile 中设置。修改 IMG ?= controller:latest 值来设置您的默认镜像名称。

  2. 运行以下命令来部署 Operator:

    $ make deploy IMG=<registry>/<user>/<image_name>:<tag>

    默认情况下,这个命令会创建一个带有 Operator 项目名称的命名空间,格式为 <project_name>-system,用于部署。此命令还从 config/rbac 安装 RBAC 清单。

  3. 验证 Operator 是否正在运行:

    $ oc get deployment -n <project_name>-system

    输出示例

    NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
    <project_name>-controller-manager       1/1     1            1           8m

5.4.2.6.3. 捆绑 Operator 并使用 Operator Lifecycle Manager 进行部署
5.4.2.6.3.1. 捆绑 Operator

Operator 捆绑包格式是 Operator SDK 和 Operator Lifecycle Manager(OLM)的默认打包方法。您可以使用 Operator SDK 来构建和推送 Operator 项目作为捆绑包镜像,使 Operator 可供 OLM 使用。

先决条件

  • 在开发工作站上安装 operator SDK CLI
  • 已安装 OpenShift CLI(oc)v4.10+
  • 使用 Operator SDK 初始化 operator 项目
  • 如果 Operator 是基于 Go 的,则必须更新您的项目以使用支持的镜像在 OpenShift Container Platform 上运行

流程

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

    1. 构建镜像:

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

      由 SDK 为 Operator 生成的 Dockerfile 需要为 go build 明确引用 GOARCH=amd64。这可以在非 AMD64 构架中使用 GOARCH=$TARGETARCH。Docker 自动将环境变量设置为 -platform 指定的值。对于 Buildah,需要使用 -build-arg 来实现这一目的。如需更多信息,请参阅多个架构

    2. 将镜像推送到存储库:

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

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

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

    • 包含 ClusterServiceVersion 对象的捆绑包清单目录,名为 bundle/manifests
    • 名为 bundle/metadata 的捆绑包元数据目录
    • config/crd 目录中的所有自定义资源定义(CRD)
    • 一个 Dockerfile bundle.Dockerfile

    然后,使用 operator-sdk bundle validate 自动验证这些文件,以确保磁盘上的捆绑包的格式是正确的。

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

    1. 构建捆绑包镜像。使用您要推送镜像的 registry、用户命名空间和镜像标签的详情,设置 BUNDLE_IMG

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

      $ docker push <registry>/<user>/<bundle_image_name>:<tag>
5.4.2.6.3.2. 使用 Operator Lifecycle Manager 部署 Operator

Operator Lifecycle Manager(OLM)可帮助您在 Kubernetes 集群中安装、更新和管理 Operator 及其相关服务的生命周期。OLM 在 OpenShift Container Platform 上默认安装,并作为 Kubernetes 扩展运行,以便您可以在没有任何额外工具的情况下将 Web 控制台和 OpenShift CLI(oc)用于所有 Operator 生命周期管理功能。

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

先决条件

  • 在开发工作站上安装 operator SDK CLI
  • 构建并推送到 registry 的 Operator 捆绑包镜像
  • OLM安装在一个基于 Kubernetes 的集群上(如果使用 apiextensions.k8s.io/v1 CRD,则为 v1.16.0 或更新版本,如 OpenShift Container Platform 4.10)
  • 使用具有 cluster-admin 权限的账户使用 oc 登录到集群
  • 如果 Operator 是基于 Go 的,则必须更新您的项目以使用支持的镜像在 OpenShift Container Platform 上运行

流程

  1. 输入以下命令在集群中运行 Operator:

    $ operator-sdk run bundle \
        [-n <namespace>] \1
        <registry>/<user>/<bundle_image_name>:<tag>
    1
    默认情况下,命令会在 ~/.kube/config 文件中当前活跃的项目中安装 Operator。您可以添加 -n 标志来为安装设置不同的命名空间范围。

    这个命令执行以下操作:

    • 创建引用捆绑包镜像的索引镜像。索引镜像不透明且具有临时性,但准确反映了如何将捆绑包添加到生产中的目录中。
    • 创建指向新索引镜像的目录源,以便 OperatorHub 能够发现 Operator。
    • 通过创建一个 OperatorGroupSubscriptionInstallPlan 和所有其他必要的对象(包括 RBAC),将 Operator 部署到集群中。

5.4.2.7. 创建自定义资源

安装 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>

5.4.2.8. 其他资源

5.4.3. 基于 Go 的 Operator 的项目布局

operator-sdk CLI 可为每个 Operator 项目生成或 scaffold 多个 软件包和文件。

5.4.3.1. 基于 Go 的项目布局

使用 operator-sdk init 命令生成的基于 Go 的 Operator 项目(默认类型)包含以下文件和目录:

文件或目录用途

main.go

Operator 的主要程序。这会实例化一个新管理器,它会在 apis/ 目录中注册所有自定义资源定义(CRD),并启动 controllers/ 目录中的所有控制器。

apis/

定义 CRD API 的目录树。您必须编辑 apis/<version> /<kind>_types.go 文件,为每个资源类型定义 API,并在控制器中导入这些软件包以监视这些资源类型。

controllers/

控制器的实现。编辑 controller/<kind>_controller.go 文件,以定义控制器处理指定种类资源类型的协调逻辑。

config/

用于在集群中部署控制器的 Kubernetes 清单,包括 CRD、RBAC 和证书。

Makefile

用于构建和部署控制器的目标。

Docker

容器引擎用来构建 Operator 的说明。

manifests/

Kubernetes 清单用于注册 CRD、设置 RBAC 和将 Operator 部署为部署。