1. 程式人生 > >面向容器日誌的技術實踐

面向容器日誌的技術實踐

背景

自 2013 年 dotCloud 公司開源 Docker 以來,以 Docker 為代表的容器產品憑藉著隔離性好、可移植性高、資源佔用少、啟動迅速等特性迅速風靡世界。下圖展示了 2013 年以來 Docker 和 OpenStack 的搜尋趨勢。

Google Trend

容器技術在部署、交付等環節給人們帶來了很多便捷,但在日誌處理領域卻帶來了許多新的挑戰,包括:

  1. 如果把日誌儲存在容器內部,它會隨著容器的銷燬而被刪除。由於容器的生命週期相對虛擬機器大大縮短,建立銷燬屬於常態,因此需要一種方式持久化的儲存日誌;
  2. 進入容器時代後,需要管理的目標物件遠多於虛擬機器或物理機,登入到目標容器排查問題會變得更加複雜且不經濟;
  3. 容器的出現讓微服務更容易落地,它在給我們的系統帶來鬆耦合的同時引入了更多的元件。因此我們需要一種技術,它既能幫助我們全域性性的瞭解系統執行情況,又能迅速定位問題現場、還原上下文。

日誌處理流程

本文以 Docker 為例,依託阿里雲日誌服務團隊在日誌領域深耕多年積累下的豐富經驗,介紹容器日誌處理的一般方法和最佳實踐,包括:

  1. 容器日誌實時採集;
  2. 查詢分析和視覺化;
  3. 日誌上下文分析;
  4. LiveTail - 雲上 tail -f。

容器日誌實時採集

容器日誌分類

採集日誌首先要弄清日誌存在的位置,這裡以 Nginx、Tomcat 這兩個常用容器為例進行分析。

Nginx 產生的日誌包括 access.log 和 error.log,根據 nginx Dockerfile 可知 access.log 和 error.log 被分別重定向到了 STDOUT 和 STDERR 上。

Tomcat 產生的日誌比較多,包括 catalina.log、access.log、manager.log、host-manager.log 等,tomcat Dockerfile 並沒有將這些日誌重定向到標準輸出,它們存在於容器內部。

容器產生的日誌大部分都可以歸結於上述情形。這裡,我們不妨將容器日誌分成以下兩類。

容器日誌分類 定義
標準輸出 通過 STDOUT、STDERR 輸出的資訊,包括被重定向到標準輸出的文字檔案。
文字日誌 存在於容器內部並且沒有被重定向到標準輸出的日誌。

標準輸出

使用 logging driver

容器的標準輸出會由 logging driver 統一處理。如下圖所示,不同的 logging driver 會將標準輸出寫往不同的目的地。

logging driver

通過 logging driver 採集容器標準輸出的優勢在於使用簡單,例如:

# 該命令表示在 docker daemon 級別為所有容器配置 syslog 日誌驅動
dockerd -–log-driver syslog –-log-opt syslog-address=udp://1.2.3.4:1111

# 該命令表示為當前容器配置 syslog 日誌驅動
docker run -–log-driver syslog –-log-opt syslog-address=udp://1.2.3.4:1111 alpine echo hello world
缺點

除了 json-file 和 journald,使用其他 logging driver 將使 docker logs API 不可用。例如,當您使用 portainer 管理宿主機上的容器,並且使用了上述兩者之外的 logging driver,您會發現無法通過 UI 介面觀察到容器的標準輸出。

使用 docker logs API

對於那些使用預設 logging driver 的容器,我們可以通過向 docker daemon 傳送 docker logs 命令來獲取容器的標準輸出。使用此方式採集日誌的工具包括 logspoutsematext-agent-docker 等。下列樣例中的命令表示獲取容器自2018-01-01T15:00:00以來最新的5條日誌。

docker logs --since "2018-01-01T15:00:00" --tail 5 <container-id>
缺點

當日志量較大時,這種方式會對 docker daemon 造成較大壓力,導致 docker daemon 無法及時響應建立容器、銷燬容器等命令。

採集 json-file 檔案

預設 logging driver 會將日誌以 json 的格式寫入宿主機檔案裡,檔案路徑為/var/lib/docker/containers/<container-id>/<container-id>-json.log。這樣可以通過直接採集宿主機檔案來達到採集容器標準輸出的目的。

該方案較為推薦,因為它既不會使 docker logs API 變得不可用,又不會影響 docker daemon,並且現在許多工具原生支援採集宿主機檔案,如 filebeatlogtail 等。

文字日誌

掛載宿主機目錄

採集容器內文字日誌最簡單的方法是在啟動容器時通過 bind mountsvolumes 方式將宿主機目錄掛載到容器日誌所在目錄上,如下圖所示。

容器文字日誌

針對 tomcat 容器的 access log,使用命令docker run -it -v /tmp/app/vol1:/usr/local/tomcat/logs tomcat將宿主機目錄/tmp/app/vol1掛載到 access log 在容器中的目錄/usr/local/tomcat/logs上,通過採集宿主機目錄/tmp/app/vol1下日誌達到採集 tomcat access log 的目的。

