暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

云原生|详解Kubernetes Operator在项目中的开发应用

585

本期摘要

Kubernetes Operator是一种扩展形式,可帮助用户以Kubernetes的声明式API风格自定义管理应用和服务。在项目中,开发Kubernetes Operator能够极大地提高应用和服务的可靠性和可维护性。


本文主要介绍了k8s operator在项目中开发中的应用,供各位参考。

 作者 

刘健 | 后端开发工程师

一位神秘的程序员~



01

使用场景


controller-runtime是基于kubernetes控制器模式衍生出来的一套框架。控制回路(Control Loop)是一个非终止回路,用于调节系统状态。一个控制器至少追踪一种类型的 Kubernetes 资源。这些 对象 有一个代表期望状态的 spec 字段。该资源的控制器负责确保其当前状态接近期望状态。


从中,我们可以得出,controller-runtime开发需要遵循以下几点。


  1. controller-runtime需要持续监听kubernetes对象。(通过list/watch机制实现)


  2. controller-runtime需要提供某些机制,Kubernetes对象的status(实际状态)维持到与spec(期望状态)一致。(通过reconcile实现)



一、client-go中处理逻辑


在client-go中,会将一种Kubernetes对象封装成一个对的informer类型。informer类型中包含Reflector,DeltaFIFO,Indexer,EventHandler。


  • Reflector:用于调用Kubernetes的API接口。informer会通过两个Kubernetes的API接口来实现kubernetes对象的监听。list/watch失败时,会自动重新list/watch


  • list接口:通过http请求,获取informer中定义的全量kubernetes对象数据到deltaFIFO中


  • watch接口:从本地存储的最大resourceVersion开始监听。当发送 watch 请求时,API 服务器会响应更改流。这些更改逐项列出了在你指定为 watch 请求参数的 resourceVersion 之后发生的操作(例如 ADDED、MOD-IFIED、DELETED、BOOKMARK、ERROR)的结果。


  • DeltaFIFO:存储每个待处理的Kubernetes对象。DeltaFIFO中有两个存储kubernetes对象的数据结构,一个是存储对象key值的一维数组,一个存储数据流的map结构。


  • queue: 一维数组,每个kubernetes对象对应一个key(namespace/name)。多次入队的变更数据会合并存储,出队时,处理整个对象。


  • items: 存储kubernetes对象的变更流。(Added,Updated,Deleted,Replaced,Sync)启动informer时,持续从deltaFIFO中读取数据进行处理。


  • Indexer:从DeltaFIFO中获取的Kubernetes对象,存储到内存map中。


  • EventHandler:从DeltaFIFO中获取的Kubernetes对象,根据对象的变更事件类型,来进行不同的处理。一般是将key值传递给工作队列。然后通过custom controller进行处理。


二、controller-runtime中处理逻辑


根据要监听的Kubernetes对象,定义Informer


定义EventHandler处理逻辑。将informer eventhandler中的key值存入工作队列。该工作队列中会对key值进行去重处理。以避免在一个工作队列出现并发处理同一个对象的情况。


从工作队列中取出key值,在reconcile进行处理。

自定义结构体实现reconcile方法。在该方法中,协调kubernetes对象的状态,使其向期望状态靠拢。


controller-runtime框架简介


controller-runtime顶层使用manager进行封装。manager包含以下信息


  • cluster:用于与kubernetes集群通信。包含kubernetes的kubeconfig配置信息,连接kuberenetes的apiserver的客户端信息,注册到operator的kubernetes对象scheme信息,用于获取gvk和gvr的RESTMapper信息,用于informer存储的indexer信息,用于和kubernetes对象读,写和事件发布的客户端信息。


  • controller:用于informer的配置与启动,reconcile的定义


  • cache:缓存informer信息。包含informer的定义和indexer数据读取。


  • webhook:用于发布kubernetes中的三种类型的webhook服务。


  • election:用于operator的主备配置


  • prometheus:用于发布prometheus的数据服务


  • probe:定义operator的探针服务,包含两种探针数据:健康探针和存活探针



02

使用controller-runtime开发operator项目


一、生成框架代码


官方推荐使用kubebuilder插件来自动生成controller-runtime框架。


生成项目框架:kubebuilder init --domain demo --plugins=go/v4-alpha


生成kubernetes的crd对象对应的api(使用Kubernetes默认的对象可忽略):kubebuilder create api --group webapp --version v1 --kind Guestbook


