【博客686】k8s informer list-watch机制中的re-list与resync

这篇具有很好参考价值的文章主要介绍了【博客686】k8s informer list-watch机制中的re-list与resync。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

k8s informer的re-list与resync

1、informer的list-watch机制

client-go中的reflector模块首先会list apiserver获取某个资源的全量信息,然后根据list到的resourceversion来watch资源的增量信息。且希望使用client-go编写的控制器组件在与apiserver发生连接异常时,尽量的re-watch资源而不是re-list

2、re-list的场景:

场景一:very short watch

reflector与api建立watch连接,但apiserver关闭了连接,则会重新re-list

这意味着 apiserver 接受了监视请求,但立即终止了连接,如果您偶尔看到它,则表明存在暂时性错误,并不值得警惕。如果您反复看到它,则意味着 apiserver(或 etcd)有问题。

I0728 11:32:06.170821 67483 streamwatcher.go:114] Unexpected EOF during watch stream event decoding: unexpected EOF I0728 11:32:06.171062 67483 reflector.go:391] k8s.io/client-go/informers/factory.go:134: Watch close - *v1.Deployment total 0 items received W0728 11:32:06.187394 67483 reflector.go:302] k8s.io/client-go/informers/factory.go:134: watch of *v1.Deployment ended with: very short watch: k8s.io/client-go/informers/factory.go:134: Unexpected watch close - watch lasted less than a second and no items received

场景二:401 Gone

为什么跟etcd不会一直记录历史版本有关:参考:bookmark机制

reflector与api建立watch连接,但是出现watch的相关事件丢失时(etcd不会一直记录历史版本),api返回401 Gone,reflector提示too old resource version并重新re-list

I0728 14:40:58.807670 71423 reflector.go:300] k8s.io/client-go/informers/factory.go:134: watch of *v1.Deployment ended with: too old resource version: 332167941 (332223202) I0728 14:40:59.808153 71423 reflector.go:159] Listing and watching *v1.Deployment from k8s.io/client-go/informers/factory.go:134 I0728 14:41:00.300695 71423 reflector.go:312] reflector list resourceVersion: 332226582

3、resync场景:

// k8s.io/client-go/tools/cache/delta_fifo.go
// 重新同步一次 Indexer 缓存数据到 Delta FIFO 队列中
func (f *DeltaFIFO) Resync() error {
    f.lock.Lock()
    defer f.lock.Unlock()

    if f.knownObjects == nil {
        return nil
    }
    // 遍历 indexer 中的 key,传入 syncKeyLocked 中处理
    keys := f.knownObjects.ListKeys()
    for _, k := range keys {
        if err := f.syncKeyLocked(k); err != nil {
            return err
        }
    }
    return nil
}

func (f *DeltaFIFO) syncKeyLocked(key string) error {
    obj, exists, err := f.knownObjects.GetByKey(key)
    if err != nil {
        klog.Errorf("Unexpected error %v during lookup of key %v, unable to queue object for sync", err, key)
        return nil
    } else if !exists {
        klog.Infof("Key %v does not exist in known objects store, unable to queue object for sync", key)
        return nil
    }
    // 如果发现 FIFO 队列中已经有相同 key 的 event 进来了,说明该资源对象有了新的 event,
    // 在 Indexer 中旧的缓存应该失效,因此不做 Resync 处理直接返回 nil
    id, err := f.KeyOf(obj)
    if err != nil {
        return KeyError{obj, err}
    }
    if len(f.items[id]) > 0 {
        return nil
    }
    // 重新放入 FIFO 队列中
    if err := f.queueActionLocked(Sync, obj); err != nil {
        return fmt.Errorf("couldn't queue object: %v", err)
    }
    return nil
}

为什么需要 Resync 机制呢?因为在处理 SharedInformer 事件回调时,可能存在处理失败的情况,定时的 Resync 让这些处理失败的事件有了重新处理的机会。

那么经过 Resync 重新放入 Delta FIFO 队列的事件,和直接从 apiserver 中 watch 得到的事件处理起来有什么不一样呢?

// k8s.io/client-go/tools/cache/shared_informer.go
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
    s.blockDeltas.Lock()
    defer s.blockDeltas.Unlock()

    // from oldest to newest
    for _, d := range obj.(Deltas) {
        // 判断事件类型,看事件是通过新增、更新、替换、删除还是 Resync 重新同步产生的
        switch d.Type {
        case Sync, Replaced, Added, Updated:
            s.cacheMutationDetector.AddObject(d.Object)
            if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
                if err := s.indexer.Update(d.Object); err != nil {
                    return err
                }
                
                isSync := false
                switch {
                case d.Type == Sync:
                    // 如果是通过 Resync 重新同步得到的事件则做个标记
                    isSync = true
                case d.Type == Replaced:
                    ...
                }
                // 如果是通过 Resync 重新同步得到的事件,则触发 onUpdate 回调
                s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
            } else {
                if err := s.indexer.Add(d.Object); err != nil {
                    return err
                }
                s.processor.distribute(addNotification{newObj: d.Object}, false)
            }
        case Deleted:
            if err := s.indexer.Delete(d.Object); err != nil {
                return err
            }
            s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
        }
    }
    return nil
}

