这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

[第10章]用Controller-Runtime库编写Operator

用Controller-Runtime库编写Operator

1 - controller-runtime简介

controller-runtime简介

正如你在第1章中所看到的,Controller Manager 是 Kubernetes 架构的一个重要部分。它嵌入了几个 Controller,每个控制器的作用是观察特定高层资源(Deployments 等)的实例,并使用低层资源(Pod等)来实现这些高层实例。

举例,Kubernetes 用户在部署无状态的应用程序时可以创建 Deployment。这个 Deployment 定义了 Pod 模板,用来在集群中创建 Pod 以及一些规范。以下是最重要的规格:

  • 副本的数量:控制器必须为 Deployment 实例部署相同的 Pod。

  • 部署策略:当 Pod 模板更新时,Pod 被替换的方式。

  • 默认策略:滚动更新(Rolling Update)可用于在不中断服务的情况下将应用程序更新到新版本,它接受各种参数。还存在一个更简单的策略:Recreate,它将首先停止Pod,然后再开始替换它。

Deployment 控制器将创建 ReplicaSet 资源的实例,每个新版本的 Pod 模板都有一个,并更新这些 ReplicaSets 的副本数量,以满足副本和策略规范。你会发现退役版本的 ReplicaSet 的副本数为零,而实际应用版本的 ReplicaSet 的副本数为正数。在滚动更新期间,两个 ReplicaSets 将拥有正数的副本(新的副本数量增加,以前的副本数量减少),以便能够在两个版本之间过渡而不中断服务。

在它这边,ReplicaSet 控制器将负责维护由 Deployment 控制器创建的每个 ReplicaSet 实例所要求的 Pod 副本数量。

第8章已经表明,可以定义新的 Kubernetes 资源来扩展 Kubernetes 的 API。即使有原生控制器管理器运行控制器来处理原生 Kubernetes 资源,你也需要编写控制器来处理自定义资源。

一般来说,这样的控制器被称为 operator,处理第三方资源,并为处理原生 Kubernetes 资源的控制器保留 Controller 这个名字。

Client-go 库提供了使用 Go 语言编写控制器和 operator 的工具,controller-runtime 库利用这些工具提供围绕控制器模式的抽象,帮助你编写 operator 。这个库可以用下面的命令来安装:

go get sigs.k8s.io/controller-runtime@v0.13.0

你可以从 github.com/kubernetes-sigs/controller-runtime/releases 的源码库中获得可用的修订版。

2 - Manager

Manager

controller-runtime 库提供的第一个重要抽象是 Manager / 管理器,它为在管理器内运行的所有控制器提供共享资源,包括:

  • 读取和写入 Kubernetes 资源的 Kubernetes 客户端

  • 用于从本地缓存中读取 Kubernetes 资源的缓存

  • 用于注册所有 Kubernetes 本地和自定义资源的 scheme

要创建管理器,你需要使用提供的 New 函数,如下所示:

import (
      "flag"
      "sigs.k8s.io/controller-runtime/pkg/client/config"
      "sigs.k8s.io/controller-runtime/pkg/manager"
)
flag.Parse()                        
mgr, err := manager.New(
     config.GetConfigOrDie(),
      manager.Options{},
)
  • ❶解析命令行标志,如 GetConfigOrDie 来处理 --kubeconfig 标志;见下文。

    第一个参数是 rest.Config 对象,见第 6 章 “连接到集群” 部分。请注意,在这个例子中,选择了 controller-runtime 库提供的 GetConfigOrDie() 实用函数,而不是使用 client-go 库的函数。

GetConfigOrDie() 函数将尝试获得一个配置来连接到集群:

  • 通过获取 --kubeconfig 标志的值,如果定义了的话,并在这个路径上读取 kubeconfig 文件。为此,首先你需要执行 flag.Parse()

  • 通过获取 KUBECONFIG 环境变量的值(如果定义了的话),并读取此路径下的 kubeconfig 文件

  • 通过查看 in-cluster 配置(见第6章的 “集群内配置 “部分),如果定义了的话

  • 通过读取 $HOME/.kube/config 文件

如果前面的情况都不可行,该函数将使程序退出,代码为1。第二个参数是一个用于选项的结构体。

一个重要的选项是 “Scheme"。默认情况下,如果你没有为这个选项指定任何值,将使用 Client-go 库提供的 Scheme。如果控制器只需要访问本地 Kubernetes 资源,这就足够了。然而,如果你想让控制器访问自定义资源,你将需要提供一个能够解决自定义资源的 Scheme

