cadvisor詳解


一. cadvisor和k8s的耦合

cadvisor是一個谷歌開發的容器監控工具,它被內嵌到k8s中作為k8s的監控組件。現在將k8s中的cadvisor實現分析一下。

k8s中和cadvisor的代碼主要在./pkg/kubelet/cadvisor目錄下。在當前k8s版本(v1.13)中,kubelet主要調用的cadvisor方法如下:

MachineInfo
RootFsInfo
VersionInfo
GetDirFsInfo

GetFsInfo

---------------------------------------
ContainerInfoV2
SubcontainerInfo
ContainerInfo
WatchEvents

分割線之上的方法和cadvisor本身耦合較松,分割線之下的方法則和cadvisor耦合緊密。怎么樣理解這里的耦合度呢?舉例來說,對於分割線

之上的方法,例如MachineInfo,它的操作只是簡單的讀取本地文件以獲取主機的信息。比如通過讀取/proc/cpuinfo文件讀取本地主機的cpu信息。

對於這種方法,我們可以非常輕松的移植他們。

而分割線之下的方法則很難從cadvisor中單獨剝離出來,它們的實現是依賴於整個cadvisor的體系。下面分析一下cadvisor具體的實現

 

二. 事件監聽層

cadvisor的架構簡單來說就是一個event機制。它基本上可以分為兩層,事件監聽層和事件處理層。事件監聽層負責監聽linux系統發生的事件,而事件處理層

負責對這些事件進行處理。

首先說說事件監聽層。事件監聽層主要包含兩個監聽者,ContainerAdd事件和OOM事件。其對應的函數是watchForNewContainers, watchForNewOoms。

watchForNewContainers完成的事情是啟動每一個watcher。代碼如下,可以看到和watcher交互的是eventsChannel。目前cadvisor中包含兩種wathcer, 一個是rawWatcher,另一個是rktWatcher。

    for _, watcher := range self.containerWatchers {
        err := watcher.Start(self.eventsChannel)
        if err != nil {
            return err
        }
    }

rawWatcher直接監控系統的cgroup根目錄,而rktWatcher似乎是與rkt的client進行交互,由於rkt不是主流的技術,因此我們目前主要研究rawWatcher。這個watcher的代碼在./manager/watcher/raw目錄下。

稍作分析就可以看出這個watcher是調用了github.com/sigma/go-inotify庫,這個庫簡單來說就是利用linux的inotify機制對cgroup根目錄進行監聽,如果根目錄創建了新的目錄,那么它就會觸發一個ContainerAdd的事件。

然后將事件發送到上面代碼中的self.eventsChannel中。注意linux的inotify機制會監聽目錄的增刪改。而這里rawWatcher只對目錄的增刪感興趣。也就是說它只對容器的創建和刪除感興趣,對容器本身狀態的變化不感興趣。

對函數rawContainerWatcher.watchDirectory的代碼稍作分析不難發現,它是一個遞歸調用的結構。如果用戶請求對任何目錄進行監聽,它會一並監聽這個目錄下的所有子目錄。

 

watchForNewOoms是為了監控OOM事件,它的執行流程與container watcher類似,只不過調用的庫是github.com/euank/go-kmsg-parser/,這個庫的原理是讀取linux系統的/dev/kmsg字符串設備。這個字符串設備的大概

意思是將系統的事件報告出來。其核心代碼如下。

    outStream := make(chan *oomparser.OomInstance, 10)
    oomLog, err := oomparser.New()
    if err != nil {
        return err
    }
    go oomLog.StreamOoms(outStream)

    go func() {
        for oomInstance := range outStream {
            // Surface OOM and OOM kill events.
            newEvent := &info.Event{
                ContainerName: oomInstance.ContainerName,
                Timestamp:     oomInstance.TimeOfDeath,
                EventType:     info.EventOom,
            }
            err := self.eventHandler.AddEvent(newEvent)
            if err != nil {
                klog.Errorf("failed to add OOM event for %q: %v", oomInstance.ContainerName, err)
            }

 

三 事件處理層

事件監聽層將event發送到self.eventsChannel上,這些event包含了,ContainerAdd, ContainerDelete,EventOomKill三種。這三種事件分兩類進行處理,對於ContainerAdd和ContainerDelete, Manager分別

調用CreateContainer和ContainerDestroy方法,然后調用self.eventHandler.AddEvent(event)方法。而EventOomkill事件則只調用self.eventHandler.AddEvent(event)方法,沒有其他特殊的處理。

那么這個eventHandler是干啥的呢。這個東西實際上就是一個緩沖區,我們看一下這個evnetHandler的數據結構。它的核心數據結構就是events.watchers,它維護了一組watch,每一個watch存儲了一個channel和一個

request。這個request其所在的watch想要監聽的事件特性。evnetsHandler每當接收到新的事件的時候,它會根據這個事件的類型分發給各個watch。

 

// events provides an implementation for the EventManager interface.
type events struct {
    // eventStore holds the events by event type.
    eventStore map[info.EventType]*utils.TimedStore
    // map of registered watchers keyed by watch id.
    watchers map[int]*watch
    // lock guarding the eventStore.
    eventsLock sync.RWMutex
    // lock guarding watchers.
    watcherLock sync.RWMutex
    // last allocated watch id.
    lastId int
    // Event storage policy.
    storagePolicy StoragePolicy
}

// initialized by a call to WatchEvents(), a watch struct will then be added
// to the events slice of *watch objects. When AddEvent() finds an event that
// satisfies the request parameter of a watch object in events.watchers,
// it will send that event out over the watch object's channel. The caller that
// called WatchEvents will receive the event over the channel provided to
// WatchEvents
type watch struct {
    // request parameters passed in by the caller of WatchEvents()
    request *Request
    // a channel used to send event back to the caller.
    eventChannel *EventChannel
}

// Request holds a set of parameters by which Event objects may be screened.
// The caller may want events that occurred within a specific timeframe
// or of a certain type, which may be specified in the *Request object
// they pass to an EventManager function
type Request struct {
    // events falling before StartTime do not satisfy the request. StartTime
    // must be left blank in calls to WatchEvents
    StartTime time.Time
    // events falling after EndTime do not satisfy the request. EndTime
    // must be left blank in calls to WatchEvents
    EndTime time.Time
    // EventType is a map that specifies the type(s) of events wanted
    EventType map[info.EventType]bool
    // allows the caller to put a limit on how many
    // events to receive. If there are more events than MaxEventsReturned
    // then the most chronologically recent events in the time period
    // specified are returned. Must be >= 1
    MaxEventsReturned int
    // the absolute container name for which the event occurred
    ContainerName string
    // if IncludeSubcontainers is false, only events occurring in the specific
    // container, and not the subcontainers, will be returned
    IncludeSubcontainers bool
}

 

 

剩下的事就很簡單了,對於任何ContainerAdd事件,manager維護了一組工廠類,每一個類對應一種container類型。這些工廠類定義在./container中。manager分析ContainerAdd事件中的相關信息,將它傳遞

給對應的工廠類,工廠類為container生成一個對應的handler並且存儲起來,handler執行具體的監控任務。具體來說就是定期讀取container對應的cgroup文件。從中獲取信息。handler將讀取到的數據存儲到自己的緩存memoryCache中。

handler的包裝類型是containerData

 

四. k8s中用到的幾個關鍵函數

GetContainerV2(),直接獲取它想要的container對應的handler,然后讀取其中memoryCache的狀態數據

WatchEvents(),這個函數主要是OOMWatcher在調用,它暴露出一個channel給OOMWatcher用以監聽系統的OOMWatcher事件

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM