網(wǎng)易基于Filebeat的日志采集服務(wù)設(shè)計(jì)與實(shí)踐
- 背景 -
云原生技術(shù)大潮已經(jīng)來臨,技術(shù)變革迫在眉睫。
在這股技術(shù)潮流之中,網(wǎng)易推出了 輕舟微服務(wù)平臺,集成了微服務(wù)、Service Mesh、容器云、DevOps等組件,已經(jīng)廣泛應(yīng)用于公司內(nèi)部,同時也支撐了很多外部客戶的云原生化改造和遷移。
在這其中,日志是平時很容易被人忽視的一部分,卻是微服務(wù)、DevOps的重要一環(huán)。沒有日志,服務(wù)問題排查無從談起,同時日志的統(tǒng)一采集也是很多業(yè)務(wù)數(shù)據(jù)分析、處理、審計(jì)的基礎(chǔ)。
但是在云原生容器化環(huán)境下,日志的采集又變得有點(diǎn)不同。
- 容器日志采集的痛點(diǎn) -
傳統(tǒng)主機(jī)模式
對于傳統(tǒng)的物理機(jī)或者虛擬機(jī)部署的服務(wù),日志采集工作清晰明了。
業(yè)務(wù)日志直接輸出到宿主機(jī)上,服務(wù)運(yùn)行在固定的節(jié)點(diǎn)上,手動或者拿自動化工具把日志采集agent部署在節(jié)點(diǎn)上,加一下agent的配置,就可以開始采集日志了。同時為了方便后續(xù)的日志配置修改,還可以引入一個配置中心,用來下發(fā)agent配置。
Kubernetes環(huán)境
而在Kubernetes環(huán)境中,情況就沒這么簡單了。
一個Kubernetes node節(jié)點(diǎn)上有很多不同服務(wù)的容器在運(yùn)行,容器的日志存儲方式有很多不同的類型,例如stdout、hostPath、emptyDir、pv等。由于在Kubernetes集群中經(jīng)常存在Pod主動或者被動的遷移,頻繁的銷毀、創(chuàng)建,我們無法像傳統(tǒng)的方式一樣人為給每個服務(wù)下發(fā)日志采集配置。另外,由于日志數(shù)據(jù)采集后會被集中存儲,所以查詢?nèi)罩緯r,可以根據(jù)Namespace、Pod、Container、Node,甚至包括容器的環(huán)境變量、Label等維度來檢索、過濾很重要。
以上都是有別于傳統(tǒng)日志采集配置方式的需求和痛點(diǎn),究其原因,還是因?yàn)閭鹘y(tǒng)的方式并非針對Kubernetes設(shè)計(jì),無法感知Kubernetes,更無法和Kubernetes集成。
經(jīng)過最近幾年的迅速發(fā)展,Kubernetes已經(jīng)不僅僅是容器編排的事實(shí)標(biāo)準(zhǔn),甚至可以被認(rèn)為是新一代的分布式操作系統(tǒng)。在這個新型的操作系統(tǒng)中,controller的設(shè)計(jì)思路驅(qū)動了整個系統(tǒng)的運(yùn)行。controller的抽象解釋如下圖所示:
由于Kubernetes良好的可擴(kuò)展性,Kubernetes設(shè)計(jì)了一種自定義資源CRD的概念,用戶可以自己定義各種資源,并借助一些framework開發(fā)controller,使用controller將我們的期望變成現(xiàn)實(shí)。
基于這個思路,對于日志采集來說,一個服務(wù)需要采集哪些日志,需要什么樣的日志配置,是用戶的期望,而這一切,就需要我們開發(fā)一個日志采集的controller去實(shí)現(xiàn)。
- 探索與架構(gòu)設(shè)計(jì) -
有了上面的解決思路,除了開發(fā)一個controller,剩下的就是圍繞著這個思路的一些選型分析。
日志采集agent選型
日志采集controller只負(fù)責(zé)對接Kubernetes,生成采集配置,并不負(fù)責(zé)真正的日志采集。目前市面上的日志采集agent有很多,例如傳統(tǒng)ELK技術(shù)棧的Logstash,CNCF已畢業(yè)項(xiàng)目Fluentd,最近推出不久的Loki,還有beats系列的Filebeat。下面簡單分析一下。
1. Logstash基于JVM,分分鐘內(nèi)存占用達(dá)到幾百M(fèi)B甚至上GB,有點(diǎn)重,首先被我們排除。
2. Fluentd背靠CNCF看著不錯,各種插件也多,不過基于Ruby和C編寫,對于輕舟團(tuán)隊(duì)的技術(shù)棧來說,還是讓人止于觀望。雖然Fluentd還推出了純粹基于C語言的Fluentd-bit項(xiàng)目,內(nèi)存占用很小,看著十分誘惑,但是使用C語言和不能動態(tài)reload配置,還是無法令人親近。
3. Loki推出的時間不久,目前功能有限,而且一些壓測數(shù)據(jù)表明性能不太好,暫時觀望。
4. Filebeat和Logstash、Kibana、Elasticsearch同屬Elastic公司,輕量級日志采集agent,推出就是為了替換Logstash,基于Golang編寫,和輕舟團(tuán)隊(duì)技術(shù)棧完美契合,實(shí)測下來性能、資源占用率各方面都比較優(yōu)秀,于是成為了輕舟日志采集agent第一選擇。
agent集成方式
對于日志采集agent,在Kubernetes環(huán)境下一般有兩種部署方式。
1. 一種為sidecar的方式,即和業(yè)務(wù)Container部署在同一個Pod里,這種方式下,F(xiàn)ilebeat只采集該業(yè)務(wù)Container的日志,也只需配置該Container的日志配置,簡單、隔離性好,但最大的問題是, 每個服務(wù)都要有一個Filebeat去采集,而一個節(jié)點(diǎn)上通常有很多的Pod,內(nèi)存等開銷加起來不容樂觀。
2. 另外一種也是最常見的每個Node上部署一個Filebeat容器,相比而言,內(nèi)存占用要小很多,而且對Pod無侵入性,比較符合我們的常規(guī)使用方式。同時一般使用Kubernetes的DaemonSet部署,無需傳統(tǒng)的類似Ansible等自動化運(yùn)維工具,部署運(yùn)維效率大大提升。所以我們優(yōu)先使用Daemonset部署Filebeat的方式。
整體架構(gòu)
選擇Filebeat作為日志采集agent,集成了自研的日志controller后,從節(jié)點(diǎn)的視角,我們看到的架構(gòu)如下所示:
1. 日志平臺下發(fā)具體的CRD實(shí)例到Kubernetes集群中,日志controller Ripple則負(fù)責(zé)從Kubernetes中List&Watch Pod和CRD實(shí)例。
2. 通過Ripple的過濾、聚合最終生成一個Filebeat的input配置文件,配置文件里描述了服務(wù)的采集Path路徑、多行日志匹配等配置,同時還會默認(rèn)把例如PodName、Hostname等配置到日志元信息中。
3. Filebeat則根據(jù)Ripple生成的配置,自動reload并采集節(jié)點(diǎn)上的日志,發(fā)送至Kafka或者Elasticsearch等。
由于Ripple監(jiān)聽了Kubernetes事件,可以感知到Pod的生命周期,不管Pod銷毀還是調(diào)度到任意的節(jié)點(diǎn),依然能夠自動生成相應(yīng)的Filebeat配置,無需人工干預(yù)。
Ripple能感知到Pod掛載的日志Volume,不管是docker Stdout的日志,還是使用HostPath、EmptyDir、Pv存儲日志,均可以生成節(jié)點(diǎn)上的日志路徑,告知Filebeat去采集。
Ripple可以同時獲取CRD和Pod的信息,所以除了默認(rèn)給日志配置加上PodName等元信息外,還可以結(jié)合容器環(huán)境變量、Pod label、Pod Annotation等給日志打標(biāo),方便后續(xù)日志的過濾、檢索查詢。
除此之外,我們還給Ripple加入了日志定時清理,確保日志不丟失等功能,進(jìn)一步增強(qiáng)了日志采集的功能和穩(wěn)定性。
- 基于Filebeat的實(shí)踐 -
功能擴(kuò)展
一般情況下Filebeat可滿足大部分的日志采集需求,但是仍然避免不了一些特殊的場景需要我們對Filebeat進(jìn)行定制化開發(fā),當(dāng)然Filebeat本身的設(shè)計(jì)也提供了良好的擴(kuò)展性。
Filebeat目前只提供了像Elasticsearch、Kafka、Logstash等幾類output客戶端,如果我們想要Filebeat直接發(fā)送至其他后端,需要定制化開發(fā)自己的output。同樣,如果需要對日志做過濾處理或者增加元信息,也可以自制processor插件。
無論是增加output還是寫processor,F(xiàn)ilebeat提供的大體思路基本相同。一般來講有3種方式:
1. 直接fork Filebeat,在現(xiàn)有的源碼上開發(fā)。output或者processor都提供了類似Run、Stop等的接口,只需要實(shí)現(xiàn)該類接口,然后在init方法中注冊相應(yīng)的插件初始化方法即可。當(dāng)然,由于Golang中init方法是在import包時才被調(diào)用,所以需要在初始化Filebeat的代碼中手動import。
2. 復(fù)制一份Filebeat的main.go,import我們自研的插件庫,然后重新編譯。本質(zhì)上和方式1區(qū)別不大。
3. Filebeat還提供了基于Golang plugin的插件機(jī)制,需要把自研的插件編譯成.so共享鏈接庫,然后在Filebeat啟動參數(shù)中通過-plugin指定庫所在路徑。不過實(shí)際上一方面Golang plugin還不夠成熟穩(wěn)定,另一方面自研的插件依然需要依賴相同版本的libbeat庫,而且還需要相同的Golang版本編譯,坑可能更多,不太推薦。
為了支持對接各種業(yè)務(wù)方,我們目前已經(jīng)擴(kuò)展開發(fā)了grpc output,支持多Kafka集群的output等。
立體化監(jiān)控
但是,真正的困難是在業(yè)務(wù)方實(shí)際使用之后,采集不到日志,多行日志配置或者采集二進(jìn)制大文件導(dǎo)致Filebeat OOM等各種問題接踵而至。我們又投入了更多的時間在對Filebeat和日志采集的全方位監(jiān)控上,例如:
1. 接入輕舟監(jiān)控平臺,有磁盤IO、網(wǎng)絡(luò)流量傳輸、內(nèi)存占用、CPU使用、Pod事件報(bào)警等,確保基礎(chǔ)監(jiān)控的完善。
2. 加入了日志平臺數(shù)據(jù)全鏈路延遲監(jiān)控。
3. 采集Filebeat自身日志,通過自身日志上報(bào)哪些日志文件開始采集,什么時候采集結(jié)束,避免每次都需要SSH到各種節(jié)點(diǎn)上查看日志配置排查問題。
4.自研Filebeat exporter,接入Prometheus,采集上報(bào)自身metrics數(shù)據(jù)。
通過立體化的監(jiān)控增強(qiáng),大大方便了我們問題的排查,減少了運(yùn)維和人力成本,也更確保了服務(wù)的穩(wěn)定性。
- Golang的性能優(yōu)化與調(diào)優(yōu) -
對應(yīng)?的性能優(yōu)化是個永恒的話題,盡管“過早優(yōu)化是萬惡之源”,實(shí)際開發(fā)過程中還是需要時刻保持優(yōu)化的意識。很多時候,我們看過太多GC原理、內(nèi)存優(yōu)化、性能優(yōu)化,卻往往在寫完代碼、做完一個項(xiàng)目的時候,無從下手。實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。所以,親自動手去排查、摸索,才是提升姿勢水平、找到關(guān)鍵問題的捷徑。對于云原生日志采集系統(tǒng)也是如此。
好消息是,對于性能優(yōu)化,Golang貼心的為我們提供了三把鑰匙:
1. go benchmark
2. go pprof
3. go trace
這些鑰匙在日志采集性能優(yōu)化場景下同樣有效。下面舉個簡單的示例。
以sync.Pool為例,sync.Pool一般用于保存和復(fù)用臨時對象,減少內(nèi)存分配,降低GC壓力。有很多的應(yīng)用場景,例如號稱比Golang官方Http快10倍的FastHttp大量使用了sync.Pool,F(xiàn)ilebeat使用sync.Pool將批量日志數(shù)據(jù)聚合成Batch分批發(fā)送,Nginx-Ingress-controller渲染生成Nginx配置時,也使用sync.Pool優(yōu)化渲染效率。輕舟的日志controller Ripple也同樣使用了sync.Pool去優(yōu)化渲染Filebeat配置時的性能。
首先,使用go benchmark壓測一下未使用sync.Pool時通過go template渲染出Filebeat配置的方法。
可以看到結(jié)果顯示的每次執(zhí)行方法的時間,和分配的內(nèi)存。
然后將go benchmark生成的profile文件,使用go pprof觀察一下整體的性能數(shù)據(jù)。
go pprof實(shí)際上有很多的數(shù)據(jù)可以供我們觀察,這里僅展示內(nèi)存的分配信息。可以看到benchmark在這段時間內(nèi)共申請了超過5G的內(nèi)存。
接著,我們使用go trace查看壓測過程中的goroutine,堆內(nèi)存,GC等信息。
這里僅截取600ms至700ms的時間段,可以很清楚地在圖中看到這100ms內(nèi)發(fā)生了170次的GC。
同樣的方法和步驟,壓測一下使用sync.Pool后的結(jié)果。
分配的內(nèi)存總量減小到了160MB,而相同的時間段內(nèi)GC次數(shù)也減少到了5次。優(yōu)化效果十分明顯。
當(dāng)然,Golang的作用不僅于此。從Docker到Kubernetes,從Istio到Knative,基于Golang的開源項(xiàng)目已然是云原生生態(tài)體系的主力軍,Golang的簡潔高效也不斷吸引著新的項(xiàng)目采用它作為開發(fā)語言。
對于輕舟微服務(wù)平臺,除了使用Golang寫Filebeat插件、開發(fā)日志采集的controller,我們還有很多基于Golang的組件,比如Service Mesh、容器云等。
- 總結(jié)與展望 -
在云原生時代,日志做為可觀測性的一部分,是排查、解決問題的基礎(chǔ),也是后續(xù)大數(shù)據(jù)分析處理的開始。
在這個領(lǐng)域,雖然有很多開源項(xiàng)目,卻仍然沒有一個強(qiáng)力而統(tǒng)一的日志采集agent,或許這種百花齊放的景象會一直持續(xù)下去。所以,輕舟團(tuán)隊(duì)自研日志agent Ripple的設(shè)計(jì)中也提出了更多的抽象,保留了對接其他日志采集agent的能力。后續(xù)我們計(jì)劃支持更多的日志采集agent,打造一個更加豐富、健壯的云原生日志采集系統(tǒng)。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!