生成webhook服务代码:kubebuilder create webhook --group webapp --version v1 --kind Guestbook --defaulting --programmatic-validation


生成的代码目录结构如下



  • api:包含crd的api定义,webhook文件,需在guestbook_types.go中补充需要的字段。然后通过Makefile中的命令生成所需要的文件(可部署到kubernetes集群的yaml文件,实现client-go中定义的object方法的go文件)


  • bin:生成文件时,用到的工具。controller-gen(生成crd关联文件)和kustomize(生成opertor项目的部署文件)


  • config:kustomize执行时用到的配置文件。


  • controllers:controllers示例文件。包含自定义的Reconcile结构体,实现了Reconcile方法用于协调kubenetes资源,和用于定义informer的方法。


  • hack:controller-gen执行时用到的配置文件。


  • dockerignore Dockerfile:生成operator项目镜像文件


  • main.go:启动文件


  • Makefile:封装一些make命令。包含生成代码命令,部署operator项目命令等


  • PROJECT:执行kubebuider命令后生成的项目配置信息文件


二、定义crd字段


补充guestbook_types.go文件。该文件中,需要补充两类数据。


  • struct中的字段


spec中定义期望状态字段。status中定义实际状态字段。


字段后的json标签用于生成kubernetes中crd文件对应的字段。omitepty表示该字段是否该字段是否必填。


  • kubebuilder注释


用于生成crd文件和实现了client-go中定义的runtime.Object的deepcopy文件。


官网中kubebuilder注释展示了注释的4种用法:


- Validation(定义在字段上,用于生成crd文件的字段验证)


Additional Printer Columns(定义在结构体上,对应crd文件中的spec.ver-sions.additionalPrinterColumns)


- Subresources(定义在结构体上,对应crd文的.spec.version.subresources)


- Multiple Versions(定义在结构体上,在多版本的中定义指定版本,对应crd文件中的spec.versions.storage字段定义)


⚠️定义在结构体上的注释与注释之间不能有空行


三、生成crd文件


执行make manifest生成WebhookConfiguration,ClusterRole,Custom-ResourceDefinition


WebhookConfiguration:根据//+kubebuilder:webhook注释生成,一般定义在guestbook_webhook.go文件中


ClusterRole:根据//+kubebuilder:rbac注释生成,一般定义controllers/guest

-book_controller.go文件中


CustomResourceDefinition:根据guestbook_types.go和groupversion_in-fo.go文件生成。


执行make generate生成实现zz_generated.deepcopy.go文件


zz_generated.deepcopy.go文件:实现了runtime.Object中的方法,用于client-go依赖包中的类型转换。根据//+kubebuilder:object注释生成。


四、初始化manager


在main.go文件中对manager进行初始化。


mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
   Scheme: scheme,
   MetricsBindAddress:     metricsAddr,
   Port:                   9443,
   HealthProbeBindAddress: probeAddr,
   LeaderElection: enableLeaderElection,
   LeaderElectionID: "aefd3536.demo",
})


配置controller


if err = (&controllers.GuestbookReconciler{
   Client: mgr.GetClient(),
   Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
   setupLog.Error(err, "unable to create controller", "controller", "Guestbook")
   os.Exit(1)
}


配置webhook


if err = (&webappv1.Guestbook{}).SetupWebhookWithManager(mgr); err != nil {
   setupLog.Error(err, "unable to create webhook", "webhook", "Guestbook")
   os.Exit(1)
}


配置探针


if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
   setupLog.Error(err, "unable to set up health check")
   os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
   setupLog.Error(err, "unable to set up ready check")
   os.Exit(1)
}


启动manager


if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
   setupLog.Error(err, "problem running manager")
   os.Exit(1)
}



五、配置controller


guestbook_controller.go文件中定义了两个方法。


func (r *GuestbookReconciler) SetupWithManager(mgr ctrl.Manager) error {
   return ctrl.NewControllerManagedBy(mgr).
      For(&webappv1.Guestbook{}).
      WithEventFilter(predicate.Funcs{
         CreateFunc: func(_ event.CreateEvent) bool {
            return false
         },
      }).
      WithOptions(controller.Options{MaxConcurrentReconciles: 2}).
      Complete(r)
}


在main.go文件中调用,用于定义informer并将informer配置到manager中


  • informer中kubernetes对象


框架对对象类型的定义提供了三种方法:


· Watches(source.Source, handler.EventHandler, ...WatchesOption)

· For(client.Object,...ForOption)

