容器日誌採集利器Log-Pilot
容器時代越來越多的傳統應用將會逐漸容器化,而日誌又是應用的一個關鍵環節,那麼在應用容器化過程中,如何方便快捷高效地來自動發現和採集應用的日誌,如何與日誌儲存系統協同來高效儲存和搜尋應用日誌。本文將主要跟大家分享下如何通過Log-Pilot來採集容器的標準輸出日誌和容器內檔案日誌。
日誌採集難點
首先我們先看一下容器日誌採集的一些難點,這裡主要從兩個方面來講,第一個是容器本身的特性,第二個是現有采集工具的一些缺陷:
容器本身特性
- 採集目標多
容器一般推薦將日誌寫在標準輸出,但是也有一些特殊的場景就是應用直接將日誌寫在容器內部。對於容器的標準輸出日誌來說,Docker Engine 本身就提供了一個很好的日誌採集能力,但是對於容器內部的檔案日誌採集,現在並沒有一個很好的工具能夠去動態發現採集。 - 容器的彈性伸縮性
我們知道 Kubernetes 本身是一個分散式叢集,那麼我們事先就無法像傳統虛擬機器環境下那樣,事先配置好日誌的採集路徑等一些資訊,因此這對於容器的日誌採集來說也將面臨一個很大的挑戰。
現有采集工具缺陷
- 缺乏動態配置的能力
目前的採集工具都需要我們事先手動配置好日誌採集方式和路徑等資訊,由於它無法能夠自動感知到容器的生命週期變化或者動態漂移,所以它無法動態地去配置。 - 日誌採集重複或丟失
現有的一些採集工具基本上是通過 tail 的方式來進行日誌採集的,那麼這裡就可能存在兩個方面的問題:一個是可能導致日誌丟失,比如採集工具在重啟的過程中,而應用依然在寫日誌,那麼就有可能導致這個視窗期的日誌丟失;而對於這種情況一般保守的做法就是,預設往前多采集 1M 日誌或 2M 的日誌,那麼這就又會可能引起日誌採集重複的問題。 - 未明確標記日誌源
一個應用可能有很多個容器,輸出的應用日誌也是一樣的,那麼當我們將所有應用日誌收集到統一日誌儲存後端時,在搜尋日誌的時候,我們就無法明確這條日誌具體是哪一個節點上的哪一個應用容器產生的。
破解利器 Log-Pilot
針對這些問題,我們提供了一個智慧容器採集工具 Log-Pilot,它不僅能夠高效便捷地將容器日誌採集輸出到多種儲存日誌後端,同時還能夠動態地發現和採集容器內部的日誌檔案。
Log-Pilot 本身分為三部分,其中一部分就是容器事件管理,它能夠動態地監聽容器的事件變化,然後依據容器的標籤來進行解析,生成日誌採集配置檔案,然後交由採集外掛來進行日誌採集。
下面看一下,針對前面容器日誌採集的難點,Log-Pilot 是如何去解決的:
- 採集目標多:Log-Pilot 同時支援標準輸出和容器內部檔案日誌採集。
- 動態伸縮性:Log-Pilot 支援宣告式的日誌配置方式。
- 缺乏動態配置的能力:Log-Pilot 具有自動感知和發現的特性。
- 日誌採集重複和丟失:Log-Pilot 內部有 CheckPoint 和控制代碼保持的機制。
- 未明確標記日誌源:Log-Pilot 支援日誌自動資料打標。
宣告式日誌配置
Log-Pilot 支援宣告式日誌配置,可以依據容器的 Label 或者 ENV 來動態地生成日誌採集配置檔案。這裡重點說明兩個變數:
- name:我們自定義的一個字串,它在不同的場景下指代不同的含義。當我們將日誌採集到 ElasticSearch 的時候, name 表示的是 Index;當我們將日誌採集到 Kafka 的時候, name 表示的是 Topic;當我們將日誌採集到阿里雲日誌服務的時候,name 表示的是 LogstoreName。
- path:它本身支援兩種,一種是約定關鍵字 stdout,表示的是採集容器的標準輸出日誌,第二種是容器內部的具體檔案日誌路徑,可以支援萬用字元的方式。比如我們要採集 tomcat 容器日誌,那麼我們通過配置標籤
aliyun.logs.catalina=stdout
來採集 tomcat 標準輸出日誌,通過配置標籤aliyun.logs.access=/usr/local/tomcat/logs/*.log
來採集 tomcat 容器內部檔案日誌。
自動發現機制
Log-Pilot 能夠自動感知宿主機上容器的建立刪除事件,進而動態配置容器日誌採集配置檔案。
一般情況下我們是通過全量掃描加事件監聽的方式,比如採集工具程序在起來的時候,會先去全量掃描一遍宿主機上的所有容器列表,然後依據容器的宣告式配置來進行日誌採集配置檔案的動態生成,然後再註冊事件監聽,那麼這樣可能會導致一個問題,在全量掃描配置的過程中並且在註冊事件監聽之前,這個視窗期的容器事件就有可能會丟失,因此這裡我們採用的是先註冊事件監聽,然後再全量掃描,這樣就可以很好地規避容器事件丟失的問題。
控制代碼保持機制
自動 CheckPoint
Log-Pilot 內部會實時跟蹤日誌採集偏移量,然後維持日誌檔案資訊與偏移量的對映關係,最後定期地持久化到磁碟中。採用偏移量的方式我們可以避免日誌採集丟失和重複的問題,同時即使當採集工具宕掉再起來,它可以通過載入持久化在磁碟上的元資料資訊,然後從指定的日誌偏移位置上繼續採集日誌。
控制代碼保持機制
Log-Pilot 在監測到配置的日誌路徑目錄下有新的日誌檔案產生時會主動地開啟其控制代碼,並維持開啟狀態,這樣是為了防止因日誌採集工具比較慢或者應用日誌輸出速率特別大,比如說當前已經生成五個日誌檔案但只採集到第三個,後面兩個還沒有開始採集,一旦這個容器退出就可能導致後面兩個檔案的日誌丟失了。
因此 Log-Pilot 在監測到有新的日誌產生的時候,會立即將其檔案控制代碼開啟,這樣的話即使這個日誌檔案刪除,它在磁碟中的資料並沒有被擦除,只有當該日誌檔案採集完成後,我們才會主動去釋放這個檔案控制代碼,這樣就可以保證日誌檔案裡面的日誌不會丟失。
自動資料打標
Log-Pilot 在採集容器日誌的時候,同時也會收集容器的元資料資訊,包括容器的名稱,容器所屬的服務名稱以及容器所屬的應用名稱,同時在 Kubernetes 裡面也會採集容器所屬的 Pod 資訊,包括 Pod 的名稱,Pod 所屬的 namespace 以及 Pod 所在的節點資訊。這樣我們排查問題時,就可以很方便地知道這個日誌資料是來源於哪個節點上的哪個應用容器。
支援高階特性
Log-Pilot 除了提供前面的幾個特性外,還支援一些其他的高階特性,比如低資源消耗,支援自定義 tag,支援多種日誌解析格式,支援自定義日誌輸出 target 以及支援 fluentd 和 filebeat 等外掛,最後支援對接到多種日誌儲存後端。
低資源消耗
針對低資源消耗,我們先簡單看一下容器日誌採集一般採用的兩種部署模式:
- SideCar 模式
這種需要我們在每個 Pod 中都附帶一個 logging 容器來進行本 Pod 內部容器的日誌採集,一般採用共享卷的方式,但是對於這一種模式來說,很明顯的一個問題就是佔用的資源比較多,尤其是在叢集規模比較大的情況下,或者說單個節點上容器特別多的情況下,它會佔用過多的系統資源,同時也對日誌儲存後端佔用過多的連線數。當我們的叢集規模越大,這種部署模式引發的潛在問題就越大。
- Node 模式
這種模式是我們在每個 Node 節點上僅需佈署一個 logging 容器來進行本 Node 所有容器的日誌採集。這樣跟前面的模式相比最明顯的優勢就是佔用資源比較少,同樣在叢集規模比較大的情況下表現出的優勢越明顯。
但對於這種模式來說我們就需要一個更加智慧的日誌採集工具來配合,那麼這裡用 Log-Pilot 工具就是一個很好的選擇,因此我們在佈署 Log-Pilot 採集工具的時候採用的就是 Node 模式。
支援自定義Tag
Log-Pilot 也支援自定義Tag,我們可以在容器的標籤或者環境變數裡配置 aliyun.logs.$name.tags: k=v
,那麼在採集日誌的時候也會將k=v
採集到容器的日誌輸出中。
比如我們有一種場景,有一個開發環境和測試環境,應用日誌都會被採集到統一的一個日誌儲存後端,假設是一個 ElasticSearch 叢集,但是我們在 ElasticSearch 中查詢日誌的時候又想區分出來,具體某條日誌記錄到底來源於生產環境,還是測試環境。
那麼我們就可以通過給測試環境的容器打上 stage=dev
的 tag,給生產環境的容器打上 stage=pro
的 tag,Log-Pilot 在採集容器日誌的時候,同時會將這些 tag 隨容器日誌一同採集到日誌儲存後端中,那麼當我們在查詢日誌的時候,就可以通過 stage=dev
或者 stage=pro
能明確地區分出某條日誌是來源於生產環境的應用容器所產生,還是測試環境應用容器所產生的。另外通過自定義 tag 的方式我們還可以進行日誌統計、日誌路由和日誌過濾。
支援多種日誌解析格式
Log-Pilot 也支援多種日誌解析格式,通過 aliyun.logs.$name.format: <format>
標籤就可以告訴 Log-Pilot 在採集日誌的時候,同時以什麼樣的格式來解析日誌記錄。目前主要支援六種:
- none:預設格式,指不對日誌記錄做任何解析,整行採集出來直接輸出到日誌儲存後端。
- json:Log-Pilot 在採集日誌的時候同時會將每一行日誌以 json 的方式進行解析,解析出多個 KV 對,然後輸出到日誌儲存後端。
- csv:主要是針對csv格式的日誌採集配置(需配置fluentd外掛)。
- nginx:主要是針對Nginx的日誌採集配置(需配置fluentd外掛)。
- apache2:主要是針對Apache的日誌採集配置(需配置fluentd外掛)。
- regexp:使用者可以通過 format 標籤來自定義正則表示式,告訴 Log-Pilot 在解析日誌記錄的時候以什麼樣的拆分格式來進行解析拆分(需配置fluentd外掛)。
支援自定義輸出Target
這裡假設一種場景,我們同時有一個生產環境和一個測試環境,應用日誌都需要被採集到同一套 Kafka 中,然後由不同的 consumer 去消費。
但是我們同樣希望區分出來,某條日誌資料是由生產環境的應用容器產生的,還是測試環境的應用容器產生的,但我們在測試環境中的應用容器已經配置了 aliyun.logs.svc=stdout
標籤,那麼當這些應用容器的標準輸出日誌被採集到 kafka 中,它最終會被路由到 topic=svc
的訊息佇列中,那麼訂閱了 topic=svc
的 consumer 就能夠接收測試環境的應用容器產生的日誌。
但當我們將該應用釋出到生產環境時,希望它產生的日誌只能交由生產環境的 consumer 來接收處理,那麼我們就可以通過 target 的方式,給生產環境的應用容器額外定義一個 target=pro-svc
,那麼生產環境的應用日誌在被採集到 Kafka 中時,最終會被路由到 topic 為 pro-svc 的訊息佇列中,那麼訂閱了 topic =pro-svc
的 consumer 就可以正常地接收到來自於生產環境的容器產生的日誌。
因此這裡的 target 本身也有三種含義:
- 當我們將日誌對接到ElasticeSearch時,這個 target 字串是 Index;
- 當我們對接到Kafka時,它指代的是 topic;
- 當我們將日誌對接到日誌服務時,它代表的是 Logstore Name。
支援多采集外掛
目前 Log-Pilot 支援兩種採集外掛:一個是CNCF社群的Fluentd外掛,一個是Elastic的Filebeat外掛;其同時其支援對接多種儲存後端,目前 Fluentd 和 Filebeat 都支援 Elasticsearch、Kafka、File、Console 作為日誌儲存後端,而 Fluentd 還支援 Graylog、阿里雲日誌服務 以及 Mongodb 作為儲存後端。
社群支援
Log-Pilot在2017年初已在GitHub開源:https://github.com/AliyunContainerService/log-pilot,本著開源開放的原則,歡迎大家使用和貢獻支援,共同將Log-Pilot打造成更加智慧更加高效的容器日誌採集利器。