从上面对 Delta FIFO 的队列处理源码可看出,如果是从 Resync 重新同步到 Delta FIFO 队列的事件,会分发到 updateNotification 中触发 onUpdate 的回调

Resync 机制的引入,定时将 Indexer 缓存事件重新同步到 Delta FIFO 队列中,在处理 SharedInformer 事件回调时,让处理失败的事件得到重新处理。并且通过入队前判断 FIFO 队列中是否已经有了更新版本的 event,来决定是否丢弃 Indexer 缓存不进行 Resync 入队。在处理 Delta FIFO 队列中的 Resync 的事件数据时,触发 onUpdate 回调来让事件重新处理。

4、Reflector.lastSyncResourceVersion 是哪个资源的 resourceVersion

一个resourceVersion怎么对应多种资源呢?其实是一个informer对应一个Reflector,一个informer本来就是对应一种资源的,然后每一类resourceVersion是不断递增的,比如:informer watch了pod,那么informer对应的Reflector的resourceVersion是对应k8s etcd里pod这一类资源的resourceVersion。因此是一类资源使用一个resourceVersion

list-watch example:

https://codeburst.io/kubernetes-watches-by-example-bc1edfb2f83

5、注意点

  • 1、resync不是re-list,resync不需要访问apiserver

  • 2、resync 是重放 informer 中的 obj 到 DeltaFIFO 队列中,触发 handler 再次处理 obj。

    目的是防止有些 handler 处理失败了而缺乏重试的机会。特别是,需要修改外部系统的状态的时候,需要做一些补偿的时候。

    比如说,根据 networkpolicy刷新 node 上的 iptables。
    iptables 有可能会被其他进程或者管理员意外修改,有 resync 的话,才有机会定期修正。
    这也说明,回调函数的实现需要保证幂等性。对于 OnUpdate 函数而言,有可能会拿到完全一样的两个 Obj,实现 OnUpdate 时要考虑到。

  • 3、re-list 是指 reflector 重新调用 kube-apiserver 全量同步所有 obj。但目前(v1.20)没有显式配置 re-list 周期的参数。

    list 的时机一般是在程序第一次启动,或者 watch 有错误,才会 re-list。

  • 4、resync 是一个水平触发的模式

    水平触发是只要处于某个状态,就会一直通知。比如在这里,对象已经在缓存里,会触发不止一次回调函数。

  • 5、process 函数怎么区分从 DeltaFIFO 里拿到的 obj 是新的还是重放的呢?

    根据 obj 的 key(namespace/name)从 index 里拿到旧的 obj,和新出队的 obj 比较 resource revision,这两个 resource revision 如果一样,就是重放的,如果不一样,就是从 kube-apiserver 拿到的新的。因为 resource revision 只有在 etcd 才能更新。 index 作为客户端缓存,这个值是不变的。

  • 6、sharedInformer 如何实现 Resync

    把需要 resync(resync 的周期到了) 的 listeners 数组复制一份到 syncingListeners,在 distribute 中,
    会调用 syncingListeners 的 add 函数,触发 syncingListeners 上的回调函数

6、resync要注意的问题:

  • 1、如何配置resync的周期?

    func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers)

    第三个参数 defaultEventHandlerResyncPeriod 就指定多久 resync 一次。如果为0,则不 resync。

    AddEventHandlerWithResyncPeriod也可以给单独的 handler 定义 resync period,否则默认和 informer 的是一样的。

  • 2、配置resync周期间隔太小会有什么问题

    此时会以比较高的频率促使事件重新入队进行reconcile,造成controller的压力过大

  • 3、resync用于解决什么问题,resync 多久一次比较合适?或者需不需要 resync?

    根据具体业务场景来,根据外部状态是不是稳定的、是否需要做这个补偿来决定的,举例:假设controller是一个LB controller

    当watch到了service创建,然后调用LB api去创建一个对应的LB,然后如果此时这个对应的LB由于某种bug被删除了,此时service就不通了,
    那么此时状态不一致了,集群里有这个service,LB那边没有对应的LB,并且由于bug被删除了,而不是删除service而触发LB删除的,此时service是没有变化的,
    也就不会出发reconcile了。假设我们reconcile里有逻辑是判断如果service没有对应的LB就创建,那么此时reconcile不会被出发,那也就没有被执行了。
    此时如果有resync,定时将indexer里的对象,也就是缓存的对象来一次update事件的入队,进行后续出队触发reconcile,那我们就会发现service对应的LB没了,
    进而进行创建。也就是本质上resync是防止业务层的bug。且resync将indexer的对象重入队,里面的service不是所有service,而是创建了LB的service,因为我们
    只会watch我们关心的资源,前提是代码里添加的对象写的是有LB字段的service

    因为这些操作都是异步的 合理的sync可以提高事件消费的容错性。Resync 机制的引入,定时将 Indexer 缓存事件重新同步到 Delta FIFO 队列中,在处理 SharedInformer 事件回调时,让处理失败的事件得到重新处理。并且通过入队前判断 FIFO 队列中是否已经有了更新版本的 event,来决定是否丢弃 Indexer 缓存不进行 Resync 入队。在处理 Delta FIFO 队列中的 Resync 的事件数据时,触发 onUpdate 回调来让事件重新处理。

7、这个issue讨论里面有Programming Kubernetes相关讨论内容:

https://github.com/cloudnativeto/sig-kubernetes/issues/11文章来源地址https://www.toymoban.com/news/detail-628093.html

到了这里,关于【博客686】k8s informer list-watch机制中的re-list与resync的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【博客683】k8s list请求优化以及合理使用list以维护集群稳定性

    1、LIST apis/cilium.io/v2/ciliumendpoints?limit=500resourceVersion = 0 这里同时传了两个参数,但 resourceVersion=0 会导致 apiserver 忽略 limit=500, 所以客户端拿到的是全量 ciliumendpoints 数据 此时不会查etcd,因为有resourceVersion = 0,且resourceVersion = 0会忽略limit,因为limit一定要查etcd 一种资源的全

    2024年02月15日
    浏览(43)
  • 【List-Watch】

    Kubernetes 是通过 List-Watch 的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面建立 Pod 和 Container。 APIServer 经过 API 调用,权限控制,调用资源和存储资源的过程,实际上还没

    2024年02月07日
    浏览(33)
  • 【博客682】k8s apiserver bookmarks机制以更高效检测变更

    List-Watch 是kubernetes中server和client通信的最核心的机制, 比如说api-server监听etcd, kubelet监听api-server, scheduler监听api-server等等,其实其他模块监听api-server相当于监听etcd,因为在k8s的设计中,只有api-server能跟etcd通信,其他模块需要etcd的数据就只好监听api-server了。 etcd默认保留

    2024年02月15日
    浏览(45)
  • list-watch集群调度

    调度约束 Kubernetes 是通过 List-Watch    **** 的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面建立 Pod 和 Container。 APIServer 经过 API 调用,权限控制,调用资源和存储资源的

    2024年02月06日
    浏览(33)
  • 解决k8s node节点报错: Failed to watch *v1.Secret: unknown

    现象:  这个现象是发生在k8s集群证书过期,重新续签证书以后。 记得master节点的/etc/kubernetes/kubelet.conf文件已经复制到node节点了。 但是为什么还是报这个错,然后运行证书检查命令看一下:   看样子是差/etc/kubernetes/pki/apiserver.crt文件。 但是从master节点scpapiserver.crt文件以

    2024年01月16日
    浏览(49)
  • Kubernetes(K8S)快速搭建typecho个人博客

    Kubernetes(K8S)快速搭建typecho个人博客 K8S集群环境,搭建教程参考腾讯云Lighthouse组建跨地域Kubernetes集群 K8S集群面板,搭建教程参考Kubernetes集群管理面板的安装及使用 - 青阳のblog-一个计算机爱好者的个人博客 (hipyt.cn) 如果没有集群或者服务器不够可以通过传送门新购。 腾讯

    2024年02月04日
    浏览(59)
  • k8s 安全机制

    安全机制 //机制说明 Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介, 也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。 比如 kubectl 如果想向 API Server 请求资源,

    2024年02月17日
    浏览(42)
  • Kubeadm - K8S1.20 - 高可用集群部署(博客)

    1.系统设置 注意事项: master节点cpu核心数要求大于2 ●最新的版本不一定好,但相对于旧版本,核心功能稳定,但新增功能、接口相对不稳 ●学会一个版本的 高可用部署,其他版本操作都差不多 ●宿主机尽量升级到CentOS 7.9 ●内核kernel升级到 4.19+ 这种稳定的内核 ●部署k8

    2024年02月05日
    浏览(43)
  • k8s 操作命令(合集List)

    一、K8S最常用命令如下: 1、获取pod信息:kubectl get pod 2、查看指定pod的日志信息:kubectl logs -f --tail(最后多少行) 500 podName(pod名) 3、查看pod的描述信息:kubectl describe pod podName 4、查看节点信息:kubectl get nodes 5、查看pod的详细信息,以yaml或者json格式展示:kubectl get pods -o yaml、

    2024年02月12日
    浏览(37)
  • k8s 安全机制详解

    目录 一、安全机制 三个主要安全机制 认证(Authentication): 鉴权(Authorization): 准入控制(Admission Control): 二、 认证 认证方式 对比总结 认证机制框架和相关组件 Service Account 访问权限机制 Service Account 通常包含以下三个部分: 当 Pod 在 Kubernetes 集群中启动时, 三、 鉴

    2024年03月15日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包