1. 程式人生 > >Kubernetes Events介紹(中)_Kubernetes中文社群

Kubernetes Events介紹(中)_Kubernetes中文社群

上一回,歷經千辛萬苦終於破解了Events的姓名之謎,(Kubernetes(K8s)Events介紹(上))尋得Events真身。那麼Events的身世究竟如何?根據Pod怎樣才能找到對應的Events?本回將一一揭開謎底。

順藤摸瓜

前面說到在DockerManager裡定義了EventRecorder的成員,它的方法Event()、Eventf()、PastEventf()都可以用來構造Events例項,略有區別的地方是Eventf()呼叫了Sprintf()來輸出Events message,PastEventf()可建立指定時間發生的Events。

一方面可以推測所有擁有EventsRecorder成員的Kubernetes資源定義都可以產生Events。經過暴力搜尋發現,EventsRecorder主要被K8s的重要元件ControllerManager和Kubelet使用。比如,負責管理註冊、登出等的NodeController,會將Node的狀態變化資訊記錄為Events。DeploymentController會記錄回滾、擴容等的Events。他們都在ControllerManager啟動時被初始化並執行。與此同時Kubelet除了會記錄它本身執行時的Events,比如:無法為Pod掛載卷、無法頻寬整型等,還包含了一系列像docker_manager這樣的小單元,它們各司其職,並記錄相關Events。

另一方面在調查的時候發現,Events分為兩類,並定義在kubernetes/pkg/api/types.go裡,分別是EventTypeNormal和EventTypeWarning,它們分別表示該Events“僅表示資訊,不會造成影響”和“可能有些地方不太對”。

在types.go裡,還找到了Event資料結構的定義:

type Event struct {
    unversioned.TypeMeta `json:",inline"`
    ObjectMeta           `json:"metadata,omitempty"`

    // Required. The object that this event is about.
InvolvedObject ObjectReference `json:"involvedObject,omitempty"` Reason string `json:"reason,omitempty"` Message string `json:"message,omitempty"` Source EventSource `json:"source,omitempty"` FirstTimestamp unversioned.Time `json:"firstTimestamp,omitempty"` LastTimestamp
unversioned.Time `json:"lastTimestamp,omitempty"` Count int32 `json:"count,omitempty"` Type string `json:"type,omitempty"` }

除了標準的Kubernetes資源必備的unversioned.TypeMeta和ObjectMeta成員外,Event結構體還包含了Events相關的物件、原因、內容、訊息源、首次記錄時間、最近記錄時間、記錄統計和型別。

另外還定義了EventsList的結構型別,這就是我們使用kubectl get events和GET /api/v1/namespaces/{namespace}/events獲取Events列表時K8s使用的資料結構。

在Events的定義裡,比較重要的有兩個成員,一個是InvolvedObject, 另一個是Source。

首先,InvolvedObject表示的是這個Events所屬的資源。它的型別是ObjectReference,定義如下:

type ObjectReference struct 
{
    Kind            string    `json:"kind,omitempty"`
    Namespace       string    `json:"namespace,omitempty"`
    Name            string    `json:"name,omitempty"`
    UID             types.UID `json:"uid,omitempty"`
    APIVersion      string    `json:"apiVersion,omitempty"`
    ResourceVersion string    `json:"resourceVersion,omitempty"`
    FieldPath       string    `json:"fieldPath,omitempty"`
}

ObjectReference裡包含的資訊足夠我們唯一確定該資源例項。

然後,Source表示的是該Events的來源,它的型別是EventSource,定義如下:

type EventSource struct {    
    // Component from which the event is generated.
    Component string `json:"component,omitempty"`
    // Host name on which the event is generated.
    Host string `json:"host,omitempty"`
}

來龍無去脈

前面的研究已經為我們大致畫出了Events的內部輪廓。回到開始時的問題: 既然Events的名字跟發生它的Pod的名字不同,那麼kubectl describe pod時如何找到對應的Events的?我們可以大膽推測,正是通過Events定義裡的InvolvedObject成員來鎖定了它們之間的關係。

在前面分析kubeadm原理的文章中已經介紹過Kubernetes的命令都是利用第三方包Cobra生成的,kubectl describe也不例外,它定義在kubernetes/pkg/kubectl/cmd/describe.go裡。

Kubernetes中只有部分資源可以被describe,可以稱為“DescribableResources”,通過一個資源型別(unversioned.GroupKind)和對應描述器(Describer)的Map對映相關聯。這些資源有我們常見的Pod、ReplicationController、Service、Node、Deloyment、ReplicaSet等等。注意這些資源裡並不包含Events。

