日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

Kubernetes Informer基本原理,你明白了嗎?

來源: 責(zé)編: 時間:2024-02-01 12:51:38 278觀看
導(dǎo)讀本文分析 k8s controller 中 informer 啟動的基本流程不論是 k8s 自身組件,還是自己編寫 controller,都需要通過 apiserver 監(jiān)聽 etcd 事件來完成自己的控制循環(huán)邏輯。如何高效可靠進(jìn)行事件監(jiān)聽,k8s 客戶端工具包 client

本文分析 k8s controller 中 informer 啟動的基本流程Baf28資訊網(wǎng)——每日最新資訊28at.com

不論是 k8s 自身組件,還是自己編寫 controller,都需要通過 apiserver 監(jiān)聽 etcd 事件來完成自己的控制循環(huán)邏輯。Baf28資訊網(wǎng)——每日最新資訊28at.com

如何高效可靠進(jìn)行事件監(jiān)聽,k8s 客戶端工具包 client-go 提供了一個通用的 informer 包,通過 informer,可以方便和高效的進(jìn)行 controller 開發(fā)。Baf28資訊網(wǎng)——每日最新資訊28at.com

informer 包提供了如下的一些功能:Baf28資訊網(wǎng)——每日最新資訊28at.com

1、本地緩存(store)Baf28資訊網(wǎng)——每日最新資訊28at.com

2、索引機制(indexer)Baf28資訊網(wǎng)——每日最新資訊28at.com

3、Handler 注冊功能(eventHandler)Baf28資訊網(wǎng)——每日最新資訊28at.com

1、informer 架構(gòu)

整個 informer 機制架構(gòu)如下圖(圖片源自 Client-go):Baf28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片Baf28資訊網(wǎng)——每日最新資訊28at.com

可以看到這張圖分為上下兩個部分,上半部分由 client-go 提供,下半部分則是需要自己實現(xiàn)的控制循環(huán)邏輯Baf28資訊網(wǎng)——每日最新資訊28at.com

本文主要分析上半部分的邏輯,包括下面幾個組件:Baf28資訊網(wǎng)——每日最新資訊28at.com

1.1、Reflector:

從圖上可以看到 Reflector 是一個和 apiserver 交互的組件,通過 list 和 watch api 將資源對象壓入隊列Baf28資訊網(wǎng)——每日最新資訊28at.com

1.2、DeltaFifo:

DeltaFifo的結(jié)構(gòu)體示意如下:Baf28資訊網(wǎng)——每日最新資訊28at.com

