輕量級(jí)日志 Loki 全攻略

文章來源:https://c1n.cn/0wHvF
前言
在對(duì)公司容器云的日志方案進(jìn)行設(shè)計(jì)的時(shí)候,發(fā)現(xiàn)主流的 ELK(Elasticsearch,Logstash,Kibana)或者 EFK(Elasticsearch,F(xiàn)ilebeat or Fluentd,Kibana)比較重,再加上現(xiàn)階段對(duì)于 ES 復(fù)雜的搜索功能很多都用不上,最終選擇了 Grafana 開源的 Loki 日志系統(tǒng)。
下面我們來介紹下 Loki 的一些基本概念和架構(gòu),當(dāng)然 EFK 作為業(yè)界成熟的日志聚合解決方案也是大家應(yīng)該需要熟悉和掌握的。
簡介
Loki 是 Grafana Labs 團(tuán)隊(duì)最新的開源項(xiàng)目,是一個(gè)水平可擴(kuò)展,高可用性,多租戶的日志聚合系統(tǒng)。
它的設(shè)計(jì)非常經(jīng)濟(jì)高效且易于操作,因?yàn)樗粫?huì)為日志內(nèi)容編制索引,而是為每個(gè)日志流編制一組標(biāo)簽,專門為 Prometheus 和 Kubernetes 用戶做了相關(guān)優(yōu)化。
該項(xiàng)目受 Prometheus 啟發(fā),官方的介紹就是:Like Prometheus,But For Logs。類似于 Prometheus 的日志系統(tǒng)。
項(xiàng)目地址:
https://github.com/grafana/loki/
與其他日志聚合系統(tǒng)相比,Loki 具有下面的一些特性:
不對(duì)日志進(jìn)行全文索引。通過存儲(chǔ)壓縮非結(jié)構(gòu)化日志和僅索引元數(shù)據(jù),Loki 操作起來會(huì)更簡單,更省成本。 通過使用與 Prometheus 相同的標(biāo)簽記錄流對(duì)日志進(jìn)行索引和分組,這使得日志的擴(kuò)展和操作效率更高,能對(duì)接 alertmanager。 特別適合儲(chǔ)存 Kubernetes Pod 日志;諸如 Pod 標(biāo)簽之類的元數(shù)據(jù)會(huì)被自動(dòng)刪除和編入索引。 受 Grafana 原生支持,避免 kibana 和 grafana 來回切換。
架構(gòu)說明

組件說明
說明如下:
Promtail 作為采集器,類比 filebeat Loki 相當(dāng)于服務(wù)端,類比 es
Loki 進(jìn)程包含四種角色:
querier 查詢器 inester 日志存儲(chǔ)器 query-frontend 前置查詢器 distributor 寫入分發(fā)器
可以通過 Loki 二進(jìn)制的 -target 參數(shù)指定運(yùn)行角色。
read path
如下:
查詢器接受 HTTP/1 數(shù)據(jù)請(qǐng)求 查詢器將查詢傳遞給所有 ingesters 請(qǐng)求內(nèi)存中的數(shù)據(jù) 接收器接受讀取的請(qǐng)求,并返回與查詢匹配的數(shù)據(jù)(如果有) 如果沒有接受者返回?cái)?shù)據(jù),則查詢器會(huì)從后備存儲(chǔ)中延遲加載數(shù)據(jù)并對(duì)其執(zhí)行查詢 查詢器將迭代所有接收到的數(shù)據(jù)并進(jìn)行重復(fù)數(shù)據(jù)刪除,從而通過 HTTP/1 連接返回最終數(shù)據(jù)集
write path

如上圖:
分發(fā)服務(wù)器收到一個(gè) HTTP/1 請(qǐng)求,以存儲(chǔ)流數(shù)據(jù) 每個(gè)流都使用散列環(huán)散列 分發(fā)程序?qū)⒚總€(gè)流發(fā)送到適當(dāng)?shù)?inester 和其副本(基于配置的復(fù)制因子) 每個(gè)實(shí)例將為流的數(shù)據(jù)創(chuàng)建一個(gè)塊或?qū)⑵渥芳拥浆F(xiàn)有塊中,, 每個(gè)租戶和每個(gè)標(biāo)簽集的塊都是唯一的 分發(fā)服務(wù)器通過 HTTP/1 鏈接以成功代碼作為響應(yīng)
部署
本地化模式安裝
下載 Promtail 和 Loki:
wget??https://github.com/grafana/loki/releases/download/v2.2.1/loki-linux-amd64.zip
wget?https://github.com/grafana/loki/releases/download/v2.2.1/promtail-linux-amd64.zip
安裝 Promtail:
$?mkdir?/opt/app/{promtail,loki}?-pv
#?promtail配置文件
$?cat?<?/opt/app/promtail/promtail.yaml
server:
??http_listen_port:?9080
??grpc_listen_port:?0
positions:
??filename:?/var/log/positions.yaml?#?This?location?needs?to?be?writeable?by?promtail.
client:
??url:?http://localhost:3100/loki/api/v1/push
scrape_configs:
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?varlogs
??????host:?yourhost
??????__path__:?/var/log/*.log
EOF
#?解壓安裝包
unzip?promtail-linux-amd64.zip
mv?promtail-linux-amd64?/opt/app/promtail/promtail
#?service文件
$?cat?</etc/systemd/system/promtail.service
[Unit]
Description=promtail?server
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/app/promtail/promtail?-config.file=/opt/app/promtail/promtail.yaml
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=promtail
[Install]
WantedBy=default.target
EOF
systemctl?daemon-reload
systemctl?restart?promtail
systemctl?status?promtail
安裝 Loki:
$?mkdir?/opt/app/{promtail,loki}?-pv
#?promtail配置文件
$?cat?<?/opt/app/loki/loki.yaml
auth_enabled:?false
server:
??http_listen_port:?3100
??grpc_listen_port:?9096
ingester:
??wal:
????enabled:?true
????dir:?/opt/app/loki/wal
??lifecycler:
????address:?127.0.0.1
????ring:
??????kvstore:
????????store:?inmemory
??????replication_factor:?1
????final_sleep:?0s
??chunk_idle_period:?1h???????#?Any?chunk?not?receiving?new?logs?in?this?time?will?be?flushed
??max_chunk_age:?1h???????????#?All?chunks?will?be?flushed?when?they?hit?this?age,?default?is?1h
??chunk_target_size:?1048576??#?Loki?will?attempt?to?build?chunks?up?to?1.5MB,?flushing?first?if?chunk_idle_period?or?max_chunk_age?is?reached?first
??chunk_retain_period:?30s????#?Must?be?greater?than?index?read?cache?TTL?if?using?an?index?cache?(Default?index?read?cache?TTL?is?5m)
??max_transfer_retries:?0?????#?Chunk?transfers?disabled
schema_config:
??configs:
????-?from:?2020-10-24
??????store:?boltdb-shipper
??????object_store:?filesystem
??????schema:?v11
??????index:
????????prefix:?index_
????????period:?24h
storage_config:
??boltdb_shipper:
????active_index_directory:?/opt/app/loki/boltdb-shipper-active
????cache_location:?/opt/app/loki/boltdb-shipper-cache
????cache_ttl:?24h?????????#?Can?be?increased?for?faster?performance?over?longer?query?periods,?uses?more?disk?space
????shared_store:?filesystem
??filesystem:
????directory:?/opt/app/loki/chunks
compactor:
??working_directory:?/opt/app/loki/boltdb-shipper-compactor
??shared_store:?filesystem
limits_config:
??reject_old_samples:?true
??reject_old_samples_max_age:?168h
chunk_store_config:
??max_look_back_period:?0s
table_manager:
??retention_deletes_enabled:?false
??retention_period:?0s
ruler:
??storage:
????type:?local
????local:
??????directory:?/opt/app/loki/rules
??rule_path:?/opt/app/loki/rules-temp
??alertmanager_url:?http://localhost:9093
??ring:
????kvstore:
??????store:?inmemory
??enable_api:?true
EOF
#?解壓包
unzip?loki-linux-amd64.zip?
mv?loki-linux-amd64?/opt/app/loki/loki
#?service文件
$?cat?</etc/systemd/system/loki.service
[Unit]
Description=loki?server
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/opt/app/loki/loki?-config.file=/opt/app/loki/loki.yaml
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=loki
[Install]
WantedBy=default.target
EOF
systemctl?daemon-reload
systemctl?restart?loki
systemctl?status?loki
使用
grafana 上配置 loki 數(shù)據(jù)源
如下圖:

grafana-loki-dashsource
在數(shù)據(jù)源列表中選擇 Loki,配置 Loki 源地址:
grafana-loki-dashsource-config
源地址配置 http://loki:3100 即可,保存。
保存完成后,切換到 grafana 左側(cè)區(qū)域的 Explore,即可進(jìn)入到 Loki 的頁面:

grafana-loki
然后我們點(diǎn)擊 Log labels 就可以把當(dāng)前系統(tǒng)采集的日志標(biāo)簽給顯示出來,可以根據(jù)這些標(biāo)簽進(jìn)行日志的過濾查詢:

grafana-loki-log-labels
比如我們這里選擇 /var/log/messages,就會(huì)把該文件下面的日志過濾展示出來,不過由于時(shí)區(qū)的問題,可能還需要設(shè)置下時(shí)間才可以看到數(shù)據(jù):

grafana-loki-logs
這里展示的是 promtail 容器里面 / var/log 目錄中的日志。
promtail 容器 /etc/promtail/config.yml:
server:
??http_listen_port:?9080
??grpc_listen_port:?0
positions:
??filename:?/tmp/positions.yaml
clients:
??-?url:?http://loki:3100/loki/api/v1/push
scrape_configs:
-?job_name:?system
??static_configs:
??-?targets:
??????-?localhost
????labels:
??????job:?varlogs
??????__path__:?/var/log/*log
這里的 job 就是 varlog,文件路徑就是 /var/log/*log。
在 grafana explore 上配置查看日志
查看日志?rate({job="message"}?|="kubelet"
算 qps rate({job=”message”} |=”kubelet” [1m])
只索引標(biāo)簽
之前多次提到 loki 和 es 最大的不同是 loki 只對(duì)標(biāo)簽進(jìn)行索引而不對(duì)內(nèi)容索引。下面我們舉例來看下。
靜態(tài)標(biāo)簽匹配模式
以簡單的 promtail 配置舉例:
scrape_configs:
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?message
??????__path__:?/var/log/messages
配置解讀:
上面這段配置代表啟動(dòng)一個(gè)日志采集任務(wù) 這個(gè)任務(wù)有 1 個(gè)固定標(biāo)簽 job=”syslog” 采集日志路徑為 /var/log/messages,會(huì)以一個(gè)名為 filename 的固定標(biāo)簽 在 promtail 的 web 頁面上可以看到類似 prometheus 的 target 信息頁面
可以和使用 Prometheus 一樣的標(biāo)簽匹配語句進(jìn)行查詢。
{job="syslog"}:
scrape_configs:
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?syslog
??????__path__:?/var/log/syslog
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?apache
??????__path__:?/var/log/apache.log
如果我們配置了兩個(gè) job,則可以使用job=~”apache進(jìn)行多 job 匹配;同時(shí)也支持正則和正則非匹配。
標(biāo)簽匹配模式的特點(diǎn)
原理如下:
和 prometheus 一致,相同標(biāo)簽對(duì)應(yīng)的是一個(gè)流 prometheus 處理 series 的模式 prometheus 中標(biāo)簽一致對(duì)應(yīng)的同一個(gè) hash 值和 refid(正整數(shù)遞增的 id),也就是同一個(gè) series 時(shí)序數(shù)據(jù)不斷的 append 追加到這個(gè) memseries 中 當(dāng)有任意標(biāo)簽發(fā)生變化時(shí)會(huì)產(chǎn)生新的 hash 值和 refid,對(duì)應(yīng)新的 series
loki 處理日志的模式和 prometheus 一致,loki 一組標(biāo)簽值會(huì)生成一個(gè) stream。日志隨著時(shí)間的遞增會(huì)追加到這個(gè) stream 中,最后壓縮為 chunk。當(dāng)有任意標(biāo)簽發(fā)生變化時(shí)會(huì)產(chǎn)生新的 hash 值,對(duì)應(yīng)新的 stream。
查詢過程
所以 loki 先根據(jù)標(biāo)簽算出 hash 值在倒排索引中找到對(duì)應(yīng)的 chunk? 然后再根據(jù)查詢語句中的關(guān)鍵詞等進(jìn)行過濾,這樣能大大的提速 因?yàn)檫@種根據(jù)標(biāo)簽算哈希在倒排中查找 id,對(duì)應(yīng)找到存儲(chǔ)的塊在 prometheus 中已經(jīng)被驗(yàn)證過了 屬于開銷低 速度快
動(dòng)態(tài)標(biāo)簽和高基數(shù)
所以有了上述知識(shí),那么就得談?wù)剟?dòng)態(tài)標(biāo)簽的問題了。
兩個(gè)概念:
何為動(dòng)態(tài)標(biāo)簽:說白了就是標(biāo)簽的 value 不固定 何為高基數(shù)標(biāo)簽:說白了就是標(biāo)簽的 value 可能性太多了,達(dá)到 10 萬,100 萬甚至更多
比如 apache 的 access 日志:
11.11.11.11?-?frank?[25/Jan/2000:14:00:01?-0500]?"GET?/1986.js?HTTP/1.1"?200?932?"-"?"Mozilla/5.0?(Windows;?U;?Windows?NT?5.1;?de;?rv:1.9.1.7)?Gecko/20091221?Firefox/3.5.7?GTB6"
在 Promtail 中使用 regex 想要匹配 action 和 status_code 兩個(gè)標(biāo)簽:
scrape_configs:
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?syslog
??????__path__:?/var/log/syslog
?-?job_name:?system
???pipeline_stages:
???static_configs:
???-?targets:
??????-?localhost
?????labels:
??????job:?apache
??????__path__:?/var/log/apache.log
??-?job_name:?system
????pipeline_stages:
???????-?regex:
?????????expression:?"^(?P\\S+)?(?P\\S+)?(?P\\S+)?\\[(?P[\\w:/]+\\s[+\\-]\\d{4})\\]?\"(?P\\S+)\\s?(?P\\S+)?\\s?(?P\\S+)?\"?(?P\\d{3}|-)?(?P\\d+|-)\\s?\"?(?P[^\"]*)\"?\\s?\"?(?P[^\"]*)?\"?$"
?????-?labels:
?????????action:
?????????status_code:
????static_configs:
????-?targets:
???????-?localhost
??????labels:
???????job:?apache
???????env:?dev
???????__path__:?/var/log/apache.log
那么對(duì)應(yīng) action=get/post 和 status_code=200/400 則對(duì)應(yīng) 4 個(gè)流:
11.11.11.11?-?frank?[25/Jan/2000:14:00:01?-0500]?"GET?/1986.js?HTTP/1.1"?200?932?"-"?"Mozilla/5.0?(Windows;?U;?Windows?NT?5.1;?de;?rv:1.9.1.7)?Gecko/20091221?Firefox/3.5.7?GTB6"
11.11.11.12?-?frank?[25/Jan/2000:14:00:02?-0500]?"POST?/1986.js?HTTP/1.1"?200?932?"-"?"Mozilla/5.0?(Windows;?U;?Windows?NT?5.1;?de;?rv:1.9.1.7)?Gecko/20091221?Firefox/3.5.7?GTB6"
11.11.11.13?-?frank?[25/Jan/2000:14:00:03?-0500]?"GET?/1986.js?HTTP/1.1"?400?932?"-"?"Mozilla/5.0?(Windows;?U;?Windows?NT?5.1;?de;?rv:1.9.1.7)?Gecko/20091221?Firefox/3.5.7?GTB6"
11.11.11.14?-?frank?[25/Jan/2000:14:00:04?-0500]?"POST?/1986.js?HTTP/1.1"?400?932?"-"?"Mozilla/5.0?(Windows;?U;?Windows?NT?5.1;?de;?rv:1.9.1.7)?Gecko/20091221?Firefox/3.5.7?GTB6"
那四個(gè)日志行將變成四個(gè)單獨(dú)的流,并開始填充四個(gè)單獨(dú)的塊。
如果出現(xiàn)另一個(gè)獨(dú)特的標(biāo)簽組合(例如 status_code =“500”),則會(huì)創(chuàng)建另一個(gè)新流。
高基數(shù)問題
就像上面,如果給 ip 設(shè)置一個(gè)標(biāo)簽,現(xiàn)在想象一下,如果您為設(shè)置了標(biāo)簽 ip,來自用戶的每個(gè)不同的 ip 請(qǐng)求不僅成為唯一的流。可以快速生成成千上萬的流,這是高基數(shù),這可以殺死 Loki。
如果字段沒有被當(dāng)做標(biāo)簽被索引,會(huì)不會(huì)查詢很慢,Loki 的超級(jí)能力是將查詢分解為小塊并并行分發(fā),以便您可以在短時(shí)間內(nèi)查詢大量日志數(shù)據(jù)。
全文索引問題
大索引既復(fù)雜又昂貴。通常,日志數(shù)據(jù)的全文索引的大小等于或大于日志數(shù)據(jù)本身的大小。
要查詢?nèi)罩緮?shù)據(jù),需要加載此索引,并且為了提高性能,它可能應(yīng)該在內(nèi)存中。這很難擴(kuò)展,并且隨著您攝入更多日志,索引會(huì)迅速變大。
Loki 的索引通常比攝取的日志量小一個(gè)數(shù)量級(jí),索引的增長非常緩慢。
加速查詢沒標(biāo)簽字段:以上邊提到的 ip 字段為例 - 使用過濾器表達(dá)式查詢。
{job="apache"}?|=?"11.11.11.11"
loki 查詢時(shí)的分片(按時(shí)間范圍分段 grep):
Loki 將把查詢分解成較小的分片,并為與標(biāo)簽匹配的流打開每個(gè)區(qū)塊,并開始尋找該 IP 地址。 這些分片的大小和并行化的數(shù)量是可配置的,并取決于您提供的資源 如果需要,您可以將分片間隔配置為 5m,部署 20 個(gè)查詢器,并在幾秒鐘內(nèi)處理千兆字節(jié)的日志 或者,您可以發(fā)瘋并設(shè)置 200 個(gè)查詢器并處理 TB 的日志!
兩種索引模式對(duì)比:
es 的大索引,不管你查不查詢,他都必須時(shí)刻存在。比如長時(shí)間占用過多的內(nèi)存 loki 的邏輯是查詢時(shí)再啟動(dòng)多個(gè)分段并行查詢
日志量少時(shí)少加標(biāo)簽:
因?yàn)槊慷嗉虞d一個(gè) chunk 就有額外的開銷 舉例,如果該查詢是 {app=”loki”,level!=”debug”} 在沒加 level 標(biāo)簽的情況下只需加載一個(gè) chunk 即 app=“l(fā)oki” 的標(biāo)簽 如果加了 level 的情況,則需要把 level=info,warn,error,critical 5 個(gè) chunk 都加載再查詢
需要標(biāo)簽時(shí)再去添加:
當(dāng) chunk_target_size=1MB 時(shí)代表 以 1MB 的壓縮大小來切割塊 對(duì)應(yīng)的原始日志大小在 5MB-10MB,如果日志在 max_chunk_age 時(shí)間內(nèi)能達(dá)到 10MB,考慮添加標(biāo)簽
日志應(yīng)當(dāng)按時(shí)間遞增:
這個(gè)問題和 tsdb 中處理舊數(shù)據(jù)是一樣的道理 目前 loki 為了性能考慮直接拒絕掉舊數(shù)據(jù)
歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價(jià)值的Java的干貨文章,助力您成為有思想的Java開發(fā)工程師!
