介绍

Kubernetes控制器是一个主动调谐的过程,它会watch一些对象的期望状态,也会watch实际的状态,然后控制器会发送一些指令尝试让对象的当前状态往期望状态迁移。

控制器最简单的实现就是一个循环:

for{
desired := getDesiredState()
current := getCurrentState()
makeChanges(desired,current)
}

Watches 这些都只是这个逻辑的优化。

指南

当你在编写控制器时,有一些准则将有助于确保你得到你需要的结果和性能。

  • 一次只操作一个元素,如果你使用了workqueue.Interface,你可以将某个资源的变化排成队列,随后将他们弹到多个“worker” gofuncs 中,并保证没有两个gofuncs会同时对同一个元素进行操作。

    许多控制器必须触发多个资源(我需要“如果 Y 更改,则检查 X”),但几乎所有控制器都可以根据关系将这些资源折叠到“检查此 X”的队列中。例如,ReplicaSet 控制器需要对被删除的 pod 做出反应,但它通过查找相关的 ReplicaSet 并将它们排队来做到这一点。

  • 资源之间的随机排序。当控制器对多种类型的资源进行排队时,无法保证资源之间的排序。

    不同的watch独立更新。即使使用“created resourceA/X”和“created resourceB/Y”的客观顺序,您的控制器也可以观察到“created resourceB/Y”和“created resourceA/X”。

  • 水平驱动,而不是边缘驱动。就像没有一直运行的shell脚本一样,您的控制器可能会在再次运行之前关闭一段不确定的时间。

    如果一个API对象出现的标记值为true,您不能指望看到它从false变为true,只是您现在观察到它是true。即使是API watch也会遇到这个问题,因此请确保您不要指望看到更改,除非您的控制器还在对象状态中标记了它上次做出的决定信息。

  • 使用 SharedInformers。SharedInformers 提供了回调函数来接收特定资源的添加、更新和删除的通知,它们还提供了访问共享缓存和确定缓存何时启动的便利功能。

    使用 https://git.k8s.io/kubernetes/staging/src/k8s.io/client-go/informers/factory.go 中的工厂方法来确保你和其他人共享同一个缓存实例。

    这样我们就大大减少了 APIServer 的连接以及重复的序列化、重复的反序列化、重复的缓存等成本。

    你可能会看到其他机制,比如反射器和 DeltaFIFO 驱动控制器。这些都是旧的机制,我们后来用它们来构建 SharedInformers,你应该避免在新控制器中使用它们。

  • 永远不要改变原始对象!缓存在控制器之间共享,这意味着如果您改变对象的“副本”(实际上是引用或浅拷贝),您将弄乱其他控制器(不仅仅是您自己的)。

    最常见的失败点是制作一个浅拷贝,然后改变一个映射,比如Annotations. 用于api.Scheme.Copy制作深拷贝。

  • 等待您的二级缓存。许多控制器具有主要和次要资源。主要资源是您将为其更新的资源Status。次要资源是您将要管理(创建/删除)或用于查找的资源。

    在启动主要同步功能之前,使用该framework.WaitForCacheSync功能等待二级缓存。这将确保诸如 ReplicaSet 的 Pod 计数之类的东西不会因已知的过时信息而导致抖动。

  • 系统中还有其他参与者。仅仅因为您没有更改对象并不意味着其他人没有。

    不要忘记当前状态可能随时改变——仅仅观察期望的状态是不够的。如果您使用不存在所需状态的对象来指示应删除当前状态的事物,请确保您的观察代码中没有错误(例如,在缓存填满之前采取行动)。

  • 将错误渗透到顶层以实现一致的重新排队。我们有一个 workqueue.RateLimitingInterface允许简单的重新排队和合理的退避。

