1. 程式人生 > >分散式系統中的日誌落地經驗總結

分散式系統中的日誌落地經驗總結

在過去的2年多的時間裡,隨著在公司推進容器雲,陸陸續續的和日誌打了不少交道,在這裡做一個總結:

  • 為什麼需要日誌
  • 日誌如何接收與儲存
  • 日誌如何收集
  • 日誌收集客戶端分析
  • 日誌的標準化
  • 日誌報警
  • 日誌歸檔
  • 其他問題

為什麼需要日誌

日誌的作用我覺得有三點:

  1. 故障排錯
  2. 資料分析
  3. 業務審計

1,關於故障排錯,當線上發生異常,檢視應用的錯誤日誌、堆疊資訊、代理層的訪問日誌是非常有幫助的,不同級別的日誌能夠很好的幫助我們定位到故障點,而訪問日誌則能讓我們知道異常情況發生在哪個環節,是client到代理,還是代理到後端。這也是降低MTTD的一個很好的工具。

2,對日誌資料的分析,一方面能協助分析問題、定位故障,另一方面還可以幫我們更好的瞭解系統執行狀態甚至是瞭解我們的使用者。比如基於HTTP狀態碼和響應時間可以分析出系統的穩定性和效能狀況。而基於訪問日誌的來源IP可以分析出使用者地域屬性、而基於日誌量級的時間分佈可以瞭解到系統和使用者的活躍時間範圍。

3,上面兩種多數是最近幾個月的熱資料,需要實時檢視和分析,也有一部分需要儲存的更久,以滿足合規、審計等的需求。比如有些備案就要求不同級別的日誌儲存不同的時長以備隨時呼叫檢視。

日誌的收集與儲存

在分散式系統中,眾多服務分散部署在成百上千臺甚至成千上萬臺伺服器上,要想快速方便的實現上述的查詢、分析和歸檔功能,就需要有一個集中的日誌系統,通過日誌收集器將各類日誌進行統一彙總,儲存,這樣不僅能方便查詢所有的日誌,還有可能在眾多日誌資料中挖掘到一些意想不到的關聯關係。

有了這個定位接下來就可以開始詳細的規劃了,首先是日誌伺服器的選型,有經典的ELK,有商業的splunk,但我們並沒有採取上述兩種,splunk功能完全符合,但對大量級的日誌而言成本偏高,ELK中的kibana也在高版本中開始商業化,這讓我們開始尋找替代方案,graylog便是一種。它的絕大多數功能都是免費的,並且效能優越,上圖:

graylog也採用Elasticsearch作為儲存和索引以保障效能,MongoDB用來儲存少量的自身配置資訊,master-node模式具有很好的擴充套件性,UI上自帶的基礎查詢與分析功能比較實用且高效,支援LDAP、許可權控制並有豐富的日誌型別和標準(如syslog,GELF)並支援基於日誌的報警。

在日誌接收方面通常是網路傳輸,可以是TCP也可以是UDP,在實際生產環境量級較大多數採用UDP,也可以通過MQ來消費日誌。

不同日誌有不同的收集方式,總結下來有如下幾種:

  1. 伺服器系統日誌、登入日誌可通過rsyslog傳輸到統一伺服器
$ head /etc/rsyslog.conf
*.* @your-log-server-addr:port;RSYSLOG_SyslogProtocol23Format
  1. 訪問日誌,如access.log這類文字,可經過日誌收集器傳輸
  2. 應用日誌,可通過落地文字後,再經過日誌收集器傳輸;也可以直接經網路傳送到日誌伺服器或訊息佇列應用日誌,可通過落地文字後,再經過日誌收集器傳輸;也可以直接經網路傳送到日誌伺服器或訊息佇列
  3. docker日誌,除了將日誌掛載到宿主機上然後通過收集器傳輸之外,還可以在docker的配置中可設定不同logdriver將日誌以不同渠道輸出,我不推薦如gelf這類driver,因為它雖然可以無縫對接日誌平臺,但配置不靈活,如需變更還需要重啟docker daemon(預設情況下),而且在實際使用中發現gelf在效能上也存在問題。demo:docker日誌,除了將日誌掛載到宿主機上然後通過收集器傳輸之外,還可以在docker的配置中可設定不同logdriver將日誌以不同渠道輸出,但我不推薦如gelf等driver,因為它雖然可以無縫對接日誌平臺,但配置不靈活,如需變更還需要重啟docker daemon(預設情況下),而且在實際使用中發現效能上也存在問題,demo: docker版本要求在1.8以上。
