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"` LastTimestampunversioned.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列表按特定格式輸出。即下圖:
這麼一說就明白了,原來我們在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()獲取
- 獲取Pod失敗時
Events的GetFieldSelector()方法同時根據InvolvedObject的名稱、名稱空間、資源型別和UID生成一個FieldSelector。使用它作為ListOptions,可以選中滿足這個Selector對應的資源。如果選中的Events不為空,說明“獲取Pod出錯,但發現了Events”的情況,並將其按照特定的格式列印。 - 獲取Pod成功,GetReference()失敗
GetReference()根據傳入的K8s資源例項,構造它的引用說明。如果執行失敗,記錄失敗日誌,並直接執行describePod(),將目前獲取的結果輸出到螢幕上。 - 獲取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域裡的鍵值對。