還在用ES查日志嗎,快看看石墨文檔 Clickhouse 日志架構(gòu)玩法
1 背景
石墨文檔全部應(yīng)用部署在Kubernetes上,每時(shí)每刻都會(huì)有大量的日志輸出,我們之前主要使用SLS和ES作為日志存儲(chǔ)。但是我們?cè)谑褂眠@些組件的時(shí)候,發(fā)現(xiàn)了一些問題。
成本問題:
SLS個(gè)人覺得是一個(gè)非常優(yōu)秀的產(chǎn)品,速度快,交互方便,但是SLS索引成本比較貴我們想減少
SLS索引成本的時(shí)候,發(fā)現(xiàn)云廠商并不支持分析單個(gè)索引的成本,導(dǎo)致我們無法知道是哪些索引構(gòu)建的不夠合理ES使用的存儲(chǔ)非常多,并且耗費(fèi)大量的內(nèi)存通用問題:
如果業(yè)務(wù)是混合云架構(gòu),或者業(yè)務(wù)形態(tài)有
SAAS和私有化兩種方式,那么SLS并不能通用日志和鏈路,需要用兩套云產(chǎn)品,不是很方便
精確度問題:
SLS存儲(chǔ)的精度只能到秒,但我們實(shí)際日志精度到毫秒,如果日志里面有traceid,SLS中無法通過根據(jù)traceid信息,將日志根據(jù)毫秒時(shí)間做排序,不利于排查錯(cuò)誤
我們經(jīng)過一番調(diào)研后,發(fā)現(xiàn)使用Clickhouse能夠很好的解決以上問題,并且Clickhouse省存儲(chǔ)空間,非常省錢,所以我們選擇了Clickhouse方案存儲(chǔ)日志。但當(dāng)我們深入研究后,Clickhouse作為日志存儲(chǔ)有許多落地的細(xì)節(jié),但業(yè)界并沒有很好闡述相關(guān)Clickhouse采集日志的整套流程,以及沒有一款優(yōu)秀的Clickhouse日志查詢工具幫助分析日志,為此我們寫了一套Clickhouse日志系統(tǒng)貢獻(xiàn)給開源社區(qū),并將Clickhouse的日志采集架構(gòu)的經(jīng)驗(yàn)做了總結(jié)。先上個(gè)Clickhouse日志查詢界面,讓大家感受下石墨最懂前端的后端程序員。

2 架構(gòu)原理圖
我們將日志系統(tǒng)分為四個(gè)部分:日志采集、日志傳輸、日志存儲(chǔ)、日志管理。
日志采集:
LogCollector采用Daemonset方式部署,將宿主機(jī)日志目錄掛載到LogCollector的容器內(nèi),LogCollector通過掛載的目錄能夠采集到應(yīng)用日志、系統(tǒng)日志、K8S審計(jì)日志等日志傳輸:通過不同
Logstore映射到Kafka中不同的Topic,將不同數(shù)據(jù)結(jié)構(gòu)的日志做了分離日志存儲(chǔ):使用
Clickhouse中的兩種引擎數(shù)據(jù)表和物化視圖日志管理:開源的
Mogo系統(tǒng),能夠查詢?nèi)罩荆O(shè)置日志索引,設(shè)置LogCollector配置,設(shè)置Clickhouse表,設(shè)置報(bào)警等