例如,如果你想让控制器访问第九章中定义的自定义资源,你将需要在初始化时运行以下代码:

import (
      "k8s.io/apimachinery/pkg/runtime"
      clientgoscheme "k8s.io/client-go/kubernetes/scheme"
      mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1"
)
scheme := runtime.NewScheme()                  
clientgoscheme.AddToScheme(scheme)             
mygroupv1alpha1.AddToScheme(scheme)            
mgr, err := manager.New(
      config.GetConfigOrDie(),
      manager.Options{
            Scheme: scheme,                    
      },
)

❶ 创建一个新的空 scheme

❷ 使用 Client-go 库添加本地 Kubernetes 资源

❷ 将 mygroup/v1alpha1 的资源添加到 scheme 中,其中包含我们的自定义资源

❹ 在这个管理器中使用这个 scheme

3 - Controller

Controller

第二个重要的抽象是控制器 / Controller。控制器负责实现特定 Kubernetes 资源的实例所给出的规范(Spec)。(在 operator 的情况下,自定义资源是由 operator 处理的)。

为此,控制器观察特定的资源(至少是相关的自定义资源,在本节中称为 “主要资源”),并接收这些资源的 watch 事件(即,创建、更新、删除)。当事件发生在资源上时,控制器用包含受事件影响的 “主要资源” 实例的名称和命名空间的请求填充一个队列。

请注意,入队的对象只是被 operator 监视的主要资源的实例。如果事件是由另一个资源的实例接收的,则通过跟踪所有者参考(ownerReference)找到主资源。例如,Deployment 控制器观察 Deployment 资源和 ReplicaSet 资源。所有 ReplicaSet 实例都包含一个指向 Deployment 实例的 ownerReference。

  • 当 Deployment 被创建时,控制器会收到一个 Create 事件,刚刚创建的 Deployment 实例被入队

  • 当 ReplicaSet 被修改时(例如,被一些用户修改),这个 ReplicaSet 会收到一个 Update 事件,控制器会使用 ReplicaSet 中包含的 ownerReference 找到被更新的 ReplicaSet 引用的 Deployment。然后,被引用的 Deployment 实例被入队。

控制器实现 Reconcile 方法,每当队列中出现一个 Request 时,它就会被调用。这个 Reconcile 方法接收 Request 作为参数,其中包含要调和(Reconcile)的主要资源的名称和命名空间。

请注意,触发请求的事件不是请求的一部分,因此,Reconcile 方法不能依赖这个信息。此外,如果事件发生在一个被拥有的(owned)资源上,只有主要资源被入队,Reconcile 方法不能依赖哪个被拥有的(owned)资源触发了该事件。

此外,由于多个事件可能在短时间内发生,并与同一主要资源有关,请求可以被批量处理,以限制入队的请求数量。

创建 Controller

要创建控制器,你需要使用提供的 New 函数:

import (
      "sigs.k8s.io/controller-runtime/pkg/controller"
)
controller, err = controller.New(
     "my-operator", mgr,
     controller.Options{
            Reconciler: myReconciler,
})

Reconciler 选项是必需的,其值是一个必须实现 Reconciler 接口的对象,定义为:

type Reconciler interface {
      Reconcile(context.Context, Request) (Result, error)
}

作为一种设施,提供了 reconcile.Func 类型,它实现了 Reconciler 接口,并且与 Reconcile 方法的签名相同的函数类型。

type Func func(context.Context, Request) (Result, error)
func (r Func) Reconcile(ctx context.Context, o Request) (Result, error) { 
    return r(ctx, o) 
}

由于这个 reconcile.Func 类型,你可以用 Reconcile 签名构造一个函数,并把它分配给 Reconciler 选项。比如说:

controller, err = controller.New(
     "my-operator", mgr,
     controller.Options{
            Reconciler: reconcile.Func(reconcileFunction),
})

func reconcileFunction(
     ctx context.Context,
     r reconcile.Request,
) (reconcile.Result, error) {
      [...]
      return reconcile.Result{}, nil
}

监控资源

在控制器创建后,你需要向容器指出哪些资源需要观察,以及这些资源是主要资源还是被拥有的(owned)资源。

控制器上的 Watch 方法被用来添加一个 Watch。该方法定义如下:

Watch(
     src source.Source,
     eventhandler handler.EventHandler,
     predicates ...predicate.Predicate,
) error

