天天看點

網易基于Filebeat的日志采集服務設計與實踐

雲原生技術大潮已經來臨,技術變革迫在眉睫。

在這股技術潮流之中,網易推出了 輕舟微服務平台,內建了微服務、Service Mesh、容器雲、DevOps等元件,已經廣泛應用于公司内部,同時也支撐了很多外部客戶的雲原生化改造和遷移。

網易基于Filebeat的日志采集服務設計與實踐

在這其中,日志是平時很容易被人忽視的一部分,卻是微服務、DevOps的重要一環。沒有日志,服務問題排查無從談起,同時日志的統一采集也是很多業務資料分析、處理、審計的基礎。

但是在雲原生容器化環境下,日志的采集又變得有點不同。

對于傳統的實體機或者虛拟機部署的服務,日志采集工作清晰明了。

業務日志直接輸出到主控端上,服務運作在固定的節點上,手動或者拿自動化工具把日志采集agent部署在節點上,加一下agent的配置,就可以開始采集日志了。同時為了友善後續的日志配置修改,還可以引入一個配置中心,用來下發agent配置。

而在Kubernetes環境中,情況就沒這麼簡單了。

一個Kubernetes node節點上有很多不同服務的容器在運作,容器的日志存儲方式有很多不同的類型,例如stdout、hostPath、emptyDir、pv等。由于在Kubernetes叢集中經常存在Pod主動或者被動的遷移,頻繁的銷毀、建立,我們無法像傳統的方式一樣人為給每個服務下發日志采集配置。另外,由于日志資料采集後會被集中存儲,是以查詢日志時,可以根據Namespace、Pod、Container、Node,甚至包括容器的環境變量、Label等次元來檢索、過濾很重要。

以上都是有别于傳統日志采集配置方式的需求和痛點,究其原因,還是因為傳統的方式并非針對Kubernetes設計,無法感覺Kubernetes,更無法和Kubernetes內建。

經過最近幾年的迅速發展,Kubernetes已經不僅僅是容器編排的事實标準,甚至可以被認為是新一代的分布式作業系統。在這個新型的作業系統中,controller的設計思路驅動了整個系統的運作。controller的抽象解釋如下圖所示:

網易基于Filebeat的日志采集服務設計與實踐

由于Kubernetes良好的可擴充性,Kubernetes設計了一種自定義資源CRD的概念,使用者可以自己定義各種資源,并借助一些framework開發controller,使用controller将我們的期望變成現實。

基于這個思路,對于日志采集來說,一個服務需要采集哪些日志,需要什麼樣的日志配置,是使用者的期望,而這一切,就需要我們開發一個日志采集的controller去實作。

網易基于Filebeat的日志采集服務設計與實踐

有了上面的解決思路,除了開發一個controller,剩下的就是圍繞着這個思路的一些選型分析。

日志采集controller隻負責對接Kubernetes,生成采集配置,并不負責真正的日志采集。目前市面上的日志采集agent有很多,例如傳統ELK技術棧的Logstash,CNCF已畢業項目Fluentd,最近推出不久的Loki,還有beats系列的Filebeat。下面簡單分析一下。

1. Logstash基于JVM,分分鐘記憶體占用達到幾百MB甚至上GB,有點重,首先被我們排除。

2. Fluentd背靠CNCF看着不錯,各種插件也多,不過基于Ruby和C編寫,對于輕舟團隊的技術棧來說,還是讓人止于觀望。雖然Fluentd還推出了純粹基于C語言的Fluentd-bit項目,記憶體占用很小,看着十分誘惑,但是使用C語言和不能動态reload配置,還是無法令人親近。

3. Loki推出的時間不久,目前功能有限,而且一些壓測資料表明性能不太好,暫時觀望。

4. Filebeat和Logstash、Kibana、Elasticsearch同屬Elastic公司,輕量級日志采集agent,推出就是為了替換Logstash,基于Golang編寫,和輕舟團隊技術棧完美契合,實測下來性能、資源占用率各方面都比較優秀,于是成為了輕舟日志采集agent第一選擇。

