Loki簡介,部署,使用


前言

​ 在對公司容器雲的日志方案進行設計的時候,發現主流的 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,就會把該文件下面的日志過濾展示出來,不過由於時區的問題,可能還需要設置下時間才可以看到數據:

s

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 為了性能考慮直接拒絕掉舊數據


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM