9 個技巧,解決 K8s 中的日誌輸出問題

作者 | 元乙  阿里雲存儲服務技術專家

導讀:近年來,越來越多的同學諮詢如何爲 Kubernetes 構建一個日誌系統,或者是來求助在此過程中遇到一系列問題如何解決,授人以魚不如授人以漁,於是作者想把這些年積累的經驗以文章的形式發出來,讓看到文章的同學少走彎路。K8s 日誌系列文章內容偏向落地實操以及經驗分享,且內容會隨着技術的迭代而不定期更新,本文爲該系列文章的第 3 篇。

第一篇:《6 個 K8s 日誌系統建設中的典型問題,你遇到過幾個?》

第二篇:《一文看懂 K8s 日誌系統設計和實踐》

前言

在上一篇文章《一文看懂 K8s 日誌系統設計和實踐》中,主要和大家介紹從全局維度考慮如何去構建 K8s 中的日誌系統,本文我們將從實踐角度出發來一步步構建 K8s 中的日誌監控體系。

構建日誌系統的第一步是如何去產生這些日誌,而這也往往是最繁雜最困難的一步。

2009 年阿里雲春節上班第一天,在北京一間連暖氣都沒有的辦公室裏,一幫工程師一邊口呼白氣,一邊敲出了“飛天”的第一行代碼。“飛天”作爲阿里雲的核心技術平臺,其英文名 Apsara——來自吳哥王朝的阿僕薩羅飛天仙女的名字。

阿里雲飛天系統的第一行代碼就是爲了編寫一個日誌系統,而現在 apsara logging 的日誌庫應用在飛天所有的系統中,包括盤古、女媧、伏羲、洛神...

1.png

Kubernetes 中日誌重要性