計算容器 rootfs 掛載點

使用掛載宿主機目錄的方式採集日誌對應用會有一定的侵入性,因為它要求容器啟動的時候包含掛載命令。如果採集過程能對使用者透明那就太棒了。事實上,可以通過計算容器 rootfs 掛載點來達到這種目的。

和容器 rootfs 掛載點密不可分的一個概念是 storage driver。實際使用過程中,使用者往往會根據 linux 版本、檔案系統型別、容器讀寫情況等因素選擇合適的 storage driver。不同 storage driver 下,容器的 rootfs 掛載點遵循一定規律,因此我們可以根據 storage driver 的型別推斷出容器的 rootfs 掛載點,進而採集容器內部日誌。下表展示了部分 storage dirver 的 rootfs 掛載點及其計算方法。

Storage driver rootfs 掛載點 計算方法
aufs /var/lib/docker/aufs/mnt/<id> id 可以從如下檔案讀到。
/var/lib/docker/image/aufs/layerdb/mounts/\<container-id\>/mount-id
overlay /var/lib/docker/overlay/<id>/merged 完整路徑可以通過如下命令得到。
docker inspect -f '{{.GraphDriver.Data.MergedDir}}' <container-id>
overlay2 /var/lib/docker/overlay2/<id>/merged 完整路徑可以通過如下命令得到。
docker inspect -f '{{.GraphDriver.Data.MergedDir}}' <container-id>
devicemapper /var/lib/docker/devicemapper/mnt/<id>/rootfs id 可以通過如下命令得到。
docker inspect -f '{{.GraphDriver.Data.DeviceName}}' <container-id>

Logtail 方案

在充分比較了容器日誌的各種採集方法,綜合整理了廣大使用者的反饋與訴求後,日誌服務團隊推出了容器日誌一站式解決方案。

logtail方案

功能特點

logtail 方案包含如下功能:

  1. 支援採集宿主機檔案以及宿主機上容器的日誌(包括標準輸出和日誌檔案);
  2. 支援容器自動發現,即當您配置了採集目標後,每當有符合條件的容器被建立時,該容器上的目標日誌將被自動採集;
  3. 支援通過 docker label 以及環境變數過濾指定容器,支援白名單、黑名單機制;
  4. 採集資料自動打標,即對收集上來的日誌自動加上 container name、container IP、檔案路徑等用於標識資料來源的資訊;
  5. 支援採集 K8s 容器日誌。

核心優勢

  1. 通過 checkpoint 機制以及部署額外的監控程序保證 at-least-once 語義;
  2. 歷經多次雙十一、雙十二的考驗以及阿里集團內部百萬級別的部署規模,穩定和效能方面非常有保障。

K8s 容器日誌採集

和 K8s 生態深度整合,能非常方便地採集 K8s 容器日誌是日誌服務 logtail 方案的又一大特色。

採集配置管理:

  1. 支援通過 WEB 控制檯進行採集配置管理;
  2. 支援通過 CRD(CustomResourceDefinition)方式進行採集配置管理(該方式更容易與 K8s 的部署、釋出流程進行整合)。

採集模式:

  1. 支援通過 DaemonSet 模式採集 K8s 容器日誌,即每個節點上執行一個採集客戶端 logtail,適用於功能單一型的叢集;
  2. 支援通過 Sidecar 模式採集 K8s 容器日誌,即每個 Pod 裡以容器的形式執行一個採集客戶端 logtail,適用於大型、混合型、PAAS 型叢集。

關於 Logtail 方案的詳細說明可參考文章全面提升,阿里雲Docker/Kubernetes(K8S) 日誌解決方案與選型對比

查詢分析和視覺化

完成日誌採集工作後,下一步需要對這些日誌進行查詢分析和視覺化。這裡以 Tomcat 訪問日誌為例,介紹日誌服務提供的強大的查詢、分析、視覺化功能。

快速查詢

容器日誌被採集時會帶上 container name、container IP、目標檔案路徑等資訊,因此在查詢的時候可以通過這些資訊快速定位目標容器和檔案。查詢功能的詳細介紹可參考文件查詢語法

實時分析

日誌服務實時分析功能相容 SQL 語法且提供了 200 多種聚合函式。如果您有使用 SQL 的經驗,能夠很容易寫出滿足業務需求的分析語句。例如:

  1. 統計訪問次數排名前 10 的 uri。
* | SELECT request_uri, COUNT(*) as c GROUP by request_uri ORDER by c DESC LIMIT 10
  1. 統計當前15分鐘的網路流量相對於前一個小時的變化情況。
* | SELECT diff[1] AS c1, diff[2] AS c2, round(diff[1] * 100.0 / diff[2] - 100.0, 2) AS c3 FROM (select compare( flow, 3600) AS diff from (select sum(body_bytes_sent) as flow from log))

該語句使用同比環比函式計算不同時間段的網路流量。

視覺化