当需要重新排队时,您的主控制器 func 应该返回错误。如果不是,它应该使用utilruntime.HandleError并返回 nil。这使得审阅者很容易检查错误处理情况,并确信您的控制器不会意外丢失它应该重试的东西。

  • Watches 和 Informers 将“同步”。他们会定期将集群中的每个匹配对象传递给您的Update方法。这适用于您可能需要对对象采取额外操作但有时您知道不会有更多工作要做的情况。

    如果您确定在没有新更改的情况下不需要重新排队项目,您可以比较新旧对象的资源版本。如果它们相同,则跳过重新排队工作。执行此操作时要小心。如果您在失败时跳过重新排队您的项目,您可能会失败,而不是重新排队,然后再也不会重试该项目。

  • 如果您的控制器正在协调的主要资源在其状态中支持 ObservedGeneration,请确保在两个字段之间的值不匹配时将其正确设置为 metadata.Generation。

    这让客户端知道控制器已经处理了资源。确保您的控制器是负责该资源的主控制器,否则如果您需要通过您自己的控制器传达观察,则需要在资源的状态中创建不同类型的 ObservedGeneration。

  • 考虑对导致创建其他资源的资源使用所有者引用(例如,ReplicaSet 导致创建 Pod)。因此,您可以确保一旦您的控制器管理的资源被删除,子资源将被垃圾收集。有关所有者参考的更多信息,请在此处阅读更多信息。

    要特别注意收养的方式。当父母或孩子被标记为删除时,您不应该为资源收养孩子。如果您正在为您的资源使用缓存,您可能需要通过直接读取 API 来绕过它,以防您发现所有者引用已为其中一个孩子更新。因此,您可以确保您的控制器不会与垃圾收集器竞争。

示例

package main

import (
"flag"
"fmt"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
"path/filepath"
"time"
) type Controller struct {
indexer cache.Indexer
queue workqueue.RateLimitingInterface
informer cache.Controller
} func NewController(queue workqueue.RateLimitingInterface, indexer cache.Indexer, informer cache.Controller) *Controller {
return &Controller{
indexer: indexer,
queue: queue,
informer: informer,
}
} func (c *Controller) processNexItem() bool {
// 等待工作队列中有一个新元素
key, quit := c.queue.Get()
if quit {
return false
} // 告诉队列我们已经完成了处理 此 key 的操作
// 这将为其他 worker 解锁该 key
// 这将确保安全的并行处理,因为永远不会并行处理具有相同key的两个pod
defer c.queue.Done(key) // 调用包含业务逻辑的方法
err := c.syncToStdout(key.(string))
// 如果在执行业务逻辑期间出现错误,则处理错误
c.handlerErr(err, key)
return true
} // 控制器的业务逻辑实现
// 在此控制器中,它只是将有关 Pod 的信息打印到 stdout
// 如果发生错误,则简单的返回错误
// 此外重试逻辑不应该成为业务逻辑的一部分
func (c *Controller) syncToStdout(key string) error {
// 从本地存储中获取key对应的对象
obj, exists, err := c.indexer.GetByKey(key)
if err != nil {
klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
return err
}
if !exists {
fmt.Printf("Pod %s does not exists anymore\n", key)
} else {
fmt.Printf("Sync/Add/Update for Pod %s\n", obj.(*v1.Pod).GetName())
}
return nil
} // 检查是否发生错误,并确保我们稍后重试
func (c *Controller) handlerErr(err error, key interface{}) {
if err == nil {
// 忘记每次成功同步时 key 的#AddRateLimited历史记录。
// 这样可以确保不会因过时的错误历史记录而延迟此key更新的以后处理
c.queue.Forget(key)
return
} // 如果出现问题,此控制器将重试5次
if c.queue.NumRequeues(key) < 5 {
// 将 key 重新加入到限速队列中
// 根据队列上的速率限制器和重新入队的历史记录,稍后将再次处理该key
c.queue.AddRateLimited(key)
return
} c.queue.Forget(key)
// 多次重试,我们也无法成功处理该key
runtime.HandleError(err)
klog.Infof("Dropping pod %q out of the queue: %v", key, err)
} func (c *Controller) Run(thread int, stopCh chan struct{}) {
defer runtime.HandleCrash() // 停止控制器后关闭队列
defer c.queue.ShutDown() klog.Info("Starting Pod controller")
// 启动
go c.informer.Run(stopCh) // 等待所有相关的缓存同步,然后再开始处理队列中的项目
if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
return
} for i := 0; i < thread; i++ {
go wait.Until(c.runWorker, time.Second, stopCh)
} <-stopCh
klog.Info("Stopping Pod controller")
} func (c *Controller) runWorker() {
for c.processNexItem() { }
} func initClient() (*kubernetes.Clientset, error) {
var err error
var config *rest.Config
var kubeconfig *string if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(可选) kubeconfig 文件的绝对路径")
} else {
kubeconfig = flag.String("kubeconfig", "", "kubeconfig 文件的绝对路径")
}
flag.Parse() // 首先使用 inCluster 模式(需要去配置对应的RBAC权限,默认的sa是default->是没有获取deploy的list权限)
if config, err = rest.InClusterConfig(); err != nil {
// 使用 kubeConfig 文件创建汲取配置 Config 对象
if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {
panic(err.Error())
}
} // 通过 rest.Config 对象 创建 Clientset 对象
return kubernetes.NewForConfig(config)
} func main() {
clientset, err := initClient()
if err != nil {
klog.Fatal(err)
} // 创建 Pod ListWatcher
podListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything()) // 创建队列
queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) // 在 informer 的帮助下,将工作队列绑定到缓存
// 这样,我们确保无论何时更新缓存,都将 pod key 添加到工作队列中
// 注意: 当我们最终从工作队列中处理元素时,我们可能会看到Pod的版本比响应触发更新的版本新
indexer, informer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
key, err := cache.MetaNamespaceKeyFunc(newObj)
if err == nil {
queue.Add(key)
}
},
DeleteFunc: func(obj interface{}) {
key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err == nil {
queue.Add(key)
}
},
}, cache.Indexers{}) controller := NewController(queue, indexer, informer) err = indexer.Add(&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "mypod",
Namespace: v1.NamespaceDefault,
},
})
if err != nil {
panic(err)
} // start controller
stopCh := make(chan struct{})
defer close(stopCh)
go controller.Run(1, stopCh)
select {}
} ======
程序运行结果:
// 如果default有pod,则会输出
Sync/Add/Update for Pod pod名
// 接着会输出
Pod default/mypod does not exists anymore