對于日志采集agent,在Kubernetes環境下一般有兩種部署方式。

1. 一種為sidecar的方式,即和業務Container部署在同一個Pod裡,這種方式下,Filebeat隻采集該業務Container的日志,也隻需配置該Container的日志配置,簡單、隔離性好,但最大的問題是, 每個服務都要有一個Filebeat去采集,而一個節點上通常有很多的Pod,記憶體等開銷加起來不容樂觀。

2. 另外一種也是最常見的每個Node上部署一個Filebeat容器,相比而言,記憶體占用要小很多,而且對Pod無侵入性,比較符合我們的正常使用方式。同時一般使用Kubernetes的DaemonSet部署,無需傳統的類似Ansible等自動化運維工具,部署運維效率大大提升。是以我們優先使用Daemonset部署Filebeat的方式。

選擇Filebeat作為日志采集agent,內建了自研的日志controller後,從節點的視角,我們看到的架構如下所示:

網易基于Filebeat的日志采集服務設計與實踐

1. 日志平台下發具體的CRD執行個體到Kubernetes叢集中,日志controller Ripple則負責從Kubernetes中List&Watch Pod和CRD執行個體。

2. 通過Ripple的過濾、聚合最終生成一個Filebeat的input配置檔案,配置檔案裡描述了服務的采集Path路徑、多行日志比對等配置,同時還會預設把例如PodName、Hostname等配置到日志元資訊中。

3. Filebeat則根據Ripple生成的配置,自動reload并采集節點上的日志,發送至Kafka或者Elasticsearch等。

由于Ripple監聽了Kubernetes事件,可以感覺到Pod的生命周期,不管Pod銷毀還是排程到任意的節點,依然能夠自動生成相應的Filebeat配置,無需人工幹預。

Ripple能感覺到Pod挂載的日志Volume,不管是docker Stdout的日志,還是使用HostPath、EmptyDir、Pv存儲日志,均可以生成節點上的日志路徑,告知Filebeat去采集。

Ripple可以同時擷取CRD和Pod的資訊,是以除了預設給日志配置加上PodName等元資訊外,還可以結合容器環境變量、Pod label、Pod Annotation等給日志打标,友善後續日志的過濾、檢索查詢。

除此之外,我們還給Ripple加入了日志定時清理,確定日志不丢失等功能,進一步增強了日志采集的功能和穩定性。

一般情況下Filebeat可滿足大部分的日志采集需求,但是仍然避免不了一些特殊的場景需要我們對Filebeat進行定制化開發,當然Filebeat本身的設計也提供了良好的擴充性。

Filebeat目前隻提供了像Elasticsearch、Kafka、Logstash等幾類output用戶端,如果我們想要Filebeat直接發送至其他後端,需要定制化開發自己的output。同樣,如果需要對日志做過濾處理或者增加元資訊,也可以自制processor插件。

無論是增加output還是寫processor,Filebeat提供的大體思路基本相同。一般來講有3種方式:

1. 直接fork Filebeat,在現有的源碼上開發。output或者processor都提供了類似Run、Stop等的接口,隻需要實作該類接口,然後在init方法中注冊相應的插件初始化方法即可。當然,由于Golang中init方法是在import包時才被調用,是以需要在初始化Filebeat的代碼中手動import。

2. 複制一份Filebeat的main.go,import我們自研的插件庫,然後重新編譯。本質上和方式1差別不大。

3. Filebeat還提供了基于Golang plugin的插件機制,需要把自研的插件編譯成.so共享連結庫,然後在Filebeat啟動參數中通過-plugin指定庫所在路徑。不過實際上一方面Golang plugin還不夠成熟穩定,另一方面自研的插件依然需要依賴相同版本的libbeat庫,而且還需要相同的Golang版本編譯,坑可能更多,不太推薦。

為了支援對接各種業務方,我們目前已經擴充開發了grpc output,支援多Kafka叢集的output等。

但是,真正的困難是在業務方實際使用之後,采集不到日志,多行日志配置或者采集二進制大檔案導緻Filebeat OOM等各種問題接踵而至。我們又投入了更多的時間在對Filebeat和日志采集的全方位監控上,例如:

1. 接入輕舟監控平台,有磁盤IO、網絡流量傳輸、記憶體占用、CPU使用、Pod事件報警等,確定基礎監控的完善。

2. 加入了日志平台資料全鍊路延遲監控。

3. 采集Filebeat自身日志,通過自身日志上報哪些日志檔案開始采集,什麼時候采集結束,避免每次都需要SSH到各種節點上檢視日志配置排查問題。

4. 自研Filebeat exporter,接入Prometheus,采集上報自身metrics資料。

通過立體化的監控增強,大大友善了我們問題的排查,減少了運維和人力成本,也更確定了服務的穩定性。

對應⽤的性能優化是個永恒的話題,盡管“過早優化是萬惡之源”,實際開發過程中還是需要時刻保持優化的意識。很多時候,我們看過太多GC原理、記憶體優化、性能優化,卻往往在寫完代碼、做完一個項目的時候,無從下手。實踐是檢驗真理的唯一标準。是以,親自動手去排查、摸索,才是提升姿勢水準、找到關鍵問題的捷徑。對于雲原生日志采集系統也是如此。

好消息是,對于性能優化,Golang貼心的為我們提供了三把鑰匙:

1. go benchmark

2. go pprof

3. go trace

這些鑰匙在日志采集性能優化場景下同樣有效。下面舉個簡單的示例。

以sync.Pool為例,sync.Pool一般用于儲存和複用臨時對象,減少記憶體配置設定,降低GC壓力。有很多的應用場景,例如号稱比Golang官方Http快10倍的FastHttp大量使用了sync.Pool,Filebeat使用sync.Pool将批量日志資料聚合成Batch分批發送,Nginx-Ingress-controller渲染生成Nginx配置時,也使用sync.Pool優化渲染效率。輕舟的日志controller Ripple也同樣使用了sync.Pool去優化渲染Filebeat配置時的性能。

首先,使用go benchmark壓測一下未使用sync.Pool時通過go template渲染出Filebeat配置的方法。

可以看到結果顯示的每次執行方法的時間,和配置設定的記憶體。

然後将go benchmark生成的profile檔案,使用go pprof觀察一下整體的性能資料。

網易基于Filebeat的日志采集服務設計與實踐

go pprof實際上有很多的資料可以供我們觀察,這裡僅展示記憶體的配置設定資訊。可以看到benchmark在這段時間内共申請了超過5G的記憶體。

接着,我們使用go trace檢視壓測過程中的goroutine,堆記憶體,GC等資訊。

網易基于Filebeat的日志采集服務設計與實踐

這裡僅截取600ms至700ms的時間段,可以很清楚地在圖中看到這100ms内發生了170次的GC。

同樣的方法和步驟,壓測一下使用sync.Pool後的結果。

網易基于Filebeat的日志采集服務設計與實踐

配置設定的記憶體總量減小到了160MB,而相同的時間段内GC次數也減少到了5次。優化效果十分明顯。

當然,Golang的作用不僅于此。從Docker到Kubernetes,從Istio到Knative,基于Golang的開源項目已然是雲原生生态體系的主力軍,Golang的簡潔高效也不斷吸引着新的項目采用它作為開發語言。

對于輕舟微服務平台,除了使用Golang寫Filebeat插件、開發日志采集的controller,我們還有很多基于Golang的元件,比如Service Mesh、容器雲等。

在雲原生時代,日志做為可觀測性的一部分,是排查、解決問題的基礎,也是後續大資料分析處理的開始。

在這個領域,雖然有很多開源項目,卻仍然沒有一個強力而統一的日志采集agent,或許這種百花齊放的景象會一直持續下去。是以,輕舟團隊自研日志agent Ripple的設計中也提出了更多的抽象,保留了對接其他日志采集agent的能力。後續我們計劃支援更多的日志采集agent,打造一個更加豐富、健壯的雲原生日志采集系統。

- END -

網易基于Filebeat的日志采集服務設計與實踐