type DeltaFIFO struct {  ...  // We depend on the property that items in the s    et are in  // the queue and vice versa, and that all Deltas in this  // map have at least one Delta.  items map[string]Deltas  queue []string  ...}

主要分為兩部分,fifo 和 deltaBaf28資訊網(wǎng)——每日最新資訊28at.com

(1)fifo:先進(jìn)先出隊列Baf28資訊網(wǎng)——每日最新資訊28at.com

對應(yīng)結(jié)構(gòu)體中的 queue,結(jié)構(gòu)體示例如下:Baf28資訊網(wǎng)——每日最新資訊28at.com

[default/centos-fd77b5886-pfrgn, xxx, xxx]

(2)delta:對應(yīng)結(jié)構(gòu)體中的items,存儲了資源對象并且攜帶了資源操作類型的一個 map,結(jié)構(gòu)體示例如下:Baf28資訊網(wǎng)——每日最新資訊28at.com

map:{"default/centos-fd77b5886-pfrgn":[{Replaced &Pod{ObjectMeta: ${pod參數(shù)}], "xxx": [{},{}]}

消費者從 queue 中 pop 出對象進(jìn)行消費,并從 items 獲取具體的消費操作(執(zhí)行動作 Update/Deleted/Sync,和執(zhí)行的對象 object spec)Baf28資訊網(wǎng)——每日最新資訊28at.com

1.3、Indexer:

client-go 用來存儲資源對象并自帶索引功能的本地存儲,deltaFIFO 中 pop 出的對象將存儲到 Indexer。Baf28資訊網(wǎng)——每日最新資訊28at.com

indexer 與 etcd 集群中的數(shù)據(jù)保持一致,從而 client-go 可以直接從本地緩存獲取資源對象,減少 apiserver 和 etcd 集群的壓力。Baf28資訊網(wǎng)——每日最新資訊28at.com

2、一個基本例子

func main() {  stopCh := make(chan struct{})  defer close(stopCh)    // (1)New a k8s clientset  masterUrl := "172.27.32.110:8080"  config, err := clientcmd.BuildConfigFromFlags(masterUrl, "")  if err != nil {    klog.Errorf("BuildConfigFromFlags err, err: %v", err)  }    clientset, err := k.NewForConfig(config)  if err != nil {    klog.Errorf("Get clientset err, err: %v", err)  }    // (2)New a sharedInformers factory  sharedInformers := informers.NewSharedInformerFactory(clientset, defaultResync)      // (3)Register a informer  //  f.informers[informerType] = informer,  //  the detail for informer is build in NewFilteredPodInformer()  podInformer := sharedInformers.Core().V1().Pods().Informer()    // (4)Register event handler  podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{      AddFunc: func(obj interface{}) {        mObj := obj.(v1.Object)        klog.Infof("Get new obj: %v", mObj)        klog.Infof("Get new obj name: %s", mObj.GetName())      },  })    // (5)Start all informers  sharedInformers.Start(stopCh)    // (6)A cronjob for cache sync  if !cache.WaitForCacheSync(stopCh, podInformer.HasSynced) {    klog.Infof("Cache sync fail!")  }    // (7)Use lister  podLister := sharedInformers.Core().V1().Pods().Lister()  pods, err := podLister.List(labels.Everything())  if err != nil {    klog.Infof("err: %v", err)  }  klog.Infof("len(pods), %d", len(pods))  for _, v := range pods {    klog.Infof("pod: %s", v.Name)  }    <- stopChan}

上面就是一個簡單的 informer 的使用例子,整個過程如上述幾個步驟,著重說一下(2)、(3)、(4)、(5)四個步驟Baf28資訊網(wǎng)——每日最新資訊28at.com

3、流程分析

3.1、New a sharedInformers factory

sharedInformers := informers.NewSharedInformerFactory(clientset, defaultResync)factory := &sharedInformerFactory{  client:           client,  namespace:        v1.NamespaceAll,  defaultResync:    defaultResync,  informers:        make(map[reflect.Type]cache.SharedIndexInformer),  startedInformers: make(map[reflect.Type]bool),  customResync:     make(map[reflect.Type]time.Duration),}

這個過程就是創(chuàng)建一個 informer 的工廠 sharedInformerFactory,sharedInformerFactory 中有一個 informers 對象,里面是一個 informer 的 map,sharedInformerFactory 是為了防止過多的重復(fù) informer 監(jiān)聽 apiserver,導(dǎo)致 apiserver 壓力過大,在同一個服務(wù)中,不同的 controller 使用同一個 informerBaf28資訊網(wǎng)——每日最新資訊28at.com

3.2、Register a informer

這個過程主要是生成和注冊 informer 到 sharedInformerFactoryBaf28資訊網(wǎng)——每日最新資訊28at.com

podInformer := sharedInformers.Core().V1().Pods().Informer()func (f *podInformer) Informer() cache.SharedIndexInformer {  return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)}### f.factory.InformerFor:### 注冊 informer func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {  ...  informer = newFunc(f.client, resyncPeriod)  f.informers[informerType] = informer  return informer}### f.defaultInformer:### 生成 informerfunc (f *podInformer) defaultInformer(client k.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {  return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)}func NewFilteredPodInformer(client k.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {  return cache.NewSharedIndexInformer(    &cache.ListWatch{    ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {      if tweakListOptions != nil {        tweakListOptions(&options)      }      return client.CoreV1().Pods(namespace).List(context.TODO(), options)    },    WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {      if tweakListOptions != nil {        tweakListOptions(&options)      }      return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)    },    },    &corev1.Pod{},    resyncPeriod,    indexers,  )}### cache.NewSharedIndexInformer:func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {  realClock := &clock.RealClock{}  sharedIndexInformer := &sharedIndexInformer{    processor:                       &sharedProcessor{clock: realClock},    indexer:                         NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers),    listerWatcher:                   lw,    objectType:                      exampleObject,    resyncCheckPeriod:               defaultEventHandlerResyncPeriod,    defaultEventHandlerResyncPeriod: defaultEventHandlerResyncPeriod,    cacheMutationDetector:           NewCacheMutationDetector(fmt.Sprintf("%T", exampleObject)),    clock:                           realClock,  }  return sharedIndexInformer}

首先通過 f.defaultInformer 方法生成 informer,然后通過 f.factory.InformerFor 方法,將 informer 注冊到 sharedInformerFactoryBaf28資訊網(wǎng)——每日最新資訊28at.com

3.3、Register event handler

這個過程展示如何注冊一個回調(diào)函數(shù),以及如何觸發(fā)這個回調(diào)函數(shù)Baf28資訊網(wǎng)——每日最新資訊28at.com

### podInformer.AddEventHandler:func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) {  s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)}func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {  ...  listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(),  initialBufferSize)  if !s.started {    s.processor.addListener(listener)    return  }  ...}### s.processor.addListener(listener):func (p *sharedProcessor) addListener(listener *processorListener) {  p.addListenerLocked(listener)  if p.listenersStarted {    p.wg.Start(listener.run)    p.wg.Start(listener.pop)  }}### listener.run:func (p *processorListener) run() {  // this call blocks until the channel is closed.  When a panic happens during the notification  // we will catch it, **the offending item will be skipped!**, and after a short delay (one second)  // the next notification will be attempted.  This is usually better than the alternative of never  // delivering again.  stopCh := make(chan struct{})  wait.Until(func() {    for next := range p.nextCh {      switch notification := next.(type) {        // 通過next結(jié)構(gòu)體本身的類型來判斷事件類型      case updateNotification:        p.handler.OnUpdate(notification.oldObj, notification.newObj)      case addNotification:        p.handler.OnAdd(notification.newObj)      case deleteNotification:        p.handler.OnDelete(notification.oldObj)      default:        utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))      }    }    // the only way to get here is if the p.nextCh is empty and closed    close(stopCh)  }, 1*time.Second, stopCh)}### listener.pop:func (p *processorListener) pop() {  var nextCh chan<- interface{}  var notification interface{}  for {    select {    case nextCh <- notification:      // Notification dispatched      var ok bool      notification, ok = p.pendingNotifications.ReadOne()      if !ok { // Nothing to pop        nextCh = nil // Disable this select case      }    case notificationToAdd, ok := <-p.addCh:      if !ok {        return      }      if notification == nil { // No notification to pop (and pendingNotifications is empty)        // Optimize the case - skip adding to pendingNotifications        notification = notificationToAdd        nextCh = p.nextCh      } else { // There is already a notification waiting to be dispatched        p.pendingNotifications.WriteOne(notificationToAdd)      }    }  }}

這個過程總結(jié)就是:Baf28資訊網(wǎng)——每日最新資訊28at.com

(1)AddEventHandler 到 sharedProcessor,注冊事件回調(diào)函數(shù)到 sharedProcessorBaf28資訊網(wǎng)——每日最新資訊28at.com

(2)listener pop 方法里會監(jiān)聽 p.addCh,通過 nextCh = p.nextCh 將 addCh 將事件傳遞給 p.nextChBaf28資訊網(wǎng)——每日最新資訊28at.com

(3)listener run 方法里會監(jiān)聽 p.nextCh,收到信號之后,判斷是屬于什么類型的方法,并且執(zhí)行前面注冊的 HandlerBaf28資訊網(wǎng)——每日最新資訊28at.com

所以后面需要關(guān)注當(dāng)資源對象發(fā)生變更時,是如何將變更信號給 p.addCh,進(jìn)一步觸發(fā)回調(diào)函數(shù)的Baf28資訊網(wǎng)——每日最新資訊28at.com

3.4、Start all informers

通過 sharedInformers.Start(stopCh)啟動所有的 informer,代碼如下:Baf28資訊網(wǎng)——每日最新資訊28at.com

// Start initializes all requested informers.func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {  for informerType, informer := range f.informers {    if !f.startedInformers[informerType] {      go informer.Run(stopCh)      f.startedInformers[informerType] = true    }  }}

我們的例子中其實就只啟動了 PodInformer,接下來看到 podInformer 的 Run 方法做了什么Baf28資訊網(wǎng)——每日最新資訊28at.com

### go informer.Run(stopCh):func (s *sharedIndexInformer) Run(stopCh <-chan struct{}){  defer utilruntime.HandleCrash()  fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{   // Deltafifo    KnownObjects:          s.indexer,    EmitDeltaTypeReplaced: true,  })  cfg := &Config{    Queue:            fifo,         // Deltafifo    ListerWatcher:    s.listerWatcher,  // listerWatcher    ObjectType:       s.objectType,    FullResyncPeriod: s.resyncCheckPeriod,    RetryOnError:     false,    ShouldResync:     s.processor.shouldResync,    // HandleDeltas, added to process, and done in processloop    Process:           s.HandleDeltas,    WatchErrorHandler: s.watchErrorHandler,  }  func() {    ...    s.controller = New(cfg)    ...  }    s.controller.Run(stopCh)}### s.controller.Run(stopCh)func (c *controller) Run(stopCh <-chan struct{}) {  r := NewReflector(    c.config.ListerWatcher,    c.config.ObjectType,    c.config.Queue,    c.config.FullResyncPeriod,  )  c.reflector = r  // Run reflector  wg.StartWithChannel(stopCh, r.Run)    // Run processLoop, pop from deltafifo and do ProcessFunc,  // ProcessFunc is the s.HandleDeltas before  wait.Until(c.processLoop, time.Second, stopCh)}

可以看到上面的邏輯首先生成一個 DeltaFifo,然后接下來的邏輯分為兩塊,生產(chǎn)和消費:Baf28資訊網(wǎng)——每日最新資訊28at.com

(1)生產(chǎn)—r.Run:

主要的邏輯就是利用 list and watch 將資源對象包括操作類型壓入隊列 DeltaFifoBaf28資訊網(wǎng)——每日最新資訊28at.com

#### r.Run:func (r *Reflector) Run(stopCh <-chan struct{}) {// 執(zhí)行l(wèi)istAndWatchif err := r.ListAndWatch(stopCh);}// 執(zhí)行ListAndWatch流程func (r *Reflector)ListAndWatch(stopCh <-chan struct{}) error{  // 1、list:  // (1)、list pods, 實際調(diào)用的是podInformer里的ListFunc方法,  // client.CoreV1().Pods(namespace).List(context.TODO(), options)    r.listerWatcher.List(opts)  // (2)、獲取資源版本號,用于watch  resourceVersion = listMetaInterface.GetResourceVersion()    //  (3)、數(shù)據(jù)轉(zhuǎn)換,轉(zhuǎn)換成列表  items, err := meta.ExtractList(list)    // (4)、將資源列表中的資源對象和版本號存儲到DeltaFifo中  r.syncWith(items, resourceVersion);    // 2、watch,無限循環(huán)去watch apiserver,當(dāng)watch到事件的時候,執(zhí)行watchHandler將event事件壓入fifo  for {    // (1)、watch pods, 實際調(diào)用的是podInformer里的WatchFunc方法,    // client.CoreV1().Pods(namespace).Watch(context.TODO(), options)    w, err := r.listerWatcher.Watch(options)        // (2)、watchHandler    // watchHandler watches pod,更新DeltaFifo信息,并且更新resourceVersion    if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh);  }}### r.watchHandler// watchHandler watches w and keeps *resourceVersion up to date.func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {    ...loop:  for {    select {    case event, ok := <-w.ResultChan():      newResourceVersion := meta.GetResourceVersion()      switch event.Type {      case watch.Added:        err := r.store.Add(event.Object)    // Add event to srore, store的具體方法在fifo中        if err != nil {            utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))        }      ...      }      *resourceVersion = newResourceVersion      r.setLastSyncResourceVersion(newResourceVersion)      eventCount++    }  }  ...}### r.store.Add:## 即為deltaFifo的add方法:func (f *DeltaFIFO) Add(obj interface{}) error {  ...  return f.queueActionLocked(Added, obj)  ...}func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {  id, err := f.KeyOf(obj)  if err != nil {    return KeyError{obj, err}  }  newDeltas := append(f.items[id], Delta{actionType, obj})  newDeltas = dedupDeltas(newDeltas)  if len(newDeltas) > 0 {    if _, exists := f.items[id]; !exists {      f.queue = append(f.queue, id)    }    f.items[id] = newDeltas    f.cond.Broadcast()          // 通知所有阻塞住的消費者  }  ...  return nil}
(2)消費—c.processLoop:

消費邏輯就是從 DeltaFifo pop 出對象,然后做兩件事情:(1)觸發(fā)前面注冊的 eventhandler (2)更新本地索引緩存 indexer,保持?jǐn)?shù)據(jù)和 etcd 一致Baf28資訊網(wǎng)——每日最新資訊28at.com

func (c *controller) processLoop() {  for {    obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))  }}### Queue.Pop:## Queue.Pop是一個帶有處理函數(shù)的pod方法,首先先看Pod邏輯,即為deltaFifo的pop方法:func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {  for {                       // 無限循環(huán)    for len(f.queue) == 0 {      f.cond.Wait()       // 阻塞直到生產(chǎn)端broadcast方法通知    }    id := f.queue[0]    item, ok := f.items[id]    delete(f.items, id)    err := process(item)        // 執(zhí)行處理方法    if e, ok := err.(ErrRequeue); ok {      f.addIfNotPresent(id, item)     // 如果處理失敗的重新加入到fifo中重新處理      err = e.Err    }    return item, err  }}### c.config.Process:## c.config.Process是在初始化controller的時候賦值的,即為前面的s.HandleDeltas### s.HandleDeltas:func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {  s.blockDeltas.Lock()  defer s.blockDeltas.Unlock()  // from oldest to newest  for _, d := range obj.(Deltas) {    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:            // Sync events are only propagated to listeners that requested resync            isSync = true          case d.Type == Replaced:            if accessor, err := meta.Accessor(d.Object); err == nil {                if oldAccessor, err := meta.Accessor(old); err == nil {                  // Replaced events that didn't change resourceVersion are treated as resync events                  // and only propagated to listeners that requested resync                  isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()                }            }          }          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}

