輕量化日誌 Loki 全攻略,再也不會整懵了
文章來源:https://c1n.cn/0wHvF
目錄
-
前言
-
簡介
-
架構說明
-
部署
-
使用
前言
在對公司容器雲的日誌方案進行設計的時候,發現主流的 ELK(Elasticsearch,Logstash,Kibana)或者 EFK(Elasticsearch,Filebeat or Fluentd,Kibana)比較重,再加上現階段對於 ES 複雜的搜索功能很多都用不上,最終選擇了 Grafana 開源的 Loki 日誌系統。
下面我們來介紹下 Loki 的一些基本概念和架構,當然 EFK 作爲業界成熟的日誌聚合解決方案也是大家應該需要熟悉和掌握的。
簡介
Loki 是 Grafana Labs 團隊最新的開源項目,是一個水平可擴展,高可用性,多租戶的日誌聚合系統。
它的設計非常經濟高效且易於操作,因爲它不會爲日誌內容編制索引,而是爲每個日誌流編制一組標籤,專門爲 Prometheus 和 Kubernetes 用戶做了相關優化。
該項目受 Prometheus 啓發,官方的介紹就是:Like Prometheus,But For Logs。類似於 Prometheus 的日誌系統。
項目地址:
https://github.com/grafana/loki/
與其他日誌聚合系統相比,Loki 具有下面的一些特性:
-
不對日誌進行全文索引。通過存儲壓縮非結構化日誌和僅索引元數據,Loki 操作起來會更簡單,更省成本。
-
通過使用與 Prometheus 相同的標籤記錄流對日誌進行索引和分組,這使得日誌的擴展和操作效率更高,能對接 alertmanager。
-
特別適合儲存 Kubernetes Pod 日誌;諸如 Pod 標籤之類的元數據會被自動刪除和編入索引。
-
受 Grafana 原生支持,避免 kibana 和 grafana 來回切換。
架構說明
| 組件說明
說明如下:
-
Promtail 作爲採集器,類比 filebeat
-
Loki 相當於服務端,類比 es
Loki 進程包含四種角色:
-
querier 查詢器
-
inester 日誌存儲器
-
query-frontend 前置查詢器
-
distributor 寫入分發器
可以通過 Loki 二進制的 -target 參數指定運行角色。
| read path
如下:
-
查詢器接受 HTTP/1 數據請求
-
查詢器將查詢傳遞給所有 ingesters 請求內存中的數據
-
接收器接受讀取的請求,並返回與查詢匹配的數據(如果有)
-
如果沒有接受者返回數據,則查詢器會從後備存儲中延遲加載數據並對其執行查詢
-
查詢器將迭代所有接收到的數據並進行重複數據刪除,從而通過 HTTP/1 連接返回最終數據集
| write path
如上圖:
-
分發服務器收到一個 HTTP/1 請求,以存儲流數據
-
每個流都使用散列環散列
-
分發程序將每個流發送到適當的 inester 和其副本(基於配置的複製因子)
-
每個實例將爲流的數據創建一個塊或將其追加到現有塊中,, 每個租戶和每個標籤集的塊都是唯一的
-
分發服務器通過 HTTP/1 鏈接以成功代碼作爲響應
部署
| 本地化模式安裝
下載 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 <<EOF> /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 <<EOF >/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 <<EOF> /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 <<EOF >/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 數據源
如下圖:
grafana-loki-dashsource
在數據源列表中選擇 Loki,配置 Loki 源地址:
grafana-loki-dashsource-config
源地址配置 http://loki:3100 即可,保存。
保存完成後,切換到 grafana 左側區域的 Explore,即可進入到 Loki 的頁面:
grafana-loki
然後我們點擊 Log labels 就可以把當前系統採集的日誌標籤給顯示出來,可以根據這些標籤進行日誌的過濾查詢:
grafana-loki-log-labels
比如我們這裏選擇 /var/log/messages,就會把該文件下面的日誌過濾展示出來,不過由於時區的問題,可能還需要設置下時間纔可以看到數據:
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])
| 只索引標籤
之前多次提到 loki 和 es 最大的不同是 loki 只對標籤進行索引而不對內容索引。下面我們舉例來看下。
靜態標籤匹配模式
以簡單的 promtail 配置舉例:
scrape_configs:
- job_name: system
pipeline_stages:
static_configs:
- targets:
- localhost
labels:
job: message
__path__: /var/log/messages
配置解讀:
-
上面這段配置代表啓動一個日誌採集任務
-
這個任務有 1 個固定標籤 job=”syslog”
-
採集日誌路徑爲 /var/log/messages,會以一個名爲 filename 的固定標籤
-
在 promtail 的 web 頁面上可以看到類似 prometheus 的 target 信息頁面
可以和使用 Prometheus 一樣的標籤匹配語句進行查詢。
{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|syslog”} 進行多 job 匹配;同時也支持正則和正則非匹配。
| 標籤匹配模式的特點
原理如下:
-
和 prometheus 一致,相同標籤對應的是一個流 prometheus 處理 series 的模式
-
prometheus 中標籤一致對應的同一個 hash 值和 refid(正整數遞增的 id),也就是同一個 series
-
時序數據不斷的 append 追加到這個 memseries 中
-
當有任意標籤發生變化時會產生新的 hash 值和 refid,對應新的 series
loki 處理日誌的模式和 prometheus 一致,loki 一組標籤值會生成一個 stream。日誌隨着時間的遞增會追加到這個 stream 中,最後壓縮爲 chunk。當有任意標籤發生變化時會產生新的 hash 值,對應新的 stream。
查詢過程:
-
所以 loki 先根據標籤算出 hash 值在倒排索引中找到對應的 chunk?
-
然後再根據查詢語句中的關鍵詞等進行過濾,這樣能大大的提速
-
因爲這種根據標籤算哈希在倒排中查找 id,對應找到存儲的塊在 prometheus 中已經被驗證過了
-
屬於開銷低
-
速度快
| 動態標籤和高基數
所以有了上述知識,那麼就得談談動態標籤的問題了。
兩個概念:
-
何爲動態標籤:說白了就是標籤的 value 不固定
-
何爲高基數標籤:說白了就是標籤的 value 可能性太多了,達到 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 兩個標籤:
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<ip>\\S+) (?P<identd>\\S+) (?P<user>\\S+) \\[(?P<timestamp>[\\w:/]+\\s[+\\-]\\d{4})\\] \"(?P<action>\\S+)\\s?(?P<path>\\S+)?\\s?(?P<protocol>\\S+)?\" (?P<status_code>\\d{3}|-) (?P<size>\\d+|-)\\s?\"?(?P<referer>[^\"]*)\"?\\s?\"?(?P<useragent>[^\"]*)?\"?$"
- labels:
action:
status_code:
static_configs:
- targets:
- localhost
labels:
job: apache
env: dev
__path__: /var/log/apache.log
那麼對應 action=get/post 和 status_code=200/400 則對應 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"
那四個日誌行將變成四個單獨的流,並開始填充四個單獨的塊。
如果出現另一個獨特的標籤組合(例如 status_code =“500”),則會創建另一個新流。
**高基數問題:**就像上面,如果給 ip 設置一個標籤,現在想象一下,如果您爲設置了標籤 ip,來自用戶的每個不同的 ip 請求不僅成爲唯一的流。可以快速生成成千上萬的流,這是高基數,這可以殺死 Loki。
如果字段沒有被當做標籤被索引,會不會查詢很慢,Loki 的超級能力是將查詢分解爲小塊並並行分發,以便您可以在短時間內查詢大量日誌數據。
| 全文索引問題
大索引既複雜又昂貴。通常,日誌數據的全文索引的大小等於或大於日誌數據本身的大小。
要查詢日誌數據,需要加載此索引,並且爲了提高性能,它可能應該在內存中。這很難擴展,並且隨着您攝入更多日誌,索引會迅速變大。
Loki 的索引通常比攝取的日誌量小一個數量級,索引的增長非常緩慢。
加速查詢沒標籤字段:以上邊提到的 ip 字段爲例 - 使用過濾器表達式查詢。
{job="apache"} |= "11.11.11.11"
loki 查詢時的分片(按時間範圍分段 grep):
-
Loki 將把查詢分解成較小的分片,併爲與標籤匹配的流打開每個區塊,並開始尋找該 IP 地址。
-
這些分片的大小和並行化的數量是可配置的,並取決於您提供的資源
-
如果需要,您可以將分片間隔配置爲 5m,部署 20 個查詢器,並在幾秒鐘內處理千兆字節的日誌
-
或者,您可以發瘋並設置 200 個查詢器並處理 TB 的日誌!
兩種索引模式對比:
-
es 的大索引,不管你查不查詢,他都必須時刻存在。比如長時間佔用過多的內存
-
loki 的邏輯是查詢時再啓動多個分段並行查詢
日誌量少時少加標籤:
-
因爲每多加載一個 chunk 就有額外的開銷
-
舉例,如果該查詢是 {app=”loki”,level!=”debug”}
-
在沒加 level 標籤的情況下只需加載一個 chunk 即 app=“loki” 的標籤
-
如果加了 level 的情況,則需要把 level=info,warn,error,critical 5 個 chunk 都加載再查詢
需要標籤時再去添加:
-
當 chunk_target_size=1MB 時代表 以 1MB 的壓縮大小來切割塊
-
對應的原始日誌大小在 5MB-10MB,如果日誌在 max_chunk_age 時間內能達到 10MB,考慮添加標籤
日誌應當按時間遞增:
-
這個問題和 tsdb 中處理舊數據是一樣的道理
-
目前 loki 爲了性能考慮直接拒絕掉舊數據
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lli6k0MnnnOExVcMsNGyBg