· Owns(client.Object,...OwnsOption)


其中For和Owns是等同与Watches。


For的第二个参数默认为EnqueueRequestForObject。


Owns的第二个参数默认为EnqueueRequestForOwner


方法参数说明


Source:第一个参数,kubernetes对象类型


EventHandler:第二个参数,从DeltaFIFO中取出来的数据,在进入工作队列前进行的操作。EnqueueRequestForObject表示不做任何处理,直接进入工作队列。EnqueueRequestForOwner需要和For方法配合使用,Owns中的对象中ownerReference引用的对象类型需要和For中定义的对象类型相同,且ownerReference中的controller为true。


Predicate:第三个参数,从工作队列取出来的数据,在进行reconcile处理前进行的操作。通过builder的WithEventFilter可以给所有的对象添加Predicate。


EventHandler和Predicate方法说明


Create:kubernetes对象新增时调用


Update:kubernetes对象更新时调用


Delete:kubernetes对象删除时调用


Generic:未知的操作。非kubernetes集群的变更事件。在operator中自行使用


  • controller配置


通过builder.WithOptions定义配置。controller中有以下配置可定义。


MaxConcurrentReconciles:从工作队列取出的数据,可使用几个协程进行处理。默认一个


Reconciler:协调器,需要实现Reconciler方法。默认为guest-book_controller.go文件中定义的Reconcile


RateLimiter:工作队列的限流策略。默认有两种限流策略。令牌桶策略(BucketRateLimiter):默认100个令牌数量。每秒可取10个令牌。退避策略(ItemExponentialFailureRateLimiter):reconcile返回失败时,重新入队。下次出队时间按2的指数次方增长。初始5ms,最大1000s。


LogConstructor:reconcile中从context中取到的日志logr。默认是mgr中定义的日志。


CacheSyncTimeout:informer同步超时时间。在informer进行list/watch时,只有当list接口调用的数据全部同步到informer的indexer后,才算informer同步成功。


RecoverPanic:reconcile异常时是否自动恢复。


func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
   _ = log.FromContext(ctx)
 
   // TODO(user): your logic here
 
   return ctrl.Result{}, nil
}


Reconcile方法为自定义的逻辑。在实现自定义逻辑时,不需要太过于关注中间状态的影响,只需要保证当前状态应该尽量往期望状态靠拢。Reconcile返回值的Result中,也可以定义是否重新入队与出队时间。


六、配置webhook


框架提供了三种webhook:


Defaulter


func (r *Guestbook) Default() {
   guestbooklog.Info("default", "name", r.Name)
 
   // TODO(user): fill in your defaulting logic.
}


对应kubernetes中的MutatingAdmissionWebhook对象。


Validator


func (r *Guestbook) ValidateCreate() error {
   guestbooklog.Info("validate create", "name", r.Name)
 
   // TODO(user): fill in your validation logic upon object creation.
   return nil
}
 
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Guestbook) ValidateUpdate(old runtime.Object) error {
   guestbooklog.Info("validate update", "name", r.Name)
 
   // TODO(user): fill in your validation logic upon object update.
   return nil
}
 
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Guestbook) ValidateDelete() error {
   guestbooklog.Info("validate delete", "name", r.Name)
 
   // TODO(user): fill in your validation logic upon object deletion.
   return nil
}


对应kuberenetes中的ValidatingWebhookConfiguration对象。


convertion.Webhook


对应crd中的spec.convertion中定义的webhook


中心版本


// Hub 标注为中心版本
func (r *Guestbook) Hub() {
 
}


其他版本


func (r *Guestbook) ConvertTo(dst conversion.Hub) error {
   // todo 当前版本向中心版本转换
   return nil
}
func (r *Guestbook) ConvertFrom(src conversion.Hub) error {
   // todo 中心版本向当前版本转换
   return nil
}



那么以上就是k8s operator在项目中的开发应用啦,希望对你有所帮助~





Hello~

这里是神州数码云基地
编程大法,技术前沿,尽在其中
Odoo、数据库、云原生、DevOps
超多原创技术干货持续输出ing~


想要第一时间获取
超硬技术干货
快快点击关注+设为星标


拜托拜托啦

这对“我们”都很重要哦~


- END -



往期精选




云原生丨Packer插件开发在项目中如何应用?




云原生丨Go-Micro实践中如何进行权限设置?




云原生丨Rancher和OpenShift,究竟该怎么选?



了解云基地,就现在!



文章转载自神州数码云基地,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论