可以看到上面主要執(zhí)行兩部分邏輯:Baf28資訊網(wǎng)——每日最新資訊28at.com

s.processor.distribute
#### s.processor.distribute:### 例如新增通知:s.processor.distribute(addNotification{newObj: d.Object}, false)### 其中addNotification就是add類型的通知,后面會通過notification結(jié)構(gòu)體的類型來執(zhí)行不同的eventHandlerfunc (p *sharedProcessor) distribute(obj interface{}, sync bool) {  p.listenersLock.RLock()  defer p.listenersLock.RUnlock()    if sync {    for _, listener := range p.syncingListeners {      listener.add(obj)    }  } else {    for _, listener := range p.listeners {      listener.add(obj)    }  }}func (p *processorListener) add(notification interface{}) {  p.addCh <- notification     // 新增notification到addCh}

這里 p.addCh 對應(yīng)到前面說的關(guān)注對象 p.addCh,processorListener 收到 addCh 信號之后傳遞給 nextCh,然后通過 notification 結(jié)構(gòu)體的類型來執(zhí)行不同的 eventHandlerBaf28資訊網(wǎng)——每日最新資訊28at.com

s.indexer 的增刪改:

這個就是本地數(shù)據(jù)的緩存和索引,自定義控制邏輯里面會通過 indexer 獲取操作對象的具體參數(shù),這里就不展開細(xì)講了。Baf28資訊網(wǎng)——每日最新資訊28at.com

