基于 Golang 的云原生日志采集服務(wù)設(shè)計(jì)與實(shí)踐
在這股技術(shù)潮流之中,網(wǎng)易推出了輕舟微服務(wù)云平臺(tái),集成了微服務(wù)、Servicemesh、容器云、DevOps等,已經(jīng)廣泛應(yīng)用于公司集團(tuán)內(nèi)部,同時(shí)也支撐了很多外部客戶的云原生化改造和遷移。

但是在云原生容器化環(huán)境下,日志的采集又變得有點(diǎn)不同。

- 容器日志采集的痛點(diǎn) -
傳統(tǒng)主機(jī)模式
業(yè)務(wù)日志直接輸出到宿主機(jī)上,服務(wù)運(yùn)行在固定的節(jié)點(diǎn)上,手動(dòng)或者拿自動(dòng)化工具把日志采集agent部署在節(jié)點(diǎn)上,加一下agent的配置,然后就可以開始采集日志了。同時(shí)為了方便后續(xù)的日志配置修改,還可以引入一個(gè)配置中心,用來下發(fā)agent配置。
Kubernetes環(huán)境
一個(gè)Kubernetes node節(jié)點(diǎn)上有很多不同服務(wù)的容器在運(yùn)行,容器的日志存儲(chǔ)方式有很多不同的類型,例如stdout、hostPath、emptyDir、pv等。由于在Kubernetes集群中經(jīng)常存在Pod主動(dòng)或者被動(dòng)的遷移,頻繁的銷毀、創(chuàng)建,我們無法和傳統(tǒng)的方式一樣人為的給每個(gè)服務(wù)下發(fā)日志采集配置。另外,由于日志數(shù)據(jù)采集后會(huì)被集中存儲(chǔ),所以查詢?nèi)罩緯r(shí),可以根據(jù)namespace、pod、container、node,甚至包括容器的環(huán)境變量、label等維度來檢索、過濾很重要。
以上都是有別于傳統(tǒng)日志采集配置方式的需求和痛點(diǎn),究其原因,還是因?yàn)閭鹘y(tǒng)的方式脫離了Kubernetes,無法感知Kubernetes,更無法和Kubernetes集成。
隨著最近幾年的迅速發(fā)展,Kubernetes已經(jīng)成為容器編排的事實(shí)標(biāo)準(zhǔn),甚至可以被認(rèn)為是新一代的分布式操作系統(tǒng)。在這個(gè)新型的操作系統(tǒng)中,controller的設(shè)計(jì)思路驅(qū)動(dòng)了整個(gè)系統(tǒng)的運(yùn)行。controller的抽象解釋如下圖所示:

基于這個(gè)思路,對(duì)于日志采集來說,一個(gè)服務(wù)需要采集哪些日志,需要什么樣的日志配置,是用戶的期望,而這一切,就需要我們開發(fā)一個(gè)日志采集的controller去實(shí)現(xiàn)。


- 探索與架構(gòu)設(shè)計(jì) -
日志采集agent選型
Logstash基于JVM,分分鐘內(nèi)存占用達(dá)到幾百M(fèi)B甚至上GB,有點(diǎn)重,首先被我們排除。
Fluentd背靠CNCF看著不錯(cuò),各種插件也多,不過基于Ruby和C編寫,對(duì)于我們團(tuán)隊(duì)的技術(shù)棧來說,還是讓人止于觀望。
雖然Fluentd還推出了存粹基于C語(yǔ)言的Fluentd-bit項(xiàng)目,內(nèi)存占用很小,看著十分誘惑,但是使用C語(yǔ)言和不能動(dòng)態(tài)reload配置,還是無法令人親近。
Loki推出的時(shí)間不久,目前還是功能有限,而且一些壓測(cè)數(shù)據(jù)表明性能不太好,暫持觀望。
Filebeat和Logstash、Kibana、Elasticsearch同屬Elastic公司,輕量級(jí)日志采集agent,推出就是為了替換Logstash,基于Golang編寫,和我們團(tuán)隊(duì)技術(shù)棧完美契合,實(shí)測(cè)下來個(gè)方面性能、資源占用率都比較優(yōu)秀,于是成為了我們?nèi)罩静杉痑gent第一選擇。
agent集成方式

- 整體架構(gòu) -
選擇Filebeat作為日志采集agent,集成了自研的日志controller后,從節(jié)點(diǎn)的視角,我們看到的架構(gòu)如下所示:

日志平臺(tái)下發(fā)具體的CRD實(shí)例到Kubernetes集群中,日志controller Ripple則負(fù)責(zé)從Kubernetes中List&Watch Pod和CRD實(shí)例。 通過Ripple的過濾、聚合最終生成一個(gè)Filebeat的input配置文件,配置文件里描述了服務(wù)的采集Path路徑、多行日志匹配等配置,同時(shí)還會(huì)默認(rèn)把例如PodName、Hostname等配置到日志元信息中。 Filebeat則根據(jù)Ripple生成的配置,自動(dòng)reload并采集節(jié)點(diǎn)上的日志,發(fā)送至Kafka或者Elasticsearch等。
Ripple能感知到Pod掛載的日志Volume,不管是docker Stdout的日志,還是使用HostPath、EmptyDir、Pv存儲(chǔ)日志,均可以生成節(jié)點(diǎn)上的日志路徑,告知Filebeat去采集。
Ripple可以同時(shí)獲取CRD和Pod的信息,所以除了默認(rèn)給日志配置加上PodName等元信息外,還可以結(jié)合容器環(huán)境變量、Pod label、Pod Annotation等給日志打標(biāo),方便后續(xù)日志的過濾、檢索查詢。除此之外,我們還給Ripple加入了日志定時(shí)清理,確保日志不丟失等功能,進(jìn)一步增強(qiáng)了日志采集的功能和穩(wěn)定性。