通常日誌最基礎的作用是記錄程序的運行軌跡,在此之上會衍生出非常多的功能,例如線上監控、告警、運營分析、安全分析等等(詳情可以參見第一篇文章《6 個 K8s 日誌系統建設中的典型問題,你遇到過幾個?》,這些功能反過來也對日誌具備一定的要求,我們需要儘可能的將日誌規範化,以減少收集、解析、分析的代價。

在 Kubernetes 中,環境的動態性很強,日誌基本上都是易失的,因此需要實時將日誌採集到中心的存儲中,爲了配合日誌採集,對於日誌的輸出、採集會有更多的要求。

下述我們列舉了 Kubernetes 中,日誌輸出的常見注意事項(其中標記 (*)的是 Kubernetes 中特有的項目):

  1. 如何選擇日誌等級
  2. 日誌內容規範
  3. 合理控制日誌輸出量
  4. 選擇多種日誌輸出目標
  5. 控制日誌性能消耗
  6. 如何選擇日誌庫
  7. 日誌形態選擇(*)
  8. 日誌是否落盤以及落盤介質(*)
  9. 如何保證日誌存儲週期(*)

如何選擇日誌等級

日誌等級是用來區分日誌對應事件嚴重程度的說明,這是所有日誌中必須具備的一個選項。通常日誌會分爲 6 個不同的等級:

  • FATAL(致命):用來輸出非常嚴重或預期中不會發生的錯誤,遇到此種錯誤應當立即報警並人工介入處理;
  • ERROR (錯誤):非預期中的錯誤,此種錯誤可能導致部分系統異常但不會影響核心業務和系統正常運行;
  • WARN(警告):潛在的危險或值得關注的信息(比較核心的路徑);
  • INFO(信息):應用執行過程中的詳細信息,一般通過該信息可以看到每個請求的主要執行過程;
  • DEBUG(調試):用於線下調試的日誌信息,用於分析應用執行邏輯,線上應用切勿開啓;
  • TRACE(跟蹤):輸出最細緻的運行軌跡,可能包含涉及的數據內容。

作爲程序員,一定要合理設置日誌等級,個人在開發過程中總結了以下幾點經驗:

  1. FATAL 類型日誌一定是非常嚴重的錯誤、需要人工處理的場景打印的;
  2. ERROR 和 WARNING 的區別很多程序員難以選擇,可以從告警角度考慮:ERROR 爲一般需要告警,WARNING 爲不需要;
  3. 日誌等級一方面是爲了能夠表示日誌的嚴重程度,另一方面也是爲了控制應用程序的日誌輸出量,通常線上只能打開 INFO 或 WARN 的日誌;
  4. DEBUG 日誌可以多打,方便分析問題;
  5. 所有用戶請求日誌,必須記錄;
  6. 對於不確定的外部系統調用,日誌需儘可能覆蓋周全;
  7. 程序中的日誌庫需要具備運行期間變更日誌等級的能力,方便在遇到問題需要分析時臨時更改日誌等級;
  8. 通常在新功能上線,涉及的日誌可適當提升一個等級,方便實時觀察和監控,待穩定後再調整到正常(記得加上註釋,方便改回來)。

日誌內容規範

通常在沒有約束的情況下,程序員的發揮天馬行空,各種日誌內容都會出現,這些只有開發自己才能看懂的日誌很難進行分析和告警。因此我們需要一個日誌頂向下的規範來約束項目中的開發人員,讓所有的日誌看起來是一個人打印的而且是易於分析的。

日誌的字段

日誌中通常必備的字段有:Time、Level、Location。對於特定模塊/流程/業務,還需要有一些 Common 的字段,例如:

  1. 如果使用 Trace 系統,可以把 TraceID 附加到日誌中;
  2. 固定的流程需要附加對應的字段,例如訂單的生命週期中,一定要有訂單號、用戶 ID 等信息,這些信息可以通過 Context 附加到對應流程的日誌實例上;
  3. HTTP 請求需要記錄:URL、Method、Status、Latency、Inflow、OutFlow、ClientIP、UserAgent 等,詳情可以參考 Nginx日誌格式
  4. 如果多個模塊的日誌都打印到同一個流/文件中,必須有字段標識模塊名。

日誌的字段規約最好由運維平臺/中間件平臺自頂向下推動,約束每個模塊/流程的程序員按照規定打印日誌。

日誌表現形式

通常我們建議使用 KeyValue 對形式的日誌格式,比如我們阿里的飛天日誌庫採用的就是這種形式:

[2019-12-30 21:45:30.611992]    [WARNING]       [958] [block_writer.cpp:671]  path:pangu://localcluster/index/3/prom/7/1577711464522767696_0_1577711517     min_time:1577712000000000       max_time:1577715600000000       normal_count:27595      config:prom     start_line:57315569     end_line:57343195       latency(ms):42  type:AddBlock

KeyValue 對的日誌可以完全自解析且易於理解,同時便於日誌採集時自動解析。

另外推薦的是 JSON 日誌格式,支持以 JSON 格式輸出的日誌庫很多,而且大部分的日誌採集 Agent 都支持 JSON 格式的日誌收集。

{"addr":"tcp://0.0.0.0:10010","caller":"main.go:98","err":"listen tcp: address tcp://0.0.0.0:10010: too many colons in address","level":"error","msg":"Failed to listen","ts":"2019-03-08T10:02:47.469421Z"}

注意:絕大部分場景不建議使用非可讀的日誌格式(例如 ProtoBuf、Binlog 等)。

單條日誌換行問題

非必要情況下,儘量不要一條日誌輸出成多行,這種對於採集、解析和索引的代價都比較高。

合理控制日誌輸出量

日誌的輸出量直接影響到磁盤使用以及對於應用的性能消耗,日誌太多不利於查看、採集、分析;日誌太少不利於監控,同時在出現問題的時候沒辦法調查。

一般線上應用需合理控制日誌的數據量:

  1. 服務入口的請求和響應日誌沒有特殊原因都要輸出並採集,採集的字段可以根據需求調整;
  2. 錯誤日誌一般都要打印,如果太多,可以使用採樣方式打印;
  3. 減少無效日誌輸出,尤其是循環中打印日誌的情況需儘量減少;
  4. 請求型的日誌(比如 Ingress、Nginx 訪問日誌)一般不超過 5MB/s(500 字節每條,不超過 1W/s),應用程序日誌不超過 200KB/s(2KB 每條,不超過 100 條/s)。

選擇多種日誌輸出目標

建議一個應用不同類型的日誌輸出到不同的目標(文件),這樣便於分類採集、查看和監控。例如:

  1. 訪問日誌單獨放到一個文件,如果域名不多,可以按照一個域名一個文件的形式;
  2. 錯誤類的日誌單獨放一個文件,單獨配置監控告警;
  3. 調用外部系統的日誌單獨放一個文件,便於後續對賬、審計;
  4. 中間件通常都由統一的平臺提供,日誌一般單獨打印一個文件。

控制日誌性能消耗

日誌作爲業務系統的輔助模塊,一定不能影響到業務正常的工作,因此日誌模塊的性能消耗需要單獨額外注意,一般在選擇/開發日誌庫時,需要對日誌庫進行性能測試,確保正常情況下日誌的性能消耗不超過整體 CPU 佔用的 5%。

注意:一定要確保日誌打印是異步的,不能阻塞業務系統運行。

如何選擇日誌庫

開源的日誌庫非常多,基本每個語言都有數十種,選擇一個符合公司/業務需求的日誌庫需要精挑細選,有一個簡單的指導原則是儘可能使用比較流行的日誌庫的穩定版本,入坑的機率要小一點。例如:

  1. Java 使用 Log4J、LogBack;
  2. Golang 使用 go-kit
  3. Python 默認集成的日誌庫大部分場景都夠用,建議閱讀一下 CookBook
  4. C++ 推薦使用 spdlog,高性能、跨平臺。

日誌形態選擇

在虛擬機/物理機的場景中,絕大部分應用都以文件的形式輸出日誌(只有一些系統應用輸出到 syslog/journal);而在容器場景中,多了一個標準輸出的方式,應用把日誌打到 stdout 或 stderr 上,日誌會自動進入到 docker 的日誌模塊,可以通過 docker logs 或 kubectl logs 直接查看。

容器的標準輸出只適應於比較單一的應用,例如 K8s 中的一些系統組件,線上的服務類應用通常都會涉及到多個層級(中間件)、和各種服務交互,一般日誌都會分爲好幾類,如果全部打印到容器的標準輸出,很難區分處理。<br />同時容器標準輸出對於 DockerEngine 的性能消耗特別大,實測 10W/s 的日誌量會額外佔用 DockerEngine 1 個核心的 CPU(單核 100%)。

日誌是否落盤以及落盤介質

在 Kubernetes 中,還可以將日誌庫直接對接日誌系統,日誌打印的時候不落盤而直接傳輸到日誌系統後端。這種使用方式免去了日誌落盤、Agent 採集的過程,整體性能會高很多。

這種方式我們一般只建議日誌量極大的場景使用,普通情況下還是直接落盤,相比直接發送到後端的方式,落盤增加了一層文件緩存,在網絡失敗的情況下還能緩存一定的數據,在日誌系統不可用的情況下我們的研發運維同學可以直接查看文件的日誌,提高整體的可靠性。

Kubernetes 提供了多種存儲方式,一般在雲上,都會提供本地存儲、遠程文件存儲、對象存儲等方式。由於日誌寫入的 QPS 很高,和應用也直接相關,如果使用遠程類型的存儲,會額外多 2-3 次網絡通信開銷。我們一般建議使用本地存儲的方式,可以使用 HostVolume 或者 EmptyDir 的方式,這樣對於寫入和採集的性能影響會儘可能的小。

如何保證日誌存儲週期

相比傳統虛擬機/物理機的場景,Kubernetes 對於節點、應用層提供了強大的調度、容錯、縮/擴容能力,我們通過 Kubernetes 很容易就能讓應用獲得高可靠運行、極致彈性。這些優勢帶來的一個現象是:節點動態創建/刪除、容器動態創建/刪除,這樣日誌也會隨時銷燬,沒辦法保證日誌的存儲週期能夠滿足 DevOps、審計等相關的需求。

在動態的環境下實現日誌的長期存儲只能通過中心化的日誌存儲來實現,通過實時的日誌採集方式,將各個節點、各個容器的日誌在秒級內採集到日誌中心繫統上,即使節點/容器掛掉也能夠通過日誌還原當時的現場。

總結

日誌輸出是日誌系統建設中非常重要的環節,公司/產品線一定要遵循一個統一的日誌規範,這樣才能保證後續日誌採集、分析、監控、可視化能夠順利進行。

後面的章節會介紹如何爲 Kubernetes 規劃日誌採集和存儲的最佳實踐,敬請期待。

2羣直播海報.png

阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術圈。”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章