controller的简单介绍的更多相关文章

  1. 关于如何在其他包中写controller和简单介绍@SpringBootApplication

    本文参考博客:https://blog.csdn.net/u013473691/article/details/52353923 关于@Configuration和@Bean参考博客:https:// ...

  2. SpringMVC总结二:Controller的请求映射方式(RequestMapping)简单介绍

    在SpringMVC总结一:快速入门的基础上简单介绍一下请求映射的方式: 1,标准映射规则 1. @RequestMapping可以设置在类上,也可以设置在方法上 2. 请求的映射规则是:类上的Req ...

  3. yii2的权限管理系统RBAC简单介绍

    这里有几个概念 权限: 指用户是否可以执行哪些操作,如:编辑.发布.查看回帖 角色 比如:VIP用户组, 高级会员组,中级会员组,初级会员组 VIP用户组:发帖.回帖.删帖.浏览权限 高级会员组:发帖 ...

  4. angular1.x的简单介绍(二)

    首先还是要强调一下DI,DI(Denpendency Injection)伸手获得,主要解决模块间的耦合关系.那么模块是又什么组成的呢?在我看来,模块的最小单位是类,多个类的组合就是模块.关于在根模块 ...

  5. 【转载】JMeter学习(一)工具简单介绍

    JMeter学习(一)工具简单介绍 一.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静 ...

  6. salesforce lightning零基础学习(一) lightning简单介绍以及org开启lightning

    lightning对于开发salesforce人员来说并不陌生,即使没有做过lightning开发,这个名字肯定也是耳熟能详.原来的博客基本都是基于classic基于配置以及开发,后期博客会以ligh ...

  7. ASP.NET MVC 简单介绍①

    ASP.NET  MVC 简单介绍① 只做了重要描述,内容出自菜鸟教程网站内容. 目录 1布局 2HTML 帮助器 3.Razor 语法 4.添加样式 5.Layout 6. Controllers ...

  8. LoadRunner简单介绍----性能自动化测试工具

    在做性能测试中,我认为技术可以说是武功心法,工具则是一把利剑,有一把好的利剑可以帮助自己更好的完成性能测试工作.在这里简单介绍一下LoadRunner,带大家一起来认识一下这把尚方宝剑. 一.性能测试 ...

  9. SpringBoot 中常用注解@Controller/@RestController/@RequestMapping介绍

    原文 SpringBoot 中常用注解 @Controller/@RestController/@RequestMapping介绍 @Controller 处理http请求 @Controller / ...

  10. client高性能组件化框架React简单介绍、特点、环境搭建及经常使用语法

    [本文源址:http://blog.csdn.net/q1056843325/article/details/54729657 转载请加入该地址] 明天就是除夕了 预祝大家新春快乐 [ ]~( ̄▽ ̄) ...

随机推荐

  1. composer 2 升级操作

    update composer composer self-update // or sudo composer self-update 回滚到版本1 composer self-update --r ...

  2. JS中数组的操作方法大全

    常见的一些数组操作push . pop.unshift. shift push 语法: array.push(item1, item2, -, itemX) push( )方法:可以将一个或者更多的参 ...

  3. 什么!你还不会写Vue组件,编写《功能级权限》匹配公式组件

    说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发). 该系统文章,我会尽量说的非常详细,做到不管新手.老手都能看懂. 说明:OverallAuth2 ...

  4. 痞子衡嵌入式:记录为i.MXRT1060更换较大容量Flash(IS25LP064A_IS25LP128F)导致二级App异常启动问题解决全过程(上篇)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是为i.MXRT1060更换较大容量Flash导致二级App异常启动问题. 痞子衡最近在支持一个 RT1062 国外客户项目,客户在项目预 ...

  5. C# 中比较实用的关键字,基础高频面试题!

    前言 在C#编程中关键字是构建逻辑和实现功能的基石,它承载着编程语言的语法规则和编程智慧.熟练掌握这些基础高频关键字对提升编程能力和面试表现至关重要,它们是日常开发和解决复杂问题的关键. DotNet ...

  6. bug|Git Hooks pre-commit|git 提交代码报错|error: 'describe' 'it' 'expect' is not defined (no-undef)|pre-commit hook failed (add --no-verify to bypass)|

    前言 今天学习 jest 的 vue-test-utils 的配置及使用. 报错原因为 jest 全局变量 git 提交代码报错,使用除了参考链接里的解决方案,正好复习一下之前学习的 Git Hook ...

  7. elementui|dropdown|下拉菜单作为模态框使用

    elementui|dropdown|下拉菜单作为模态框使用 背景 场景:下拉菜单作为模态框使用: 操作:下拉菜单设置触发条件点击展示/隐藏:trigger="click" 目的: ...

  8. 对于 emlog pro 目前 avatar 头像不显示的问题,暂时使用这个方法解决

    avatar 头像 cdn 不稳定,目前 emlog 官方还没有放出更新包.因此,现在使用 JS 的方式暂时解决. 代码如下 <script> const avaUrl = 'https: ...

  9. 多态的前提--java进阶day02

    1.多态的前提条件 第一点和第二点都很好理解,第三点父类引用指向子类对象是什么意思?以下图进行讲解 我们以前的写法,如下图,叫做子类引用指向子类 那父类引用呢?就是把左边换成父类Animal即可 因为 ...

  10. 不同数据库Oracle、PostgreSQL、Vertical、Mysql常用操作

    不同数据库Oracle.PostgreSQL.Vertical.Mysql常用操作 授权语句用于管理数据库用户的权限,常见的授权语句如下: 1.授权用户对表的SELECT权限 GRANT SELECT ...