- 基于 Filebeat 的實(shí)踐 -
功能擴(kuò)展
Filebeat目前只提供了像elasticsearch、Kafka、logstash等幾類output客戶端,如果我們想要Filebeat直接發(fā)送至其他后端,需要定制化開發(fā)自己的output。同樣,如果需要對(duì)日志做過濾處理或者增加元信息,也可以自制processor插件。無論是增加output還是寫個(gè)processor,F(xiàn)ilebeat提供的大體思路基本相同。一般來講有3種方式:
直接fork Filebeat,在現(xiàn)有的源碼上開發(fā)。
output或者processor都提供了類似Run、Stop等的接口,只需要實(shí)現(xiàn)該類接口,然后在init方法中注冊(cè)相應(yīng)的插件初始化方法即可。
當(dāng)然,由于Golang中init方法是在import包時(shí)才被調(diào)用,所以需要在初始化Filebeat的代碼中手動(dòng)import。
復(fù)制一份Filebeat的main.go,import我們自研的插件庫(kù),然后重新編譯。
本質(zhì)上和方式1區(qū)別不大。
Filebeat還提供了基于Golang plugin的插件機(jī)制,需要把自研的插件編譯成.so共享鏈接庫(kù),然后在Filebeat啟動(dòng)參數(shù)中通過-plugin指定庫(kù)所在路徑。
不過實(shí)際上一方面Golang plugin還不夠成熟穩(wěn)定,一方面自研的插件依然需要依賴相同版本的libbeat庫(kù),而且還需要相同的Golang版本編譯,坑可能更多,不太推薦。
如果想要了解更多關(guān)于Filebeat的設(shè)計(jì),可以參考這篇文章。 (https://juejin.im/post/6844903888726786055)

- 立體化監(jiān)控 -
但是,真正的困難是在業(yè)務(wù)方實(shí)際使用之后,各種采集不到日志,多行日志配置或者采集二進(jìn)制大文件導(dǎo)致Filebeat oom等問題接踵而至。我們又投入了更多的時(shí)間在對(duì)Filebeat和日志采集的全方位監(jiān)控上,例如:
接入輕舟監(jiān)控平臺(tái),有磁盤io、網(wǎng)絡(luò)流量傳輸、內(nèi)存占用、cpu使用、pod事件報(bào)警等,確保基礎(chǔ)監(jiān)控的完善。 加入了日志平臺(tái)數(shù)據(jù)全鏈路延遲監(jiān)控。 采集Filebeat自身日志,通過自身日志上報(bào)哪些日志文件開始采集,什么時(shí)候采集結(jié)束,避免每次都需要ssh到各種節(jié)點(diǎn)上查看日志配置排查問題。 自研Filebeat exporter,接入prometheus,采集上報(bào)自身metrics數(shù)據(jù)。

- Golang 的性能優(yōu)化與調(diào)優(yōu) -
從Docker到Kubernetes,從Istio到Knative,基于Golang的開源項(xiàng)目已然是云原生生態(tài)體系的主力軍,Golang的簡(jiǎn)潔高效也不斷吸引著新的項(xiàng)目采用它作為開發(fā)語(yǔ)言。
但是很多時(shí)候,我們看過太多GC原理、內(nèi)存優(yōu)化、性能優(yōu)化,卻往往在寫完代碼、做完一個(gè)項(xiàng)目的時(shí)候,無從下手。 實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。 所以,親自動(dòng)手去排查、摸索,才是提升姿勢(shì)水平、找到關(guān)鍵問題的捷徑。
對(duì)于性能優(yōu)化,Golang貼心的為我們提供了三把鑰匙:
go benchmark go pprof go trace
以sync.Pool為例,sync.Pool一般用于保存和復(fù)用臨時(shí)對(duì)象,減少內(nèi)存分配,降低GC壓力。有很多的應(yīng)用場(chǎng)景,例如號(hào)稱比Golang官方Http快10倍的FastHttp大量使用了sync.Pool,F(xiàn)ilebeat使用sync.Pool將批量日志數(shù)據(jù)聚合成Batch分批發(fā)送,Nginx-Ingress-controller渲染生成nginx配置時(shí),也使用sync.Pool優(yōu)化渲染效率。我們的日志controller Ripple也同樣使用了sync.Pool去優(yōu)化渲染Filebeat配置時(shí)的性能。







- 總結(jié)與展望 -
來源:
blog.csdn.net/m0_38110132/article/details/81481454