以下我們按照這四大部分,闡述其中的架構(gòu)原理
3 日志采集
3.1 采集方式
Kubernetes容器內(nèi)日志收集的方式通常有以下三種方案
DaemonSet方式采集:在每個(gè)?
node?節(jié)點(diǎn)上部署LogCollector,并將宿主機(jī)的目錄掛載為容器的日志目錄,LogCollector讀取日志內(nèi)容,采集到日志中心。網(wǎng)絡(luò)方式采集:通過應(yīng)用的日志?
SDK,直接將日志內(nèi)容采集到日志中心 。SideCar方式采集:在每個(gè)?
pod?內(nèi)部署LogCollector,LogCollector只讀取這個(gè)?pod?內(nèi)的日志內(nèi)容,采集到日志中心。
以下是三種采集方式的優(yōu)缺點(diǎn):
| DaemonSet方式 | 網(wǎng)絡(luò)方式 | SideCar方式 | |
|---|---|---|---|
| 采集日志類型 | 標(biāo)準(zhǔn)輸出+文件 | 應(yīng)用日志 | 文件 |
| 部署運(yùn)維 | 一般,維護(hù)DaemonSet | 低,維護(hù)配置文件 | 較高,每個(gè)pod部署sidecar容器 |
| 日志分類存儲(chǔ) | 可通過容器/路徑等映射 | 業(yè)務(wù)獨(dú)立配置 | 每個(gè)POD可單獨(dú)配置,靈活性高 |
| 支持集群規(guī)模 | 取決于配置數(shù) | 無限制 | 無限制 |
| 適用場(chǎng)景 | 日志分類明確、功能較單一 | 性能要求極高的場(chǎng)景 | 大型、混合型、PAAS型集群 |
| 性能資源消耗 | 中 | 低 | 高 |
我們主要采用DaemonSet方式和網(wǎng)絡(luò)方式采集日志。DaemonSet方式用于ingress、應(yīng)用日志的采集,網(wǎng)絡(luò)方式用于大數(shù)據(jù)日志的采集(可能需要簡(jiǎn)單的說明下原因)。以下我們主要介紹下DeamonSet方式的采集方式。
3.2 日志輸出
從上面的介紹中可以看到,我們的DaemonSet會(huì)有兩種方式采集日志類型,一種是標(biāo)準(zhǔn)輸出,一種是文件。引用元乙的描述:雖然使用?Stdout?打印日志是?Docker?官方推薦的方式,但大家需要注意:這個(gè)推薦是基于容器只作為簡(jiǎn)單應(yīng)用的場(chǎng)景,實(shí)際的業(yè)務(wù)場(chǎng)景中我們還是建議大家盡可能使用文件的方式,主要的原因有以下幾點(diǎn):
Stdout?性能問題,從應(yīng)用輸出?stdout?到服務(wù)端,中間會(huì)經(jīng)過好幾個(gè)流程(例如普遍使用的JSON?LogDriver):應(yīng)用?stdout?->?DockerEngine?->?LogDriver?-> 序列化成?JSON?-> 保存到文件 ->?Agent?采集文件 -> 解析?JSON?-> 上傳服務(wù)端。整個(gè)流程相比文件的額外開銷要多很多,在壓測(cè)時(shí),每秒 10 萬行日志輸出就會(huì)額外占用?DockerEngine?1 個(gè)?CPU?核;Stdout?不支持分類,即所有的輸出都混在一個(gè)流中,無法像文件一樣分類輸出,通常一個(gè)應(yīng)用中有?AccessLog、ErrorLog、InterfaceLog(調(diào)用外部接口的日志)、TraceLog?等,而這些日志的格式、用途不一,如果混在同一個(gè)流中將很難采集和分析;Stdout?只支持容器的主程序輸出,如果是?daemon/fork?方式運(yùn)行的程序?qū)o法使用?stdout;文件的?
Dump?方式支持各種策略,例如同步/異步寫入、緩存大小、文件輪轉(zhuǎn)策略、壓縮策略、清除策略等,相對(duì)更加靈活。
從這個(gè)描述中,我們可以看出在docker中輸出文件在采集到日志中心是一個(gè)更好的實(shí)踐。所有日志采集工具都支持采集文件日志方式,但是我們?cè)谂渲萌罩静杉?guī)則的時(shí)候,發(fā)現(xiàn)開源的一些日志采集工具,例如fluentbit、filebeat在DaemonSet部署下采集文件日志是不支持追加例如pod、namespace、container_name、container_id等label信息,并且也無法通過這些label做些定制化的日志采集。
| agent類型 | 采集方式 | daemonset部署 | sidecar部署 |
|---|---|---|---|
| ilogtail | 文件日志 | 能夠追加label信息,能夠根據(jù)label過濾采集 | 能夠追加label信息,能夠根據(jù)label過濾采集 |
| fluentbit | 文件日志 | 無法追加label信息,無法根據(jù)label過濾采集 | 能夠追加abel信息,能夠根據(jù)label過濾采集 |
| filebeat | 文件日志 | 無法追加label信息,無法根據(jù)label過濾采集 | 能夠追加label信息,能夠根據(jù)label過濾采集 |
| ilogtail | 標(biāo)準(zhǔn)輸出 | 能夠追加label信息,能夠根據(jù)label過濾采集 | 能夠追加label信息,能夠根據(jù)label過濾采集 |
| fluentbit | 標(biāo)準(zhǔn)輸出 | 能夠追加label信息,能夠根據(jù)label過濾采集 | 能夠追加abel信息,能夠根據(jù)label過濾采集 |
| filebeat | 標(biāo)準(zhǔn)輸出 | 能夠追加label信息,能夠根據(jù)label過濾采集 | 能夠追加label信息,能夠根據(jù)label過濾采集 |
基于無法追加label信息的原因,我們暫時(shí)放棄了DeamonSet部署下文件日志采集方式,采用的是基于DeamonSet部署下標(biāo)準(zhǔn)輸出的采集方式。
3.3 日志目錄
以下列舉了日志目錄的基本情況
| 目錄 | 描述 | 類型 |
|---|---|---|
| /var/log/containers | 存放的是軟鏈接,軟鏈到/var/log/pods里的標(biāo)準(zhǔn)輸出日志 | 標(biāo)準(zhǔn)輸出 |
| /var/log/pods | 存放標(biāo)準(zhǔn)輸出日志 | 標(biāo)準(zhǔn)輸出 |
| /var/log/kubernetes/ | master存放Kubernetes 審計(jì)輸出日志 | 標(biāo)準(zhǔn)輸出 |
| /var/lib/docker/overlay2 | 存放應(yīng)用日志文件信息 | 文件日志 |
| /var/run | 獲取docker.sock,用于docker通信 | 文件日志 |
| /var/lib/docker/containers | 用于存儲(chǔ)容器信息 | 兩種都需要 |
因?yàn)槲覀儾杉罩臼鞘褂玫臉?biāo)準(zhǔn)輸出模式,所以根據(jù)上表我們的LogCollector只需要掛載/var/log,/var/lib/docker/containers兩個(gè)目錄。
3.3.1 標(biāo)準(zhǔn)輸出日志目錄
應(yīng)用的標(biāo)準(zhǔn)輸出日志存儲(chǔ)在/var/log/containers目錄下,文件名是按照K8S日志規(guī)范生成的。這里以nginx-ingress的日志作為一個(gè)示例。我們通過ls /var/log/containers/ | grep nginx-ingress指令,可以看到nginx-ingress的文件名。

nginx-ingress-controller-mt2wx_kube-system_nginx-ingress-controller-be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a.log
我們參照K8S日志的規(guī)范:/var/log/containers/%{DATA:pod_name}_%{DATA:namespace}_%{GREEDYDATA:container_name}-%{DATA:container_id}.log??梢詫?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">nginx-ingress日志解析為:
pod_name:nginx-ingress-controller-mt2wnamespace:kube-systemcontainer_name:nginx-ingress-controllercontainer_id:be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a
通過以上的日志解析信息,我們的LogCollector?就可以很方便的追加pod、namespace、container_name、container_id的信息。
3.3.2 容器信息目錄
應(yīng)用的容器信息存儲(chǔ)在/var/lib/docker/containers目錄下,目錄下的每一個(gè)文件夾為容器ID,我們可以通過cat config.v2.json獲取應(yīng)用的docker基本信息。

3.4 LogCollector采集日志
3.4.1 配置
我們LogCollector采用的是fluent-bit,該工具是cncf旗下的,能夠更好的與云原生相結(jié)合。通過Mogo系統(tǒng)可以選擇Kubernetes集群,很方便的設(shè)置fluent-bit?configmap的配置規(guī)則。

3.4.2 數(shù)據(jù)結(jié)構(gòu)
fluent-bit的默認(rèn)采集數(shù)據(jù)結(jié)構(gòu)
@timestamp字段:string or float,用于記錄采集日志的時(shí)間log字段:string,用于記錄日志的完整內(nèi)容
Clickhouse如果使用@timestamp的時(shí)候,因?yàn)槔锩嬗?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">@特殊字符,會(huì)處理的有問題。所以我們?cè)谔幚?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">fluent-bit的采集數(shù)據(jù)結(jié)構(gòu),會(huì)做一些映射關(guān)系,并且規(guī)定雙下劃線為Mogo系統(tǒng)日志索引,避免和業(yè)務(wù)日志的索引沖突。
_time_字段:string or float,用于記錄采集日志的時(shí)間_log_字段:string,用于記錄日志的完整內(nèi)容
例如你的日志記錄的是{"id":1},那么實(shí)際fluent-bit采集的日志會(huì)是{"_time_":"2022-01-15...","_log_":"{\"id\":1}"?該日志結(jié)構(gòu)會(huì)直接寫入到kafka中,Mogo系統(tǒng)會(huì)根據(jù)這兩個(gè)字段_time_、_log_設(shè)置clickhouse中的數(shù)據(jù)表。
3.4.3 采集
如果我們要采集ingress日志,我們需要在input配置里,設(shè)置ingress的日志目錄,fluent-bit會(huì)把ingress日志采集到內(nèi)存里。

然后我們?cè)?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">filter配置里,將log改寫為_log_

然后我們?cè)?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">ouput配置里,將追加的日志采集時(shí)間設(shè)置為_time_,設(shè)置好日志寫入的kafka borkers和kafka topics,那么fluent-bit里內(nèi)存的日志就會(huì)寫入到kafka中

日志寫入到Kafka中_log_需要為json,如果你的應(yīng)用寫入的日志不是json,那么你就需要根據(jù)fluent-bit的parser文檔,調(diào)整你的日志寫入的數(shù)據(jù)結(jié)構(gòu):https://docs.fluentbit.io/manual/pipeline/filters/parser
4 日志傳輸
Kafka主要用于日志傳輸。上文說到我們使用fluent-bit采集日志的默認(rèn)數(shù)據(jù)結(jié)構(gòu),在下圖kafka工具中我們可以看到日志采集的內(nèi)容。

在日志采集過程中,會(huì)由于不用業(yè)務(wù)日志字段不一致,解析方式是不一樣的。所以我們?cè)谌罩緜鬏旊A段,需要將不同數(shù)據(jù)結(jié)構(gòu)的日志,創(chuàng)建不同的Clickhouse表,映射到Kafka不同的Topic。這里以ingress為例,那么我們?cè)?code style="box-sizing: border-box;font-size: 13.6px;font-family: mononoki, Consolas, "Liberation Mono", Menlo, Courier, monospace, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;padding: 0.2em 0.4em;border-radius: 3px;word-break: break-word;background-size: 20px 20px;white-space: pre-wrap;background-color: rgba(27, 31, 35, 0.05);">Clickhouse中需要?jiǎng)?chuàng)建一個(gè)ingress_stdout_stream的Kafka引擎表,然后映射到Kafka的ingress-stdout Topic里。
5 日志存儲(chǔ)
我們會(huì)使用三種表,用于存儲(chǔ)一種業(yè)務(wù)類型的日志。
Kafka引擎表:將數(shù)據(jù)從Kafka采集到Clickhouse的ingress_stdout_stream數(shù)據(jù)表中
create?table?logger.ingress_stdout_stream
(
_source_?String,
_pod_name_?String,
_namespace_?String,
_node_name_?String,
_container_name_?String,
_cluster_?String,
_log_agent_?String,
_node_ip_?String,
_time_?Float64,
_log_?String
)
engine?=?Kafka?SETTINGS?kafka_broker_list?=?'kafka:9092',?kafka_topic_list?=?'ingress-stdout',?kafka_group_name?=?'logger_ingress_stdout',?kafka_format?=?'JSONEachRow',?kafka_num_consumers?=?1;
物化視圖:將數(shù)據(jù)從
ingress_stdout_stream數(shù)據(jù)表讀取出來,_log_根據(jù)Mogo配置的索引,提取字段在寫入到ingress_stdout結(jié)果表里
CREATE?MATERIALIZED?VIEW?logger.ingress_stdout_view?TO?logger.ingress_stdout?AS
SELECT
toDateTime(toInt64(_time_))?AS?_time_second_,
fromUnixTimestamp64Nano(toInt64(_time_*1000000000),'Asia/Shanghai')?AS?_time_nanosecond_,
_pod_name_,
_namespace_,
_node_name_,
_container_name_,
_cluster_,
_log_agent_,
_node_ip_,
_source_,
_log_?AS?_raw_log_,JSONExtractInt(_log_,?'status')?AS?status,JSONExtractString(_log_,?'url')?AS?url
FROM?logger.ingress_stdout_stream?where?1=1;
結(jié)果表:存儲(chǔ)最終的數(shù)據(jù)
create?table?logger.ingress_stdout
(
_time_second_?DateTime,
_time_nanosecond_?DateTime64(9,?'Asia/Shanghai'),
_source_?String,
_cluster_?String,
_log_agent_?String,
_namespace_?String,
_node_name_?String,
_node_ip_?String,
_container_name_?String,
_pod_name_?String,
_raw_log_?String,
status?Nullable(Int64),
url?Nullable(String),
)
engine?=?MergeTree?PARTITION?BY?toYYYYMMDD(_time_second_)
ORDER?BY?_time_second_
TTL?toDateTime(_time_second_)?+?INTERVAL?7?DAY
SETTINGS?index_granularity?=?8192;
6 總結(jié)流程

日志會(huì)通過
fluent-bit的規(guī)則采集到kafka,在這里我們會(huì)將日志采集到兩個(gè)字段里_time_字段用于存儲(chǔ)fluent-bit采集的時(shí)間_log_字段用于存放原始日志通過
mogo,在clickhouse里設(shè)置了三個(gè)表app_stdout_stream:將數(shù)據(jù)從Kafka采集到Clickhouse的Kafka引擎表app_stdout_view:視圖表用于存放mogo設(shè)置的索引規(guī)則app_stdout:根據(jù)app_stdout_view索引解析規(guī)則,消費(fèi)app_stdout_stream里的數(shù)據(jù),存放于app_stdout結(jié)果表中最后
mogo的UI界面,根據(jù)app_stdout的數(shù)據(jù),查詢?nèi)罩拘畔?/p>
7 Mogo界面展示
查詢?nèi)罩窘缑?/p>

設(shè)置日志采集配置界面

以上文檔描述是針對(duì)石墨Kubernetes的日志采集,想了解物理機(jī)采集日志方案的,可以在下文中找到《Mogo使用文檔》的鏈接,運(yùn)行docker-compose體驗(yàn)Mogo?全部流程,查詢Clickhouse日志。限于篇幅有限,Mogo的日志報(bào)警功能,下次在講解。
8 資料
github地址:?https://github.com/shimohq/mogo
Mogo文檔:https://mogo.shimo.im
Mogo使用文檔:https://mogo.shimo.im/doc/AV62KU4AABMRQ
fluent-bit文檔:https://docs.fluentbit.io/
K8S日志
6 個(gè) K8S 日志系統(tǒng)建設(shè)中的典型問題,你遇到過幾個(gè):https://developer.aliyun.com/article/718735
一文看懂 K8S 日志系統(tǒng)設(shè)計(jì)和實(shí)踐:https://developer.aliyun.com/article/727594
9 個(gè)技巧,解決 K8S 中的日志輸出問題:https://developer.aliyun.com/article/747821
直擊痛點(diǎn),詳解 K8S 日志采集最佳實(shí)踐:https://developer.aliyun.com/article/749468?spm=a2c6h.14164896.0.0.24031164UoPfIX
Clickhouse
Clickhouse官方文檔:https://clickhouse.com/
Clickhouse作為Kubernetes日志管理解決方案中的存儲(chǔ):http://dockone.io/article/9356
Uber 如何使用 ClickHouse 建立快速可靠且與模式無關(guān)的日志分析平臺(tái)?:https://www.infoq.cn/article/l4thjgnr7hxpkgpmw6dz
干貨 | 攜程ClickHouse日志分析實(shí)踐:https://mp.weixin.qq.com/s/IjOWAPOJXANRQqRAMWXmaw
為什么我們要從ES遷移到ClickHouse:https://mp.weixin.qq.com/s/l4RgNQPxvdNIqx52LEgBnQ
ClickHouse 在日志存儲(chǔ)與分析方面作為 ElasticSearch 和 MySQL 的替代方案:https://mp.weixin.qq.com/s/nJXorcgi0QfXPCKr_HdUZg
快手、攜程等公司轉(zhuǎn)戰(zhàn)到 ClickHouse,ES 難道不行了?:https://mp.weixin.qq.com/s/hP0ocT-cBCeIl9n1wL_HBg
日志分析下ES/ClickHouse/Loki比較與思考:https://mp.weixin.qq.com/s/n2I94X6tz2jOABzl1djxYg