為了讓資料更加生動,您可以使用日誌服務內建的多種圖表對 SQL 計算結果進行視覺化展示,並將圖表組合成一個儀表盤

logtail方案

下圖展示了基於 Tomcat 訪問日誌的儀表盤,它展示了錯誤請求率、網路流量、狀態碼隨時間的變化趨勢等資訊。該儀表盤展現的是多個 Tomcat 容器資料聚合後的結果,您可以使用儀表盤過濾器功能,通過指定容器名檢視單個容器的資料。

dashboard

日誌上下文分析

查詢分析、儀表盤等功能能幫助我們把握全域性資訊、瞭解系統整體執行情況,但定位具體問題往往需要上下文資訊的幫助。

上下文定義

上下文指的是圍繞某個問題展開的線索,如日誌中某個錯誤的前後資訊。上下文包含兩個要素:

  • 最小區分粒度:區分上下文的最小空間劃分,例如同一個執行緒、同一個檔案等。這一點在定位問題階段非常關鍵,因為它能夠使得我們在調查過程中避免很多幹擾。
  • 保序:在最小區分粒度的前提下,資訊的呈現必須是嚴格有序的,即使一秒內有幾萬次操作。

下表展示了不同資料來源的最小區分粒度。

分類 最小區分粒度
單機檔案 IP + 檔案
Docker 標準輸出 Container + STDOUT/STDERR
Docker 檔案 Container + 檔案
K8s 容器標準輸出 Namespace + Pod + Container + STDOUT/STDERR
K8s 容器檔案 Namespace + Pod + Container + 檔案
SDK 執行緒
Log Appender 執行緒

上下文查詢面臨的挑戰

在日誌集中式儲存的背景下,採集端和服務端都很難保證日誌原始的順序:

  1. 在客戶端層面,一臺宿主機上執行著多個容器,每個容器會有多個目標檔案需要採集。日誌採集軟體需要利用機器的多個 cpu 核心解析、預處理日誌,並通過多執行緒併發或者單執行緒非同步回撥的方式處理網路傳送的慢 IO 問題。這使得日誌資料不能按照機器上的事件產生順序依次到達服務端。
  2. 在服務端層面,由於水平擴充套件的多機負載均衡架構,使得同一客戶端機器的日誌會分散在多臺儲存節點上。在分散儲存的日誌基礎上再恢復最初的順序是困難的。

原理

日誌服務通過給每條日誌附加一些額外的資訊以及服務端的關鍵詞查詢能力巧妙地解決了上述難題。原理如下圖所示。

undefined

  1. 日誌被採集時會自動加入用於標識日誌來源的資訊(即上文提到的最小區分粒度)作為 source_id。針對容器場景,這些資訊包括容器名、檔案路徑等;
  2. 日誌服務的各種採集客戶端一般會選擇批量上傳日誌,若干條日誌組成一個數據包。客戶端會向這些資料包裡寫入一個單調遞增的 package_id,並且包內每條日誌都擁有包內位移 offset;
  3. 服務端會將 source_id、package_id、offset 組合起來作為一個欄位併為其建立索引。這樣,即使各種日誌在服務端是混合儲存的狀態,我們也可以根據 source_id、package_id、offset 精確定位某條日誌。

想了解更多有關上下文分析的功能可參考文章上下文查詢分散式系統日誌上下文查詢功能

LiveTail - 雲上 tail -f

除了檢視日誌的上下文資訊,有時我們也希望能夠持續觀察容器的輸出。

傳統方式

下表展示了傳統模式下實時監控容器日誌的方法。

類別 步驟
標準輸出 1. 定位容器,獲取容器 id;
2. 使用命令docker logs –f <container id>kubectl logs –f <pod name>在終端上觀察輸出;
3. 使用grepgrep –v過濾關鍵資訊。
文字日誌 1. 定位容器,獲取容器 id;
2. 使用命令docker execkubectl exec進入容器;
3. 找到目標檔案,使用命令tail –f觀察輸出;
4. 使用grepgrep –v過濾關鍵資訊。

痛點

通過傳統方法監控容器日誌存在以下痛點:

  1. 容器很多時,定位目標容器耗時耗力;
  2. 不同型別的容器日誌需要使用不同的觀察方法,增加使用成本;
  3. 關鍵資訊查詢展示不夠簡單直觀。

功能和原理

針對這些問題,日誌服務推出了 LiveTail 功能。相比傳統模式,它有如下優點:

  1. 可以根據單條日誌或日誌服務的查詢分析功能快速定位目標容器;
  2. 使用統一的方式觀察不同型別的容器日誌,無需進入目標容器;
  3. 支援通過關鍵詞進行過濾;
  4. 支援設定關鍵列。

undefined

在實現上,LiveTail 主要用到了上一章中提到的上下文查詢原理快速定位目標容器和目標檔案。然後,客戶端定期向服務端傳送請求,拉取最新資料。

視訊樣例

您還可以通過觀看視訊,進一步理解容器日誌的採集、查詢、分析和視覺化等功能。

參考資料

技術支援

日誌服務-SLS