4、總結(jié)

至此一個 informer 的 client-go 部分的流程就走完了,可以看到啟動 informer 主要流程就是:Baf28資訊網(wǎng)——每日最新資訊28at.com

1、Reflector ListAndWatch:Baf28資訊網(wǎng)——每日最新資訊28at.com

(1)通過一個 reflector run 起來一個帶有 list 和 watch api 的 clientBaf28資訊網(wǎng)——每日最新資訊28at.com

(2)list 到的 pod 列表通過 DeltaFifo 存儲,并更新最新的 ResourceVersionBaf28資訊網(wǎng)——每日最新資訊28at.com

(3)繼續(xù)監(jiān)聽 pod,監(jiān)聽到的 pod 操作事件繼續(xù)存儲到 DeltaFifo 中Baf28資訊網(wǎng)——每日最新資訊28at.com

2、DeltaFifo 生產(chǎn)和消費:Baf28資訊網(wǎng)——每日最新資訊28at.com

(1)生產(chǎn):list and watch 到的事件生產(chǎn)壓入隊列 DeltaFifoBaf28資訊網(wǎng)——每日最新資訊28at.com

(2)消費:執(zhí)行注冊的 eventHandler,并更新本地 indexerBaf28資訊網(wǎng)——每日最新資訊28at.com

所以 informer 本質(zhì)其實就是一個通過 deltaFifo 建立生產(chǎn)消費機制,并且?guī)в斜镜鼐彺婧退饕约翱梢宰曰卣{(diào)事件的 apiServer 的客戶端庫。Baf28資訊網(wǎng)——每日最新資訊28at.com

5、參考

  • https://github.com/kubernetes/sample-controller/tree/master
  • https://jimmysong.io/kubernetes-handbook/develop/client-go-informer-sourcecode-analyse.html

本文鏈接:http://m.www897cc.com/showinfo-26-70458-0.htmlKubernetes Informer基本原理,你明白了嗎?

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 太強了!CSS 文字效果還能這樣玩

下一篇: 我們一起聊聊容器資源自愈

標(biāo)簽:
  • 熱門焦點
  • 鴻蒙OS 4.0公測機型公布:甚至連nova6都支持

    華為全新的HarmonyOS 4.0操作系統(tǒng)將于今天下午正式登場,官方在發(fā)布會之前也已經(jīng)正式給出了可升級的機型產(chǎn)品,這意味著這些機型會率先支持升級享用。這次的HarmonyOS 4.0支持
  • Redmi Buds 4開箱簡評:才199還有降噪 可以無腦入

    在上個月舉辦的Redmi Note11T Pro系列新機發(fā)布會上,除了兩款手機新品之外,Redmi還帶來了兩款TWS真無線藍(lán)牙耳機產(chǎn)品,Redmi Buds 4和Redmi Buds 4 Pro,此前我們在Redmi Note11T
  • 6月安卓手機好評榜:魅族20 Pro蟬聯(lián)冠軍

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數(shù)據(jù)來源安兔兔評測,收集時間2023年6月1日至6月30日,僅限國內(nèi)市場。第一名:魅族20 Pro好評率:95%5月份的時候魅族20 Pro就是
  • 三言兩語說透柯里化和反柯里化

    JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是兩種很有用的技術(shù),可以幫助我們寫出更加優(yōu)雅、泛用的函數(shù)。本文將首先介紹柯里化和反柯里化的概念、實現(xiàn)原理和應(yīng)用
  • 共享單車的故事講到哪了?

    來源丨海克財經(jīng)與共享充電寶相差不多,共享單車已很久沒有被國內(nèi)熱點新聞關(guān)照到了。除了一再漲價和用戶直呼用不起了。近日多家媒體再發(fā)報道稱,成都、天津、鄭州等地多個共享單
  • 華為Mate 60系列用上可變靈動島:正式版體驗將會更出色

    這段時間以來,關(guān)于華為新旗艦的爆料日漸密集。據(jù)此前多方爆料,今年華為將開始恢復(fù)一年雙旗艦戰(zhàn)略,除上半年推出的P60系列外,往年下半年的Mate系列也將
  • iQOO Neo8 Pro搶先上架:首發(fā)天璣9200+ 安卓性能之王

    經(jīng)過了一段時間的密集爆料,昨日iQOO官方如期對外宣布:將于5月23日推出全新的iQOO Neo8系列新品,官方稱這是一款擁有旗艦級性能調(diào)校的作品。隨著發(fā)布時
  • iQOO Neo8 Pro真機諜照曝光:天璣9200+和V1+旗艦雙芯加持

    去年10月,iQOO推出了iQOO Neo7系列機型,不僅搭載了天璣9000+,而且是同價位唯一一款天璣9000+直屏旗艦,一經(jīng)上市便受到了用戶的廣泛關(guān)注。在時隔半年后,
  • 朋友圈可以修改可見范圍了 蘋果用戶可率先體驗

    近日,iOS用戶迎來微信8.0.27正式版更新,除了可更換二維碼背景外,還新增了多項實用功能。在新版微信中,朋友圈終于可以修改可見范圍,簡單來說就是已發(fā)布的朋友圈
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
999亚洲国产精| 影音欧美亚洲| 欧美性一区二区| 国产精品久久久久久久7电影| 国产精品毛片va一区二区三区| 国产老肥熟一区二区三区| 国内成人精品一区| 亚洲日本欧美| 午夜视黄欧洲亚洲| 免费av成人在线| 欧美日韩久久久久久| 国产欧美一区二区三区久久 | 尤物99国产成人精品视频| 亚洲三级视频| 欧美一级欧美一级在线播放| 久久综合五月| 欧美偷拍另类| 精品91免费| 亚洲视频免费观看| 久久香蕉国产线看观看av| 欧美特黄视频| 亚洲电影在线看| 亚洲综合电影一区二区三区| 久久资源在线| 国产精品女人毛片| 亚洲欧洲一区二区天堂久久 | 国产在线观看一区| 99国产精品国产精品久久| 欧美中文在线字幕| 欧美日韩高清在线播放| 国产亚洲一区在线播放| 99riav久久精品riav| 久久精品日韩欧美| 欧美视频在线免费看| 在线观看一区二区视频| 亚洲一区二区三区高清| 男女激情久久| 国产一区二区三区视频在线观看| 亚洲精品国产精品乱码不99| 久久国产99| 国产精品美女午夜av| 日韩视频中午一区| 久久青青草原一区二区| 国产精品一区二区黑丝| 一区二区电影免费观看| 欧美/亚洲一区| 国产一区深夜福利| 亚洲一区二区三区四区视频| 欧美激情精品久久久久久免费印度| 国产亚洲欧洲997久久综合| 亚洲亚洲精品三区日韩精品在线视频| 蜜桃av久久久亚洲精品| 国产亚洲免费的视频看| 亚洲在线播放| 欧美日韩国产精品一区二区亚洲| 在线播放不卡| 久久久久中文| 国产一区二区三区久久| 亚洲欧美日韩综合aⅴ视频| 欧美日韩一区二区免费视频| 亚洲国产精品一区二区第一页| 久久精品成人| 国产三区二区一区久久| 亚洲欧美三级在线| 国产精品久久久久久久久搜平片| 一本大道久久a久久精品综合| 欧美黄色网络| 亚洲精品国产系列| 嫩草国产精品入口| 亚洲国产视频一区二区| 免费视频久久| 91久久嫩草影院一区二区| 美女日韩欧美| 亚洲国产高清一区| 快she精品国产999| 亚洲电影观看| 欧美国产先锋| 日韩视频一区二区三区在线播放免费观看 | 欧美大胆人体视频| 亚洲欧洲视频在线| 欧美高清免费| 91久久在线观看| 欧美黄色影院| 亚洲精品美女91| 欧美精品一区二区高清在线观看| 91久久夜色精品国产网站| 欧美刺激午夜性久久久久久久| 久久国产免费看| 国产视频一区在线| 久久久久国产一区二区三区四区| 国内欧美视频一区二区| 久久久噜噜噜久久人人看| 在线不卡中文字幕播放| 欧美aa国产视频| 亚洲精品在线二区| 欧美天堂亚洲电影院在线观看 | 欧美电影在线观看| 亚洲另类视频| 欧美午夜精品理论片a级按摩| 亚洲视频在线观看一区| 国产精品亚洲视频| 久久国产精品一区二区三区| 在线欧美小视频| 欧美精品国产一区| 亚洲无线视频| 国产亚洲精品v| 免费在线欧美黄色| 一本色道久久综合亚洲精品小说 | 欧美一区二区三区日韩| 国内精品久久久| 麻豆久久婷婷| 99热在这里有精品免费| 国产精品美女久久久久aⅴ国产馆| 欧美一级片久久久久久久| 在线视频观看日韩| 欧美日本一道本在线视频| 亚洲一区二区三区欧美| 韩日成人在线| 欧美日韩高清区| 欧美一区二区视频在线观看2020| 红杏aⅴ成人免费视频| 欧美—级a级欧美特级ar全黄| 亚洲视频网在线直播| 国产在线精品成人一区二区三区| 免费在线观看精品| 亚洲中字在线| 亚洲国产精品久久精品怡红院 | 亚洲欧洲日本在线| 国产精品久久久久久久久免费樱桃 | 一区二区av在线| 国产午夜亚洲精品理论片色戒| 鲁鲁狠狠狠7777一区二区| 亚洲天堂网在线观看| 激情视频一区二区| 欧美视频日韩视频在线观看| 久久久91精品| 中国成人在线视频| 影音先锋久久资源网| 国产精品豆花视频| 另类天堂av| 亚洲欧美999| 亚洲人成毛片在线播放女女| 国产伦理一区| 欧美理论电影在线观看| 久久国产视频网站| 一区二区三区四区国产精品| 黄色成人av| 国产精品久久久久久亚洲毛片| 久久综合给合久久狠狠色 | 久久精品欧美日韩| 在线性视频日韩欧美| 在线成人免费视频| 国产精品一级| 欧美日韩亚洲视频一区| 久久亚洲二区| 性欧美激情精品| 在线视频欧美精品| 亚洲精品九九| 在线观看一区二区视频| 国产视频欧美视频| 国产精品www994| 欧美精品免费观看二区| 久久综合电影一区| 欧美亚洲专区| 亚洲性av在线| 日韩网站免费观看| 亚洲国产精品久久91精品| 国产日产欧美一区| 欧美午夜在线| 欧美另类视频| 欧美成人综合网站| 麻豆精品视频在线| 久久精品中文字幕免费mv| 亚洲欧洲av一区二区| 一二三四社区欧美黄| 亚洲人成人77777线观看| 影音先锋久久久| 国语自产精品视频在线看| 国产日韩av在线播放| 国产精品区二区三区日本| 欧美性色综合| 欧美日韩亚洲国产精品| 欧美人与性禽动交情品| 欧美成人精品一区| 蜜桃久久av一区| 久久久天天操| 久久久久久69| 久久精品最新地址| 欧美一区成人| 欧美一区午夜精品| 欧美一区二区免费视频| 午夜视频精品| 欧美一区二区视频在线| 欧美与黑人午夜性猛交久久久| 午夜精品福利在线| 午夜久久电影网| 香蕉亚洲视频| 欧美在线视频观看| 久久国内精品视频| 久久九九久精品国产免费直播| 欧美中文在线字幕| 久久精品伊人| 老司机午夜精品| 免费中文日韩|