$ docker --version
Docker version 1.12.0, build 8eab29e
修改配置檔案/usr/lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd --storage-driver=overlay --graph=/data/dockerapp \
--log-driver=gelf \
--log-opt gelf-address=udp://xxx.com:9999 \
--log-opt tag=docker \
--log-opt env=pro \
--log-opt gelf-compression-type=gzip \
--log-opt gelf-compression-level=1

kubernetes,如果是在公有云如Google Cloud上面的Stackdriver Logging

日誌收集器主要有:

  1. Logstash
  2. Filebeat
  3. Fluentd
  4. Fluent-bit

Logstash功能強大,但效能消耗也大,相對比較重,更適合作為中間環節,elastic後來推出的Filebeat更適合,它的效能好且資源佔用少。而Fluentd作為CNCF指定用品,在17年初用了一段時間,覺得效能不是很好,它基於磁碟或記憶體的buffer優化空間也非常有限,但隨著加入CNCF後市場佔比更多,也推出了Fluent-bit消耗1/10的資源。 這些收集器可以以daemonset的方式部署,確保每個節點上有且只有一個例項在收集日誌。

日誌標準化

上述只能實現日誌的收集、儲存和展示,但想要更好的分析,就需要用到日誌標準化,對不同日誌、不同型別做不同的管理。如為方便快速查詢某個系統在過去一段時間的訪問質量,對需要對代理層日誌中的HTTP狀態碼做清晰明瞭的界定。對於應用程式我們在日誌中的通用欄位包含:

  1. 環境資訊(如;dev,test,beta)
  2. 團隊資訊(類似namespace)
  3. 應用名
  4. 時間戳
  5. 日誌級別

為提高可擴充套件性,在應用日誌和Nginx日誌都以json格式輸出,這樣就省去如logstash等元件的加工環節,同時也可基於json中的欄位對日誌做處理,如生產環境下logLevel=debug級別的日誌不做處理,對size過大的做截斷。

對訪問日誌,可新增的就更多了,如:

log-format-upstream: '{ "message":"$remote_addr $host $request_time $status", "remote_addr":
"$remote_addr", "domain_name": "$host", "remote_user": "$remote_user", "http_tracker_id":
"$http_tracker_id", "time_local": "$time_local", "request_proto":
"$server_protocol", "request_path": "$request_uri","request_args": "$args","response_status":"$status","request_time":"$request_time","body_bytes_sent":"$body_bytes_sent","request_length":"$request_length",
"http_referer":"$http_referer","http_user_agent": "$http_user_agent","upstream_addr":"$upstream_addr",
"upstream_connect_header_response_time":"$upstream_connect_time $upstream_header_time
$upstream_response_time","upstream_status":"$upstream_status","http_x_forwarded_for":
"$http_x_forwarded_for" }'

但注意,在日誌量級較大的情況下如果欄位設定過多會對日誌收集器有一些效能壓力。

日誌報警

graylog自身支援對日誌的報警,如某個域名\應用在某個時間段內的錯誤日誌數如果超過某個閾值就報警。

日誌歸檔

可以通過訊息佇列將日誌資料再存入到HDFS中一份。

其他問題

  1. 日誌依賴ES和Luence,後者對每個欄位要求最大為32kb,超出的將不再儲存。可以設定ignore_above =256 對過大欄位不做解析y,也可以在收集端過濾較大日誌,如filebeat上設定:max_bytes = 327660
  2. 注意日誌生成時間與日誌存入時間的區分,避免元件或網路故障後的日誌時間顯示不準確。
  3. 關於日誌標準化的推廣,可以對不同語言可以做不同的包,供開發團隊直接引用。
  4. 反思之前為了確保日誌不隨著pod刪除而刪除,將日誌文字化並掛載到宿主機上再做消費,這樣會導致2份IO,且違反12因子中的日誌事件原則,當前完全基於docker的json-file日誌消費目前已經較為成熟,可以考慮直接使用該方案。