event-export源碼解析

背景:在kubernetes裏event記錄了集羣運行所遇到的各種大事件,有助於排錯,但大量的事件如果都存儲在etcd中,會帶來較大的性能與容量壓力,所以etcd中默認只保存最近1小時的,如果我們將該時間改大 會大大增加集羣etcd的壓力,所以我們需要將該數據存儲到其他地方。經過一段時間的尋找 覺的k8s-stackdriver這個項目比較符合目前我的需求,但是它存儲到的是promethues,但是目前我司給與promethues的資源並不是很足,並且保存的時間默認只有15天本,地存儲也意味着Prometheus無法持久化數據,無法存儲大量歷史數據,同時也無法靈活擴展。,所以考慮使用了ES作爲存儲。但是es存儲方案目前官方並不支持,只有通過fluentd去收集日誌達到效果,所以我這邊通過修改源碼將存儲直接改爲es,這樣就不需要藉助fluentd去收集日誌了。

 

本文主要是先分析event-export源碼,之後再講修改過程寫出來。

 

程序入口 main.go

sink, err := stackdriver.NewSdSinkFactory().CreateNew(strings.Split(*sinkOpts, " "))
if err != nil {
   glog.Fatalf("Failed to initialize sink: %v", err)
}

解釋:初始化sink對象 並根據sink-opts傳遞進來的 參數設置對應的參數

參數列表(NewSdSinkFactory):

flagSet

flushDelay

maxBufferSize

maxConcurrency

resourceModelVersion

endpoint

解析sinkOpts的參數(CreateNew)

client, err := newKubernetesClient()
if err != nil {
   glog.Fatalf("Failed to initialize kubernetes client: %v", err)
}
eventExporter := newEventExporter(client, sink, *resyncPeriod)

newKubernetesClient 初始化go-client 接口

通過集羣內部配置創建 k8s 配置信息,通過 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 環境變量方式獲取

默認tokenfile rootCAFile 在/var/run/secrets/kubernetes.io/serviceaccount/

 

newEventExporter函數的作用 我們可以看他具體實現方法:

func newEventExporter(client kubernetes.Interface, sink sinks.Sink, resyncPeriod time.Duration) *eventExporter {
   return &eventExporter{
      sink:    sink,
      watcher: createWatcher(client, sink, resyncPeriod),
   }
}

func createWatcher(client kubernetes.Interface, sink sinks.Sink, resyncPeriod time.Duration) watchers.Watcher {
   return events.NewEventWatcher(client, &events.EventWatcherConfig{
      OnList:       sink.OnList,
      ResyncPeriod: resyncPeriod,
      Handler:      sink,
   })
}

newEventExporter 返回了eventExporter這個結構體 該結構體包含sink 和watcher兩個字段。

sink: 包含所有的參數配置等

watcher: 通過createwatcher函數 監聽events resource

分解:createWatcher函數

NewEventWatcher配置了需要watche的目標對象爲event,以及間隔多久watch一次

events.EventWatcherConfig 結構體包含三個字段

OnList OnListFunc event列表

ResyncPeriod watcher的時間間隔 默認爲1分鐘watch一次 。 通過resyncPeriod參數設置

Handler 包含三個字段OnAdd OnUpdate OnDelete

OnAdd: 主要作用就是將watch到的數據通過通道存入sdSink下的logEntryChannel字段,並且在promethues插入數據

OnUpdate: 更新logEntryChannel字段,並更新promethues裏的數據

 

go func() {
   http.Handle("/metrics", promhttp.Handler())
   glog.Fatalf("Prometheus monitoring failed: %v", http.ListenAndServe(*prometheusEndpoint, nil))
}()

stopCh := newSystemStopChannel()
eventExporter.Run(stopCh)

 首先 通過gorouteing執行了匿名函數 該匿名函數 主要設置了promethues的/metrics路由並且監聽localhost的本地端口 默認爲80 通過啓動參數prometheusEndpoint設置

func newSystemStopChannel() chan struct{} {
   ch := make(chan struct{})
   go func() {
      c := make(chan os.Signal)
      signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
      sig := <-c
      glog.Infof("Received signal %s, terminating", sig.String())

      // Close stop channel to make sure every goroutine will receive stop signal.
      close(ch)
   }()

   return ch
}

newSystemStopChannel函數,通過gorouteing 執行匿名函數,當接收到SIGINT(2),SIGTERM(9)的時候 打印Received signal sig.String(), terminating的信號

 

eventExporter.Run(stopCh) 函數:

func (e *eventExporter) Run(stopCh <-chan struct{}) {
   utils.RunConcurrentlyUntil(stopCh, e.sink.Run, e.watcher.Run)
}

run函數:

stopCh: 接收newSystemStopChannel傳遞過來的信號值通道

utils.RunConcurrentlyUntil 方法

func RunConcurrentlyUntil(stopCh <-chan struct{}, funcs ...StoppableFunc) {
   var wg sync.WaitGroup
   for i := range funcs {
      wg.Add(1)
      f := funcs[i]
      go func() {
         defer wg.Done()
         f(stopCh)
      }()
   }

   <-stopCh

   wg.Wait()
}

併發運行e.sink.Run, e.watcher.Run

通過sync.WaitGroup解決同步阻塞等待的問題。

wg.Add 將計數器設置爲1

執行e.sink.Run, e.watcher.Run兩個函數

當執行完成的時候 將計數器設置減去1

在計數器不爲0的時候Wait() 一直阻塞

只有當計數器爲0的時候 或者接收到退出信號的時候纔會退出。

 

e.sink.Run:

在eventExporter的sink字段 之後跳轉到

interface.go下的Sink接口下的run方法

因爲在newEventExporter函數裏sink的值已經被賦值爲sink, err := stackdriver.NewSdSinkFactory().CreateNew(strings.Split(*sinkOpts, " "))

返回的值 createNew

並且CreateNew 函數return newSdSink(writer, clk, config, resourceModelFactory), nil

通過查看newSdSink 該函數返回的是sdSink結構體

所以e.sink.Run 就相當於函數擁有者爲sdSink下的Run方法

func (s *sdSink) Run(stopCh <-chan struct{}) {
   glog.Info("Starting Stackdriver sink")
   for {
      select {
      case entry := <-s.logEntryChannel:
         s.currentBuffer = append(s.currentBuffer, entry)
         if len(s.currentBuffer) >= s.config.MaxBufferSize {
            s.flushBuffer()
         } else if len(s.currentBuffer) == 1 {
            s.setTimer()
         }
         break
      case <-s.getTimerChannel():
         s.flushBuffer()
         break
      case <-stopCh:
         glog.Info("Stackdriver sink received stop signal, waiting for all requests to finish")
         for i := 0; i < s.config.MaxConcurrency; i++ {
            s.concurrencyChannel <- struct{}{}
         }
         glog.Info("All requests to Stackdriver finished, exiting Stackdriver sink")
         return
      }
   }
}

 run方法是一個for死循環 只有當收到newSystemStopChannel傳遞過來的信號時纔會退出,通過s.logEntryChannel傳遞過來的日誌,將該日誌存儲到s.currentBuffer裏 當存儲裏的個數大於s.config.MaxBufferSize的值時 執行flushBuffer函數

 

func (s *sdSink) flushBuffer() {
   entries := s.currentBuffer
   s.currentBuffer = nil
   s.concurrencyChannel <- struct{}{}
   go s.sendEntries(entries)
}

func (s *sdSink) sendEntries(entries []*sd.LogEntry) {
   glog.V(4).Infof("Sending %d entries to Stackdriver", len(entries))

   written := s.writer.Write(entries, s.logName, s.sdResourceFactory.defaultMonitoredResource())
   successfullySentEntryCount.Add(float64(written))

   <-s.concurrencyChannel

   glog.V(4).Infof("Successfully sent %d entries to Stackdriver", len(entries))
}

flushBuffer首先會清空s.concurrencyChannel裏的值

之後將entries的值傳遞給sendEntries

sendEntries函數下通過successfullySentEntryCount將日誌寫入promethues

之後接收s.concurrencyChannel傳遞過來的值,這裏主要解決接收到SIGINT,SIGTERM的時候等待所有請求完成之後再停止

 

當s.currentBuffer等於1時 充值計時器,默認爲5s,如果5s內沒有數據代表超時 直接執行flushBuffer函數

 

e.watcher.Run:

func (w *watcher) Run(stopCh <-chan struct{}) {
   w.reflector.Run()
   <-stopCh
}

w.reflector.Run()會跳轉到所屬者爲reflector下的Run函數

func (r *Reflector) Run() {
   glog.V(3).Infof("Starting reflector %v (%s) from %s", r.expectedType, r.resyncPeriod, r.name)
   go wait.Until(func() {
      if err := r.ListAndWatch(wait.NeverStop); err != nil {
         utilruntime.HandleError(err)
      }
   }, r.period, wait.NeverStop)
}

 聲明一個匿名函數 每隔一段時間執行這個匿名函數

該函數下的主要方法就是ListAndWatch 也就是由它收集event的日誌

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章