第一个参数表示什么是要观察的事件的来源,其类型是 source.Source 接口。controller-runtime 库为 Source 接口提供了两种实现方式:

  • Kind source 用于监视特定种类(kind)的 Kubernetes 对象的事件。Kind 结构体中的 Type 字段是必需的,其值是所需类型的对象。例如,如果我们想监视 Deployment,src 参数的值将是:

    controller.Watch(
          &source.Kind{
                Type: &appsv1.Deployment{},
          },
          ...
    
  • Channel source 是用来观察来自集群外的事件的。Channel 结构体的 Source 字段是必需的,它的值是一个发射 event.GenericEvent 类型对象的通道。

第二个参数是事件处理程序,其类型是 handler.EventHandler 接口。controller-runtime 库为 EventHandler 接口提供了两种实现方式:

  • EnqueueRequestForObject 事件处理程序用于控制器处理的主要资源。在这种情况下,控制器将把连接到事件的对象放入队列中。例如,如果控制器是处理第9章中创建的自定义资源的 operator,你将写道:

    controller.Watch(
          &source.Kind{
                Type: &mygroupv1alpha1.MyResource{},
          },
          &handler.EnqueueRequestForObject{},
    )
    
  • EnqueueRequestForOwner 事件处理程序用于由主资源拥有( owned)的资源。EnqueueRequestForOwner 的一个字段是必需的: OwnerType。这个字段的值是主资源类型的对象;控制器将跟踪 ownerReferences,直到找到这种类型的对象,并将这个对象放入队列。

    例如,如果控制器处理 MyResource 主资源,并且正在创建 Pod 来实现 MyResource,它将希望使用这个事件处理程序来观察 Pod 资源,并指定一个 MyResource 对象作为 OwnerType。

  • 如果字段 IsController 被设置为true,控制器将只考虑 Controller: true 的 ownerReferences。

    controller.Watch(
          &source.Kind{
                Type: &corev1.Pod{},
          },
          &handler.EnqueueRequestForOwner{
                OwnerType: &mygroupv1alpha1.MyResource{},
                IsController: true,
          },
    )
    

第三个参数是一个可选的谓词列表,其类型为 predicate.Predicate 。controller-runtime 库为 Predicate 接口提供了几种实现:

  • Funcs 是最通用的实现。Funcs 的结构体定义如下:

    type Funcs struct {
          // Create returns true if the Create event
         // should be processed
          CreateFunc func(event.CreateEvent) bool
          // Delete returns true if the Delete event
         // should be processed
          DeleteFunc func(event.DeleteEvent) bool
          // Update returns true if the Update event
         // should be processed
          UpdateFunc func(event.UpdateEvent) bool
          // Generic returns true if the Generic event
         // should be processed
          GenericFunc func(event.GenericEvent) bool
    }
    

    你可以把这个结构体的一个实例传递给 Watch 方法,作为 Predicate。

    未定义的字段将表示匹配类型的事件应该被处理。

    对于非 nil 字段,匹配事件的函数将被调用(注意,当源是一个通道时,GenericFunc 将被调用;见前文),如果函数返回 true,事件将被处理。

  • 使用 Predicate 的这种实现,你可以为每个事件类型定义一个特定的函数。

    func NewPredicateFuncs(
    	filter func(object client.Object) bool,
    ) Funcs
    

    该函数接受一个 filter 函数,并返回一个 Funcs 结构体,该过滤器被应用于所有事件。使用这个函数,你可以定义一个适用于所有事件类型的单一过滤器。

  • ResourceVersionChangedPredicate 结构体将定义一个只用于 UpdateEvent 的 filter。

    使用这个 predicate ,所有的Create、Delete 和 Generic 事件将被处理,不需要过滤,而 Update 事件将被过滤,以便只处理有 metadata.resourceVersion 变化的更新。

    每次保存资源的新版本时,metadata.resourceVersion 字段都会被更新,不管资源的变化是什么。

  • GenerationChangedPredicate 结构体定义了一个只用于更新事件过滤器。

    使用这个谓词,所有的 Create、Delete 和 Generic 事件都将被处理,无需过滤,而 Update 事件将被过滤,因此只有具有 metadata.Generation 增量的更新才会被处理。

    每次发生资源的 Spec 部分的更新时,API 服务器都会按顺序递增 metadata.Generation

    请注意,有些资源并不尊重这个假设。例如,当 Annotations 字段被更新时,Deployment 的 Generation 也会被递增。

    对于自定义资源,只有当状态子资源被启用时,生成才会被递增。

  • AnnotationChangedPredicate 结构体定义了一个只用于更新事件过滤器。

    使用这个谓词,所有的创建、删除和通用事件都将被处理,而更新事件将被过滤,因此只有元数据.注释变化的更新才会被处理。

第一个例子

在第一个例子中,你将创建管理器和控制器。控制器将管理主要的自定义资源 MyResource,并观察这个资源以及 Pod 资源。

Reconcile 函数将只显示 MyResource 实例的命名空间和名称来进行调节。

package main
import (
      "context"
      "fmt"
      corev1 "k8s.io/api/core/v1"
      "k8s.io/apimachinery/pkg/runtime"
      "sigs.k8s.io/controller-runtime/pkg/client/config"
      "sigs.k8s.io/controller-runtime/pkg/controller"
      "sigs.k8s.io/controller-runtime/pkg/handler"
      "sigs.k8s.io/controller-runtime/pkg/manager"
      "sigs.k8s.io/controller-runtime/pkg/reconcile"
      "sigs.k8s.io/controller-runtime/pkg/source"
      clientgoscheme "k8s.io/client-go/kubernetes/scheme"
      mygroupv1alpha1 "github.com/myid/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1"
)
func main() {
      scheme := runtime.NewScheme()                     
      clientgoscheme.AddToScheme(scheme)
      mygroupv1alpha1.AddToScheme(scheme)
      mgr, err := manager.New(                          
            config.GetConfigOrDie(),
            manager.Options{
                  Scheme: scheme,
            },
      )
      panicIf(err)
      controller, err := controller.New(                 
            "my-operator", mgr,
            controller.Options{
                  Reconciler: &MyReconciler{},
            })
      panicIf(err)
      err = controller.Watch(                             
            &source.Kind{
                  Type: &mygroupv1alpha1.MyResource{},
            },
            &handler.EnqueueRequestForObject{},
      )
      panicIf(err)
      err = controller.Watch(                              
            &source.Kind{
                  Type: &corev1.Pod{},
            },
            &handler.EnqueueRequestForOwner{
                  OwnerType:    &corev1.Pod{},
                  IsController: true,
            },
      )
      panicIf(err)
      err = mgr.Start(context.Background())                
      panicIf(err)
}
type MyReconciler struct{}                                 
func (o *MyReconciler) Reconcile(                          
     ctx context.Context,
     r reconcile.Request,
) (reconcile.Result, error) {
      fmt.Printf("reconcile %v\n", r)
      return reconcile.Result{}, nil
}
// panicIf panic if err is not nil
// Please call from main only!
func panicIf(err error) {
      if err != nil {
            panic(err)
      }
}

➊ 用本地资源和自定义资源 MyResource 创建 scheme。

➋ 使用刚刚创建的 scheme 创建管理器

➌ 创建控制器,连接到管理器,并传递 Reconciler 实现。

➍ 开始观察作为主要资源的 MyResource 实例

➎ 开始观察 Pod 实例,作为自有(owned)资源

➏ 启动管理器。这个函数是长期运行的,只有在发生错误时才会返回

➐ 实现 Reconciler 接口的类型

➑ 实现 Reconcile 方法。这将显示要 reconcile 的实例的名称空间和名称

使用Controller Builder

Controller-runtime 库提出了一个控制器生成器 / Controller Builder,使控制器的创建更加简洁。

import (
     "sigs.k8s.io/controller-runtime/pkg/builder"
)
func ControllerManagedBy(m manager.Manager) *Builder

ControllerManagedBy 函数被用来启动一个新的 ControllerBuilder。构建的控制器将被添加到 m manager 中。一个流畅的接口帮助配置构建:

  • For(object client.Object, opts ...ForOption) *Builder - 该方法用于指示控制器处理的主要资源。它只能被调用一次,因为一个控制器只能有一个主要资源。这将在内部调用 Watch 函数与事件处理程序 EnqueueRequestForObject 。

    可以用 WithPredicates 函数为这个 watch 添加谓词(Predicates),其结果实现了 ForOption 接口。

  • Owns(object client.Object, opts ...OwnsOption) *Builder - 这个方法用来表示控制器拥有(owned)的资源。这将在内部调用 Watch 函数与事件处理程序 EnqueueRequestForOwner。

    可以用 WithPredicates 函数为这个Watch添加谓词,该函数的结果实现了OwnsOption接口。

  • Watches(src source.Source, eventhandler handler.EventHandler, opts ...WatchesOption) *Builder - 这个方法可以用来添加更多For或Owns方法没有涵盖的 watcher –例如,具有 Channel source 的观察者。

    可以用 WithPredicates 函数为该 watch 添加谓词,该函数的结果实现了 WatchesOption 接口。

  • WithEventFilter(p predicate.Predicate) *Builder - 这个方法可以用来添加所有用 For、Owns 和 Watch 方法创建的观察者共有的谓词。

  • WithOptions(options controller.Options) *Builder - 此处设置将在内部传递给 controller.New 函数的选项。

  • WithLogConstructor(- 这设置了 logConstructor 选项。

    func(*reconcile.Request) logr.Logger,
    
    ) *Builder
    
  • Named(name string) *Builder - 这设置了构造函数的名称。它应该只使用下划线和字母数字字符。默认情况下,它是主要资源的 kind 的小写版本。

  • build() 构建并返回控制器。

    Build( 
    	r reconcile.Reconciler,
    ) (controller.Controller, error)
    
  • Complete(r reconcile.Reconciler) error –这就建立了控制器。你一般不需要直接访问控制器,所以你可以使用这个不返回控制器值的方法,而不是Build。

使用ControllerBuilder的第二个例子

在这个例子中,你将使用 ControllerBuilder 构建控制器,而不是使用 Controller.New 函数和控制器上的 Watch 方法。

package main
import (
      "context"
      "fmt"
      corev1 "k8s.io/api/core/v1"
      "k8s.io/apimachinery/pkg/runtime"
      clientgoscheme "k8s.io/client-go/kubernetes/scheme"
      "sigs.k8s.io/controller-runtime/pkg/builder"
      "sigs.k8s.io/controller-runtime/pkg/client"
      "sigs.k8s.io/controller-runtime/pkg/client/config"
      "sigs.k8s.io/controller-runtime/pkg/manager"
      "sigs.k8s.io/controller-runtime/pkg/reconcile"
      mygroupv1alpha1 "github.com/feloy/myresource-crd/pkg/apis/mygroup.example.com/v1alpha1"
)
func main() {
      scheme := runtime.NewScheme()
      clientgoscheme.AddToScheme(scheme)
      mygroupv1alpha1.AddToScheme(scheme)
      mgr, err := manager.New(
            config.GetConfigOrDie(),
            manager.Options{
                  Scheme: scheme,
},
      )
      panicIf(err)
      err = builder.
            ControllerManagedBy(mgr).
            For(&mygroupv1alpha1.MyResource{}).
            Owns(&corev1.Pod{}).
            Complete(&MyReconciler{})
      panicIf(err)
      err = mgr.Start(context.Background())
      panicIf(err)
}
type MyReconciler struct {}
func (a *MyReconciler) Reconcile(
      ctx context.Context,
      req reconcile.Request,
) (reconcile.Result, error) {
      fmt.Printf("reconcile %v\n", req)
      return reconcile.Result{}, nil
}
func panicIf(err error) {
      if err != nil {
            panic(err)
      }
}

4 - 将管理器资源注入 Reconciler 中

将管理器资源注入 Reconciler 中

管理器为控制器提供共享资源,包括读取和写入 Kubernetes 资源的客户端、从本地缓存中读取资源的缓存以及解析资源的方案。Reconcile 函数需要访问这些共享资源。有两种方法来共享它们:

创建 Reconciler 结构体时传递数值

当控制器被创建时,你正在传递一个 Reconcile 结构体的实例,实现 Reconciler 接口:

type MyReconciler struct {}
err = builder.
            ControllerManagedBy(mgr).
            For(&mygroupv1alpha1.MyResource{}).
            Owns(&corev1.Pod{}).
            Complete(&MyReconciler{})

在这之前,管理器已经被创建,你可以使用管理器上的 Getters 来访问共享资源。

作为例子,下面是如何从新创建的管理器中获取客户端、缓存和 schema :

mgr, err := manager.New(
      config.GetConfigOrDie(),
      manager.Options{
            manager.Options{
                  Scheme: scheme,
          },
     },
)
// handle err
mgrClient := mgr.GetClient()
mgrCache := mgr.GetCache()
mgrScheme := mgr.GetScheme()

你可以在 Reconciler 结构体中添加字段来传递这些值:

type MyReconciler struct {
     client client.Client
     cache cache.Cache
     scheme *runtime.Scheme
}

最后,你可以在创建控制器的过程中传递这些值:

err = builder.
            ControllerManagedBy(mgr).
            For(&mygroupv1alpha1.MyResource{}).
            Owns(&corev1.Pod{}).
            Complete(&MyReconciler{
      client: mgr.GetClient(),
      cache: mgr.GetCache(),
      scheme: mgr.GetScheme(),
})

使用 Injector

controller-runtime 库提供了一个注入器(Injectors)系统,用于将共享资源注入 Reconcilers,以及其他结构体,如你自己实现的 Sources、EventHandlers 和 Predicates。

Reconciler 的实现需要实现 inject 包中的特定 Injector 接口:inject.Clientinject.Cacheinject.Scheme,等等。

这些方法将在初始化时被调用,当你调用 controller.Newbuilder.Complete。为此,需要为每个接口创建一个方法,例如:

type MyReconciler struct {
     client client.Client
     cache cache.Cache
     scheme *runtime.Scheme
}
func (a *MyReconciler) InjectClient( c client.Client,) error {
      a.client = c
      return nil
}
func (a *MyReconciler) InjectCache( c cache.Cache, ) error {
      a.cache = c
      return nil
}
func (a *MyReconciler) InjectScheme( s *runtime.Scheme, ) error {
      a.scheme = s
      return nil
}

5 - 使用客户端

使用客户端

客户端可以用来读取和写入集群上的资源,并更新资源的状态。

Read 方法在内部使用一个基于 Informers 和 Listers 的 Cache 系统,以限制对 API 服务器的读取访问。使用这个缓存,同一个管理器的所有控制器都有对资源的读取权限,同时限制对 API 服务器的请求。

必须注意:

由读操作返回的对象是指向缓存的值的指针。你决不能直接修改这些对象。相反,你必须在修改这些对象之前创建一个返回对象的深度拷贝。

客户端的方法是通用的:它们与任何 Kubernetes 资源一起工作,无论是原生资源还是自定义资源,如果它们被传递给管理器的 schema 所知道的话。

所有的方法都会返回错误,其类型与 Client-go Clientset 方法所返回的错误相同。你可以参考第6章的 “错误和状态” 部分以了解更多信息。

获取资源的信息

Get 方法用来获取资源的信息。

Get(
     ctx context.Context,
     key ObjectKey,
     obj Object,
     opts ...GetOption,
) error

它需要一个 ObjectKey 值作为参数,表示资源的名称空间和名称,以及一个Object,表示要获得的资源的类型,并存储结果。Object 必须是一个指向类型资源的指针-例如,一个 Pod 或 MyResource 结构体。ObjectKey 类型是 type.NamespacedName 的别名,定义在 API Machinery 库中。

NamespacedName 也是嵌入在作为参数传递给 Reconcile 函数的 Request 中的对象的类型。你可以直接将 req.NamespacedName 作为 ObjectKey 传递,以获得要调和(reconcile)的资源。例如,使用下面的方法来获取要调和(reconcile)的资源:

myresource := mygroupv1alpha1.MyResource{}
err := a.client.Get(
      ctx,
      req.NamespacedName,
      &myresource,
)

可以向 Get 请求传递一个特定的 resourceVersion 值,传递一个 client.GetOptions 结构体实例作为最后一个参数。

GetOptions 结构体实现了 GetOption 接口,包含一个具有 metav1.GetOptions 值的单一Raw字段。例如,指定一个值为 “0 " 的 resourceVersion 来获取资源的任何版本:

err := a.client.Get(
      ctx,
      req.NamespacedName,
      &myresource,
      &client.GetOptions{
            Raw: &metav1.GetOptions{
                  ResourceVersion: "0",
            },
      },
)

列出资源

List 方法用于列出特定种类的资源:

List(
     ctx context.Context,
     list ObjectList,
     opts ...ListOption,
) error

list 参数是一个 ObjectList 值,表示要列出并存储结果的资源的种类(kind)。默认情况下,list 是在所有命名空间中进行的。

List 方法接受实现 ListOption 接口的对象的零个或多个参数。这些类型由以下支持:

  • InNamespace,string 的别名,用于返回特定命名空间的资源。

  • MatchingLabelsmap[string]string 的别名,用来表示标签的列表和它们的精确值,这些标签必须被定义为资源的返回。下面的例子建立了一个MatchingLabels 结构体来过滤带有标签 “app=myapp” 的资源。

    matchLabel := client.MatchingLabels{
          "app": "myapp",
    }
    
  • HasLabels,别名为 []string,用来表示标签的列表,独立于它们的值,必须为一个资源的返回而定义。下面的例子建立了一个 HasLabels 结构体来过滤带有 “app” 和 “debug” 标签的资源。

    hasLabels := client.HasLabels{"app", debug}
    
  • MatchingLabelsSelector,嵌入了一个 labels.Selector 接口,用来传递更高级的标签选择器。关于如何建立一个选择器的更多信息,请参见第6章的 “过滤列表结果” 部分。下面的例子建立了一个 MatchingLabelsSelector 结构,它可以作为 List 的一个选项来过滤标签 mykey 不同于 ignore 的资源。

    selector := labels.NewSelector()
    require, err := labels.NewRequirement(
        "mykey",
        selection.NotEquals,
        []string{"ignore"},
    )
    // assert err is nil
    selector = selector.Add(*require)
    labSelOption := client.MatchingLabelsSelector{
          Selector: selector,
    }
    
  • MatchingFields 是 fields.Set 的别名,它本身是 map[string]string 的别名,用来指示要匹配的字段和它们的值。下面的例子建立了一个 MatchingFields 结构体,用来过滤字段 “status.phase” 为 “Running” 的资源:

    matchFields := client.MatchingFields{
          "status.phase": "Running",
    }
    
  • MatchingFieldsSelector,嵌入了一个 fields.Selector,用来传递更高级的字段选择器。关于如何构建 fields.Selector 的更多信息,请参见第6章的 “过滤列表结果” 部分。下面的例子建立了一个 MatchingFieldsSelector 结构体来过滤字段 “status.phase” 与 “Running” 不同的资源:

    fieldSel := fields.OneTermNotEqualSelector(
          "status.phase",
          "Running",
    )
    fieldSelector := client.MatchingFieldsSelector{
          Selector: fieldSel,
    }
    
  • Limit(别名 int64)和Continue(别名 string)用于对结果进行分页。这些选项在第二章的 “分页结果” 部分有详细介绍。

创建资源

Create 方法用来在集群中创建一个新的资源。

Create(
     ctx context.Context,
     obj Object,
     opts ...CreateOption,
) error

作为参数传递的 obj 定义了要创建的对象的种类(kind),以及它的定义。下面的例子将在集群中创建一个 Pod:

podToCreate := corev1.Pod{ [...] }
podToCreate.SetName("nginx")
podToCreate.SetNamespace("default")
err = a.client.Create(ctx, &podToCreate)

以下选项可以作为 CreateOption 传递,以使 Create 请求参数化。

  • DryRunAll 值表示所有的操作都应该被执行,除了那些将资源持久化到存储的操作。

  • FieldOwner,别名为字符串,表示创建操作的字段管理器的名称。这个信息对于服务器端应用操作的正常工作很有用。

删除资源

删除方法是用来从集群中删除资源。

Delete(
     ctx context.Context,
     obj Object, k
     opts ...DeleteOption,
) error

作为参数传递的 obj 定义了要删除的对象的种类(kind),以及它的命名空间(如果资源有命名空间)和它的名字。下面的例子可以用来删除 Pod。

podToDelete := corev1.Pod{}
podToDelete.SetName("nginx")
podToDelete.SetNamespace("prj2")
err = a.client.Delete(ctx, &podToDelete)

以下选项可以作为 DeleteOption 被传递,以便对 Delete 请求进行参数化。

  • DryRunAll - 这个值表示所有的操作都应该被执行,除了那些将资源持久化到存储的操作。
  • GracePeriodSeconds, alias to int64 - 这个值只在删除 pod 时有用。它表示在删除 pod 之前的持续时间(秒)。更多细节见第6章的 “删除资源” 部分。
  • Preconditions / 前提条件,别名 metav1.Preconditions - 这表明你期望删除的资源。更多细节请参见第6章 “删除资源” 部分。
  • PropagationPolicy,别名 metav1.DeletionPropagation - 这表明是否以及如何进行垃圾回收。更多细节见第6章 “删除资源” 部分。

删除资源集合

DeleteAllOf 方法是用来从集群中删除所有给定类型的资源。

DeleteAllOf(
     ctx context.Context,
     obj Object,
     opts ...DeleteAllOfOption,
) error

作为参数传递的 obj 定义了要删除的对象的种类。

DeleteAllOf 操作的 opts 可选项是 List 操作(见 “列出资源” 部分)和 Delete 操作(见 “删除资源” 部分)的选项的组合。

作为例子,下面是如何删除给定命名空间的所有 deployment:

err = a.client.DeleteAllOf(
     ctx,
     &appsv1.Deployment{},
     client.InNamespace(aNamespace))

更新资源

TBD

修补资源

Strategic Merge Patch

Merge Patch

TBD

更新资源的状态

在和 Operator 工作时,您要在自定义资源的 status 部分修改数值,以表明它的当前状态。

Update(
     ctx context.Context,
     obj Object,
     opts ...UpdateOption,
) error

请注意,您不需要为状态进行特定的创建、获取或删除操作,因为您将在创建自定义资源本身时将状态创建为自定义资源的一部分。另外,资源的 Get 操作将返回资源的 Status 部分,删除资源将删除其 Status 部分。

然而,API 服务器强迫你对 Status 和资源的其他部分使用不同的 Update 方法,以保护你不会在同一时间错误地修改这两部分。

为了使状态的更新发挥作用,自定义资源定义必须在自定义资源的子资源列表中声明 status 字段。参见第8章的 “资源版本的定义” 部分,了解如何声明这个状态子资源。

这个方法的工作原理和资源本身的 Update 方法一样,但是要调用这个方法,你需要在 client.Status() 返回的对象上调用这个方法:

err = client.Status().Update(ctx, obj)

修补资源的状态

与更新方法一样,修补资源的状态需要一个专门的方法来处理 client.Status() 的结果。

Patch(
    ctx context.Context,
    obj Object,
    patch Patch,
    opts ...PatchOption,
) error

对 Status 的 Patch 方法与对资源本身的 Patch 方法工作相同,只是它只对资源的 Status 部分进行修补。

err = client.Status().Patch(ctx, obj, patch)

6 - 日志

日志

TBD

7 - 事件

事件

Kubernetes API提供了一个 事件/Event 资源,事件实例被附加到任何类型的特定实例。事件由控制器发送,以通知用户与某个对象相关的一些事件发生。在执行kubectl describe 时,会显示这些事件。例如,你可以看到与一个 pod 执行相关的事件:

$ kubectl describe pods nginx
[...]
Events:
  Type    Reason     Age        From               Message
  ----    ------     ----       ----               -------
  Normal  Scheduled  1s         default-scheduler  Successfully assigned...
  Normal  Pulling    0s         kubelet            Pulling image "nginx"
  Normal  Pulled     <invalid>  kubelet            Successfully pulled...
  Normal  Created    <invalid>  kubelet            Created container nginx
  Normal  Started    <invalid>  kubelet            Started container nginx

为了从 Reconcile 函数中发送此类事件,你需要访问由管理器提供的 EventRecorder 实例。在初始化过程中,你可以用管理器上的 GetEventRecorderFor 方法获得这个实例,然后在构建 Reconcile 结构体时传递这个值:

type MyReconciler struct {
      client        client.Client
      EventRecorder record.EventRecorder
}
func main() {
      [...]
      eventRecorder := mgr.GetEventRecorderFor(
"MyResource",
     )
      err = builder.
            ControllerManagedBy(mgr).
            Named(controller.Name).
            For(&mygroupv1alpha1.MyResource{}).
            Owns(&appsv1.Deployment{}).
            Complete(&controller.MyReconciler{
                  EventRecorder: eventRecorder,
            })
      [...]
}

然后,从 Reconcile 函数中,你可以调用 Event、Eventf 和 AnnotatedEventf 方法。

func (record.EventRecorder) Event(
     object runtime.Object,
     eventtype, reason, message string,
)
func (record.EventRecorder) Eventf(
     object runtime.Object,
     eventtype, reason, messageFmt string,
     args ...interface{},
)
func (record.EventRecorder) AnnotatedEventf(
     object runtime.Object,
     annotations map[string]string,
     eventtype, reason, messageFmt string,
     args ...interface{},
)

object 参数表示将事件附加到哪个对象。你将传递被调和的自定义资源。

eventtype 参数接受两个值:corev1.EventTypeNormalcorev1.EventTypeWarning

reason 参数是一个 UpperCamelCase 格式的短值。message 参数是一个人类可读的文本。

Event 方法用于传递静态消息,Eventf 方法可以使用 Sprintf 创建一个消息,AnnotatedEventf 方法也可以给事件附加注释。

结论

在本章中,你已经使用 controller-runtime 库开始创建 operator。你已经看到了如何使用适当的 schema 创建 manager,如何创建控制器并声明 Reconcile 函数,并探索了库所提供的客户端的各种功能来访问集群中的资源。

下一章将探讨如何编写 Reconcile 函数。