顯而易見,我們需要去仔細看看Pod的Describer做了什麼。PodDescriber只有一個方法,那就是Describe(),實現在kubernetes/pkg/kubectl/describe.go裡。

Describe()方法首先通過namespace和name唯一確定所請求的Pod。如果出錯並且ShowEvents標識為true的情況下,會根據FieldSelector找到Events,並說明“獲取Pod出錯,但發現了Events”。如果請求Pod未出錯且ShowEvents標識為true,則通過GetReference()方法找到相關的Events。

不管哪種方式,只要找到的Events不為空,總是會通過DescribeEvents()方法將Events列表按特定格式輸出。即下圖:

20161226093445

這麼一說就明白了,原來我們在kubectl describe pod時得到的返回的結果不僅包含了Pod的資訊,還有Events的資訊,它們來自的是不同的處理過程。

到此我們已經摸清了Events的來龍。具體來說對於describe pod時看到的Events,它是由Kubelet的DockerManager生成,在執行kubectl命令時通過PodDescriber進行採集。顯然如果我們不執行kubectl命令的時候這些Events仍然是存在的,那麼這個時候這些Events會流向何處?也就說,我們還沒捋順Events的去脈。

Kubelet在啟動的時候會初始化一個EventRecorder,這個EventRecorder又被交於Kubelet上每個小manager使用,比如DockerManager。它將產生的Events的Source成員進行初始化:Componets為“kubelet”,Host為該節點的名字。

狡兔三窟

在追尋Events的去脈前,我們先來看看PodDescriber是如何採集這些Events的。

前面簡單描述了PodDescriber的Describe()方法的作用,如果不夠明朗,下面貼出它的原始碼:

func (d *PodDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {    
    pod, err := d.Pods(namespace).Get(name)    
    if err != nil {        
        // 獲取Pod失敗時
        if describerSettings.ShowEvents {            
            eventsInterface := d.Events(namespace)            
            selector := eventsInterface.GetFieldSelector(&name, &namespace, nil, nil)            
            options := api.ListOptions{FieldSelector: selector}            events, err2 := eventsInterface.List(options)            
            if describerSettings.ShowEvents && err2 == nil && len(events.Items) > 0 {                
                return tabbedString(func(out io.Writer) error {
                    fmt.Fprintf(out, "Pod '%v': error '%v', but found events.\n", name, err)                    
DescribeEvents(events, out)                    
                    return nil
                })
            }
        }        
        return "", err
    }    
    var events *api.EventList
    // 獲取Pod成功
    if describerSettings.ShowEvents {        
        if ref, err := api.GetReference(pod); err != nil {
            glog.Errorf("Unable to construct reference to '%#v': %v", pod, err)
        } else {
            ref.Kind = ""
        // 通過Events().Search()獲取
  1. 獲取Pod失敗時
    Events的GetFieldSelector()方法同時根據InvolvedObject的名稱、名稱空間、資源型別和UID生成一個FieldSelector。使用它作為ListOptions,可以選中滿足這個Selector對應的資源。如果選中的Events不為空,說明“獲取Pod出錯,但發現了Events”的情況,並將其按照特定的格式列印。
  2. 獲取Pod成功,GetReference()失敗
    GetReference()根據傳入的K8s資源例項,構造它的引用說明。如果執行失敗,記錄失敗日誌,並直接執行describePod(),將目前獲取的結果輸出到螢幕上。
  3. 獲取Pod成功,GetReference()成功
    GetReference()成功後,呼叫Events的Search()方法,尋找關於該Pod的所有Events。最終執行describePod(),並將目前獲取的結果輸出到螢幕上。

當然,即使是Events的Search()方法,內部執行的仍是先GetFieldSelector()再Events List()的過程。這是因為DockerManager在生成Event的時候會呼叫它的makeEvent()方法(程式碼在上篇引用過,這裡不再贅述),將Pod關聯到該Events的InvolvedObject上。GetFieldSelector()返回的是一個field.Selector介面例項,它定義在kubernetes/pkg/fields/selector.go裡,通過它的Matches()方法可以選中含有該Field且對應值相同的Events。

Kubernetes裡,FieldSelector和LabelSelector的設計異曲同工,不同的是Field匹配的是該資源的域,比如Name、Namespace,而Label匹配的是Labels域裡的鍵值對。

20161219151628

作者立堯微信公眾號