輕量級日志 Loki 全攻略

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

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

如上圖:
分發(fā)服務(wù)器收到一個 HTTP/1 請求,以存儲流數(shù)據(jù) 每個流都使用散列環(huán)散列 分發(fā)程序?qū)⒚總€流發(fā)送到適當(dāng)?shù)?inester 和其副本(基于配置的復(fù)制因子) 每個實例將為流的數(shù)據(jù)創(chuàng)建一個塊或?qū)⑵渥芳拥浆F(xiàn)有塊中,, 每個租戶和每個標(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
然后我們點擊 Log labels 就可以把當(dāng)前系統(tǒng)采集的日志標(biāo)簽給顯示出來,可以根據(jù)這些標(biāo)簽進(jìn)行日志的過濾查詢:

grafana-loki-log-labels
比如我們這里選擇 /var/log/messages,就會把該文件下面的日志過濾展示出來,不過由于時區(qū)的問題,可能還需要設(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 只對標(biāo)簽進(jìn)行索引而不對內(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
配置解讀:
上面這段配置代表啟動一個日志采集任務(wù) 這個任務(wù)有 1 個固定標(biāo)簽 job=”syslog” 采集日志路徑為 /var/log/messages,會以一個名為 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
如果我們配置了兩個 job,則可以使用job=~”apache進(jìn)行多 job 匹配;同時也支持正則和正則非匹配。
標(biāo)簽匹配模式的特點
原理如下:
和 prometheus 一致,相同標(biāo)簽對應(yīng)的是一個流 prometheus 處理 series 的模式 prometheus 中標(biāo)簽一致對應(yīng)的同一個 hash 值和 refid(正整數(shù)遞增的 id),也就是同一個 series 時序數(shù)據(jù)不斷的 append 追加到這個 memseries 中 當(dāng)有任意標(biāo)簽發(fā)生變化時會產(chǎn)生新的 hash 值和 refid,對應(yīng)新的 series
loki 處理日志的模式和 prometheus 一致,loki 一組標(biāo)簽值會生成一個 stream。日志隨著時間的遞增會追加到這個 stream 中,最后壓縮為 chunk。當(dāng)有任意標(biāo)簽發(fā)生變化時會產(chǎn)生新的 hash 值,對應(yīng)新的 stream。
查詢過程
所以 loki 先根據(jù)標(biāo)簽算出 hash 值在倒排索引中找到對應(yīng)的 chunk? 然后再根據(jù)查詢語句中的關(guān)鍵詞等進(jìn)行過濾,這樣能大大的提速 因為這種根據(jù)標(biāo)簽算哈希在倒排中查找 id,對應(yīng)找到存儲的塊在 prometheus 中已經(jīng)被驗證過了 屬于開銷低 速度快
動態(tài)標(biāo)簽和高基數(shù)
所以有了上述知識,那么就得談?wù)剟討B(tài)標(biāo)簽的問題了。
兩個概念:
何為動態(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 兩個標(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
那么對應(yīng) action=get/post 和 status_code=200/400 則對應(yīng) 4 個流:
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"
那四個日志行將變成四個單獨的流,并開始填充四個單獨的塊。
如果出現(xiàn)另一個獨特的標(biāo)簽組合(例如 status_code =“500”),則會創(chuàng)建另一個新流。
高基數(shù)問題
就像上面,如果給 ip 設(shè)置一個標(biāo)簽,現(xiàn)在想象一下,如果您為設(shè)置了標(biāo)簽 ip,來自用戶的每個不同的 ip 請求不僅成為唯一的流??梢钥焖偕沙汕先f的流,這是高基數(shù),這可以殺死 Loki。
如果字段沒有被當(dāng)做標(biāo)簽被索引,會不會查詢很慢,Loki 的超級能力是將查詢分解為小塊并并行分發(fā),以便您可以在短時間內(nèi)查詢大量日志數(shù)據(jù)。
全文索引問題
大索引既復(fù)雜又昂貴。通常,日志數(shù)據(jù)的全文索引的大小等于或大于日志數(shù)據(jù)本身的大小。
要查詢?nèi)罩緮?shù)據(jù),需要加載此索引,并且為了提高性能,它可能應(yīng)該在內(nèi)存中。這很難擴展,并且隨著您攝入更多日志,索引會迅速變大。
Loki 的索引通常比攝取的日志量小一個數(shù)量級,索引的增長非常緩慢。
加速查詢沒標(biāo)簽字段:以上邊提到的 ip 字段為例 - 使用過濾器表達(dá)式查詢。
{job="apache"}?|=?"11.11.11.11"
loki 查詢時的分片(按時間范圍分段 grep):
Loki 將把查詢分解成較小的分片,并為與標(biāo)簽匹配的流打開每個區(qū)塊,并開始尋找該 IP 地址。 這些分片的大小和并行化的數(shù)量是可配置的,并取決于您提供的資源 如果需要,您可以將分片間隔配置為 5m,部署 20 個查詢器,并在幾秒鐘內(nèi)處理千兆字節(jié)的日志 或者,您可以發(fā)瘋并設(shè)置 200 個查詢器并處理 TB 的日志!
兩種索引模式對比:
es 的大索引,不管你查不查詢,他都必須時刻存在。比如長時間占用過多的內(nèi)存 loki 的邏輯是查詢時再啟動多個分段并行查詢
日志量少時少加標(biāo)簽:
因為每多加載一個 chunk 就有額外的開銷 舉例,如果該查詢是 {app=”loki”,level!=”debug”} 在沒加 level 標(biāo)簽的情況下只需加載一個 chunk 即 app=“l(fā)oki” 的標(biāo)簽 如果加了 level 的情況,則需要把 level=info,warn,error,critical 5 個 chunk 都加載再查詢
需要標(biāo)簽時再去添加:
當(dāng) chunk_target_size=1MB 時代表 以 1MB 的壓縮大小來切割塊 對應(yīng)的原始日志大小在 5MB-10MB,如果日志在 max_chunk_age 時間內(nèi)能達(dá)到 10MB,考慮添加標(biāo)簽
日志應(yīng)當(dāng)按時間遞增:
這個問題和 tsdb 中處理舊數(shù)據(jù)是一樣的道理 目前 loki 為了性能考慮直接拒絕掉舊數(shù)據(jù)
歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價值的Java的干貨文章,助力您成為有思想的Java開發(fā)工程師!
