美圖在大型容器化平台日誌的實踐(一)選型思考篇
為了降低業務成本,提高伺服器資源利用率,獲得快速伸縮及彈性計算的能力,美圖在今年( 2017)早些時候,就已經開始了容器化平台的建設,從服務編排平台的選型( Kubernetes, Mesos 等),服務的調度和負載均衡,網路方案的確定,到業務容器日誌的收集等方方面面,我們都做了非常努力的探索。本文主要分享容器日誌的收集相關方面的探索,計劃分為三篇文章。
任勇全,先後在新浪微博,奇虎 360 從事基礎架構研發工作,目前在美圖擔任高級技術經理,專註於後端技術研發,包括消息通信,分散式存儲及容器虛擬化等方向。
選型思考篇:第一篇主要介紹當前日誌收集的背景,以及面臨的問題,這直接決定了我們最終採取的日誌收集方案;
架構篇:第二篇計劃給出我們日誌收集系統的架構;
實踐篇:第三篇討論在現實情況下,面對各種歷史遺留問題,如何以過渡的方式,一步步推進日誌收集方案的落地。
如果對容器化平台的其他方面感興趣,也請留意後續文章,美圖架構的小夥伴也會在適當的時候,將我們做的一些探索和心得分享出來。
本文是容器平台日誌收集方案的第一篇,主要介紹了對日誌輸出和日誌收集的一些思考,這些思考最終促成了我們日誌收集的方案和方向,讓我們對日誌收集功能哪些可以妥協、哪些必須堅持有了參考依據和指導思想。
容器日誌收集的難點不在於做出獨創性的方案,而是基於大眾都知道的各種方案,有一個具備清晰參考依據的選型,然後克服歷史遺留問題,不同業務的複雜性等各種困難,堅定不移的執行下去。相信在調研容器日誌收集問題的同學,對應該採取的方案和未來的發展方向都有自己的思考,只是這些思考可能還不夠清晰,或者在面臨真實的項目的時候,無法說服項目開發人員;如果你面臨這些問題,這篇文章可能就是為你準備的。如果你只是想簡單了解容器化的日誌收集是如何做的,這可能要等我們第二篇文章。
本篇文章更側重於「為什麼」這麼做,而不是「怎麼做」。如果你已經有豐富的容器化建設的經驗,我們非常希望您在讀完本文後,對於我們失誤的地方予以指正,或分享給我們更好的建議或方案。
背景
隨著系統規模越來越大,代碼邏輯越來越複雜,對服務端的一個請求,可能經過多個模塊和處理邏輯,在異常發生時,如何排查代碼錯誤也會變得非常困難,當前比較有效和認可的做法就是為程序列印運行日誌,將程序的關鍵路徑,關鍵數據記錄下來,以便日後排查問題時作為參考。
日誌可以分為多個級別,比如 debug, info, warn, error 等, debug 比 info 級別具有更豐富的信息,更細粒度的日誌列印。線上運行時,為了避免過多日誌影響服務性能,我們通常設置日誌級別為 error 或 info。
對於資源訪問或 HTTP 介面,我們還期望能確認客戶端的請求是否被服務端接收到,這是 error 級別的運行日誌無法提供的,因為一個客戶端請求在服務端沒有任何錯誤,可能是服務端根本沒有接收到客戶端的請求;我們通常不會為了解決這個問題而開啟 debug 級別的日誌(影響性能),而是將這種類型的日誌單獨抽象為訪問日誌( access log)。訪問日誌一般記錄了客戶端的請求的資源或介面,請求耗時,響應結果(狀態碼)等,而這些數據除了用來排查問題,還可以用來統計服務的運行狀況和性能表現,比如 QPS,平均響應時間,錯誤率等等。
隨著大數據的興起,我們對數據的分析解讀能力越來越強,日誌作為原始數據則體現出了更大的價值,運行日誌可以交由大數據系統進行數據挖掘,以智能判斷服務的運行狀況,出錯時及時觸發報警等。訪問日誌則更適合大數據系統做統一的統計分析,比如哪些資源訪問較多,資源訪問耗時是多少,狀態碼分布規律是怎樣的等等。另外,大數據本身也衍生了更多數據方面的需求,比如用戶行為分析,敏感詞過濾,反作弊反垃圾等等,為了提供這些數據,我們通常也是以日誌的形式將這些信息列印出來。
當前的互聯網系統是高度模塊化的服務,從前端服務介面,到後端的處理邏輯可能跨越多個遠程調用,傳統的運行日誌是無法將遠程調用信息像本地調用棧一樣列印出來,通常的做法是我們在服務的入口生成 traceid,並在每個模塊的關鍵路徑列印日誌,列印的日誌都攜帶最初生成的 traceid ;這樣則可以通過 traceid 將所有調用流程關聯起來。另一種方式是大家接入專門做 trace 服務的 SDK,將關鍵調用信息通過 SDK 發送到分散式 trace 系統。
上面我大體列舉了四種類型的日誌,真實的生產環境中還會有更多其他類型的日誌,比如操作系統的 dmesg,語言運行時或 VM 的 gc 日誌,甚至是為了某個特定用途而列印的日誌等等。可以看到,上述日誌其實是不同時代的產物,產生時面臨的環境、要解決的問題隨著時間也正在慢慢改變;這給我們帶來很多思考:所有的日誌都是必要的嗎?日誌列印的方式適合當前這個時代嗎?以前的需求現在還成立嗎?能否站在當下的角度抽象為更精簡的幾種類型的日誌(而不是四種),按什麼維度去抽象和精簡? 在這裡我無法對所有問題給出答案,但這些問題在我們設計容器化日誌收集方案時,卻可以提醒我們重新審視當前的日誌列印和收集方式,避免落入固化思維。雖然無法對所有問題解答,但有些方面我們已經可以明顯的察覺到變化。
面向機器而不是面向人
這可能是傳統日誌和大數據時代日誌最明顯的差別,傳統的運行日誌和訪問日誌,基本是為開發或運維提供排查問題的信息,是面向人去設計的,人腦天生適合處理非結構化的數據,因此日誌信息非常隨意,可能只是一句話,關聯的數據項和格式也不固定,比如:
26109 2017-08-03 07:35:49 [DEBUG] server.go:122 Callback PostSubscribe SubTopics:<Topic:" / push / 251ac7af-8cdc-4b23-a5c6-faa6c0d4925f" QoS:1 > ClientID:"251ac7af-8cdc-4b23-a5c6-faa6c0d4925f"
這條日誌是從推送服務線上運行日誌摘取的,其特點很明顯,日誌行本身有一定的格式( pid,日誌,日誌級別等),但日誌信息本身卻是程序員(我)隨意去寫的,「 Callback PostSubscribe」我們知道是調用了回調函數,關聯的信息格式看起來是冒號分割的 key-value,但 SubTopics 這個欄位的 value 很明顯又有自己的格式,比如可以理解為尖括弧括起來的對象( key-object),那麼問題是這裡為什麼是尖括弧呢,為什麼不是大括弧圓括弧呢?或者我們有沒有統一的標準來表達這種 key-object 的的語義呢?答案是有的,比如 JSON 就可以做到。 大數據時代,處理日誌信息的不再是人工,而是代碼,只有結構化的數據才會便於代碼處理。
服務與運行環境解耦
這可能是很多人還未曾察覺到的變化, SaaS、容器化、微服務等技術的發展,促使我們交付的成品不是一個獨立的可運行的系統,而是高度模塊化的,相互協作的服務。
容器的出現,提供了操作系統運行時的抽象,服務可以不用關心操作系統之間的差異,也就是將服務跟真實的運行環境解耦。 Docker 容器化的服務,可以作為一個獨立的單位,部署在任何支持 Docker 容器的操作系統平台。
因此我們在做服務開發時,應該避免對運行環境的依賴。 一個思路是,我們將日誌抽象為一個流,服務只將日誌信息輸出到這個流,不應該去關心日誌存儲的位置 (12factor https://12factor.net/zh_cn/logs)
目前這種思路已經被廣泛應用,比如程序運行日誌輸出到 stdout(標準輸出),然後由運行環境截獲,比如運行在實體機上由 journald 截獲,運行在容器化平台,由 Docker 的 log driver 截獲。而服務只是將日誌列印出來,不需要關係運行在哪個環境,是否需要落到磁碟等等。
下圖就是 journald 截獲的運行服務的日誌:
傳統的日誌輸出
非結構化
上面提到,日誌產生的背景,是便於人工排查問題,因此日誌的格式一般是非結構化的,便於人類閱讀的;而現在一個功能可能由多個服務組件協作完成,這些服務組件都是遠程調用關係,排查問題已經不是簡單的單機查看日誌,而是綜合整個系統的日誌去分析。這樣不可避免,我們需要將日誌收集整合,並通過專業的分析系統進行分析和展示;比如業界通常使用的 ELK 就是實現這個功能。
輸出到文件
輸出到文件使得服務的開發跟運行環境耦合,開發者需要關心日誌輸出的路徑,或者,是由運維根據實際的運行狀況去配置路徑。這在我們人工部署的時代是可行的,容器化後,服務的運行環境則變成未知的,不同的服務可能運行在同一個物理機上,日誌的路徑可能產生衝突,而為了解決衝突,我們又需要退化到人工規劃路徑的時代;或者通過一些手段,將容器的唯一標識作為路徑的一部分,這樣實際會造成反向依賴,運行服務的容器外獲取到標識信息,並創建目錄,通過一定的手段(配置或環境變數)傳遞給容器內的服務。之所有會產生反向依賴,是因為容器內運行的服務,需要感知其底層的操作系統環境。
分多個文件
我們線上習慣將不同類型的日誌列印到不同的文件,這種設計思想仍然是為人服務的,另一方面,是由於我們的日誌是非結構化的,也不具備統一的格式,不同的格式混雜在一起使得機器解析也變得困難。這種情況,只有我們從內心接納了將日誌輸出到統一的流的概念,才能改善。比如:為了讓同一個流的日誌具備統一的格式,我們可能選擇用 JSON 作為日誌輸出格式,為了在流內區分不同的組件,我們會將一些標識性的信息,比如 pid,進程名,服務名等輸出到日誌。 另外,我們習慣將運行日誌的多個級別分別列印到不同的文件,這也是純粹為了人的需求而選擇的方案,在面向機器的時代,這是日誌可以合併到一起,便於收集。
傳統日誌的收集
傳統的日誌收集 agent 並沒有嘗試解決日誌從面向人到到面向機器的轉變。而是隨著日誌的發展,去不斷調整和適應,比如配置複雜的解析規則以應對不同格式的日誌解析( Nginx, httpd 等),實現按照匹配規則來匹配日誌文件等等。由於不同的日誌收集方,都傾向根據自己的特殊需求提供日誌收集的 agent,導致同一套服務會隨同部署多套 agent 來收集日誌。在這種架構下繼續發展,衍生出了更加耦合的 agent 設計方案,比如為了避免集中解析日誌格式, agent 會傾向在收集端做解析,通過分散式的日誌處理減輕日誌中心的壓力。
在面向機器的時代,隨著微服務化架構的流行,基於傳統的解決方案繼續演進,只會讓日誌收集變得越來越複雜,我們需要重新思考日誌收集 agent 的作用,比如,劃定單一的功能, agent 只做收集的事情,不關心具體業務欄位的解析,這樣可以抽象出一個統一的 agent;將日誌內容結構化,減輕解析日誌文本的壓力;日誌解析由日誌使用方實現,這些原始的結構化的數據是有保存價值的,不同的日誌使用方可以根據自己的需求去解讀同一份日誌(比如根據訪問日誌分析用戶行為,或統計介面性能)。
容器化面臨的挑戰
從上面分析可以看到,傳統的日誌輸出及收集方式,已經不適應大數據,微服務的發展趨勢,服務容器化將這個矛盾推向高峰。 Kubernetes 將容器的運行環境抽象,並對服務進行編排和調度,不同的服務可能被調度到不同的物理機節點上;我們無法人工針對某個業務去部署和配置日誌收集 agent。
上圖 ( 來自 fluentd.org) 描述了我們傳統的日誌收集方案衍生出來的複雜性,容器平台作為基礎運行環境,需要為所有業務提供統一的日誌收集方案,如果繼續沿用傳統的思路,將面臨諸多挑戰。
傳統日誌是面向人設計,而容器平台避免人來直接查看日誌文件
傳統日誌輸出關心運行環境,而容器平台抽象運行環境
傳統日誌收集將日誌輸出到多個文件,而容器平台期望統一截獲日誌流
傳統日誌收集將採集和解析耦合,而容器平台不關心業務如何解讀日誌
從上述矛盾(或挑戰)可以看到,如果不對日誌系統的設計思路革新,推進容器化後將會有各種複雜的情況讓我們疲於應付。我們期望日誌的輸出具有統一的結構化的格式,服務將日誌輸出到標準輸出或標準錯誤 (12factor),不需要關心日誌如何落地。運行環境截獲日誌並由統一的日誌收集 agent 將日誌過濾,處理,採集,通過日誌傳輸組件(比如 kafka)分發給不同處理程序。
我們將上述理想情況稱為現代日誌收集方案,因此我們的目標則是從傳統日誌收集方案逐步轉化為現代日誌收集方案。由於日誌是各個業務必備的組件,其改造過程註定複雜且耗時,中間需要過渡方案來適應不同的業務場景和使用需求,因此我們期望抽象出「統一日誌層」將各式各樣的日誌來源適配起來,發送到可能的,功能不同的日誌中心
統一日誌層( unified-logging-layer)
統一日誌層其實是 fluentd 提出來的概念:
Unified Logging Layer: Turning Data into Action (https://www.fluentd.org/blog/unified-logging-layer)
其基本思想是將日誌抽象為 Producer 和 Consumer。 Producer 與 Consumer 可以有多種多樣, fluentd 通過插件系統來適配不同的 Producer 和 Consumer。
關於 fluentd 本身,我們這裡就不在贅述,有興趣的同學可以參考 fluentd 的官方文檔 https://docs.fluentd.org/v0.12/articles/quickstart
上圖(來自 fluentd.org)可以看到, fluentd 抽象出日誌的生產和消費之間的統一的一層,將 NxM 的複雜組合,變成 N+M。現實情況下,我們無法實現所有組件都達到「現代日誌收集方案」的標準,並且在達到目標前,必定有各種各樣的過渡階段, fluentd 提供的統一日誌層恰好為我們的過渡提供極大的靈活性,又能作為最終的「現代日誌收集方案」,因此我們考慮引入 fluentd 作為容器化平台的日誌收集組件。
fluentd 是一個成熟的開源項目,也是 Kubernetes 平台最常用的日誌收集組件,其已經實現了「統一日誌層」的設計思想,接下來的任務是考慮如何跟我們的業務,容器平台集成,如何落地 ...
總結
本文內容雖然比較長,但其核心思想卻非常清晰,為了實現現代化的日誌收集方案,我們有兩個目標需要達成:
將日誌列印到標準輸出或標準錯誤,實現服務與運行環境解耦
大力推動業務日誌結構化(比如 JSON),以適應日誌輸出從面向人,到面向機器的轉變。
上述兩個目標說起來比較容易,但要真正落地,還有諸多困難要克服,我們下一篇文章會討論基於上述思路,如何設計我們日誌收集方案的架構,如何實現集群級別的日誌分組,如何適配不同的日誌中心等,非常期待到時候跟大家一起交流。
號外
美圖架構,專註於虛擬化、流媒體、雲儲存、通訊等基礎設施建設領域,現急需相關領域愛好者加入,工作地點可自由選擇北京、廈門、深圳。目前緊急崗位如下:
虛擬化底層研發工程師
Go 開發工程師
音視頻編解碼研究人員
有興趣者請聯繫: yt@meitu.com or ryq@meitu.com
※UC瀏覽器快開之路:如何應對大型APP優化工作周而復始難題?
※為什麼LinkedIn放棄MySQL slowlog,轉用基於網路層的慢查詢分析器?
※Web伺服器如何實現高吞吐低延遲?Dropbox從操作系統到應用層優化指南
※全方位解密直播短視頻架構:美圖互聯網技術沙龍來北京了
※測試分散式系統的線性一致性
TAG:高可用架構 |