Kubernetes-20:日誌聚合分析系統—Loki的搭建與使用

 

日誌聚合分析系統——Loki

 

什麼是Loki?

Loki 是 Grafana Labs 團隊最新的開源項目,是一個水平可擴展,高可用性,多租戶的日誌聚合系統。它的設計非常經濟高效且易於操作,因為它不會為日誌內容編製索引,而是為每個日誌流編製一組標籤,專門為 Prometheus 和 Kubernetes 用戶做了相關優化。

 

與傳統日誌收集系統(例ELK)相比,Loki的優勢有哪些?

  •  不對日誌進行全文索引。通過存儲壓縮非結構化日誌和僅索引元數據,Loki操作起來會更簡單,更省成本
  •  通過與 Prometheus 相同的標籤記錄流對日誌進行索引和分組,這使得日誌的擴展和操作效率更高
  •  特別適合存儲 Kubernetes Pod 日誌;諸如 Pod 標籤之類的元數據會被自動刪除和編入索引
  •   Grafana 原生支持,與 Prometheus 配合更加方便

 

解決痛點?

 無需再去其他界面,或者終端上查看單個Pod的日誌

 

整體架構

Loki的架構並不難,主要是以下三部分組成:

  •  Loki 為主服務器。負責存儲日誌和處理查詢
  •  Promtail 是代理。負責收集日誌並將其發送給Loki
  •  Grafana 用來UI展示。

 

Loki 使用與 Prometheus 相同的服務發現和標籤重新標記庫,編寫了 Promtail。在 Kubernetes 中 Promtail 以 DaemonSet 方式運行在每個節點中,通過 Kubernetes API 得到日誌的正確元數據,並將它們發送到Loki,如下圖:

 

 可以看到,Loki中主要的組件有Distributor、Ingester和Querier三個。

 

負責寫入的組件有Distributor和Ingester兩個:

 

Distributor

Promtaif 一旦將日誌發送給Loki,Distributor 就是第一個接收日誌的組件。由於日誌的寫入量可能很大,所以不能在它們傳入時並行寫入數據庫,要先進行批處理和壓縮數據。

  1. Distributor 接收到 HTTP 請求,用於存儲流數據
  2.  通過 hash 環對數據流進行 hash
  3.  通過hash算法計算出應該發送到哪個Ingester後,發送數據流
  4.  Ingester新建Chunks或將數據追加到已有的Chunk上

 

 

Ingester

Ingester 接收到日誌並開始構建 Chunk:

Ingester 是一個有狀態組件,負責構建和刷新Chunk,當Chunk達到一定數量或者時間後,刷新到存儲中去,每一個流日誌對應一個Ingester。index和Chunk各自使用單獨的數據庫,因為他們存儲額數據類型不同。

 

負責讀的組件則是Querier:

 

讀取就比較簡單,由 Querier 負責給定一個時間範圍和標籤選擇器,也就是收到讀請求:

  1.  Querier 收到HTTP讀請求
  2.  Querier 將請求發送至Ingester讀取還未寫入Chunks的內存數據
  3.  隨後再去index+chunks中查找數據
  4.  Querier 遍歷所有數據並進行去重處理,再返回最終結果

 

搭建使用

上邊主要介紹的Loki的工作流程及組件,下面我們實際搭建操作下:

Loki項目地址://github.com/grafana/loki/

                官網://grafana.com/oss/loki/

 

一、通過Helm部署:

## 添加chart
helm repo add loki https://grafana.github.io/loki/charts

## 更新chart
helm repo update 

## 將loki template下載到本地
helm fetch loki/loki-stack

## 解壓並自定義修改參數
tar zxvf loki-stack-2.0.2.tgz
cd loki-stack/ $$ ls
charts  Chart.yaml  README.md  requirements.lock  requirements.yaml  templates  values.yaml

cd charts/ $$ ls
filebeat  fluent-bit  grafana  logstash  loki  prometheus  promtail

 

開始helm安裝前要注意幾個點:

1、可以修改values.yaml文件,指定是否開啟Grafana、Prometheus等服務,默認不開啟的:

loki:
  enabled: true

promtail:
  enabled: true

fluent-bit:
  enabled: false

grafana:
  enabled: true
  sidecar:
    datasources:
      enabled: true
  image:
    tag: 6.7.0

prometheus:
  enabled: false

在此只開啟Grafana

 

修改Grafana的values.yaml,使其Service暴露方式為NodePort(默認為ClusterIp):

vim charts/grafana/values.yaml

service:
  type: NodePort
  port: 80
  nodePort: 30002  # 端口範圍:30000-32767
  targetPort: 3000
    # targetPort: 4181 To be used with a proxy extraContainer
  annotations: {}
  labels: {}
  portName: service

還有一處賬號密碼可以自定義修改下:

# Administrator credentials when not using an existing secret (see below)
adminUser: admin
adminPassword: admin

 

2promtail服務在構建時會自動掛載:

  1.  宿主機docker主目錄下的containers目錄,一般默認都為/var/lib/docker/containers
  2.  pod的日誌目錄,一般默認為/var/log/pods

這就需要特別注意一下,如果是修改過docker默認的存儲路徑的,需要將mount的路徑進行修改,promtail找不到對應的容器日誌

具體docker 存儲路徑,可以使用docker info 命令查詢

vim charts/promtail/values.yaml

volumes:
- name: docker
  hostPath:
    path: /data/lib/docker/containers  ## 我的是放在了data下
- name: pods
  hostPath:
    path: /var/log/pods

volumeMounts:
- name: docker
  mountPath: /data/lib/docker/containers  ## 掛載點也要進行修改
  readOnly: true
- name: pods
  mountPath: /var/log/pods
  readOnly: true

 

開始安裝:

helm install -n loki --namespace loki -f values.yaml ../loki-stack
2020/11/11 17:18:54 Warning: Merging destination map for chart 'logstash'. The destination item 'filters' is a table and ignoring the source 'filters' as it has a non-table value of: <nil>
NAME:   loki
LAST DEPLOYED: Wed Nov 11 17:18:53 2020
NAMESPACE: loki
STATUS: DEPLOYED

RESOURCES:
==> v1/ClusterRole
NAME                       AGE
loki-promtail-clusterrole  1s
loki-grafana-clusterrole   1s

==> v1/ClusterRoleBinding
NAME                              AGE
loki-promtail-clusterrolebinding  1s
loki-grafana-clusterrolebinding   1s

==> v1/ConfigMap
NAME                  DATA  AGE
loki-grafana          1     1s
loki-grafana-test     1     1s
loki-loki-stack       1     1s
loki-loki-stack-test  1     1s
loki-promtail         1     1s

==> v1/DaemonSet
NAME           DESIRED  CURRENT  READY  UP-TO-DATE  AVAILABLE  NODE SELECTOR  AGE
loki-promtail  2        2        0      2           0          <none>         1s

==> v1/Deployment
NAME          READY  UP-TO-DATE  AVAILABLE  AGE
loki-grafana  0/1    1           0          1s

==> v1/Pod(related)
NAME                         READY  STATUS             RESTARTS  AGE
loki-0                       0/1    ContainerCreating  0         2s
loki-grafana-56bf5d8d-8zcgp  0/1    Init:0/1           0         2s
loki-promtail-6r24r          0/1    ContainerCreating  0         2s
loki-promtail-fvnfc          0/1    ContainerCreating  0         2s

==> v1/Role
NAME               AGE
loki-promtail      1s
loki-grafana-test  1s
loki               1s

==> v1/RoleBinding
NAME               AGE
loki-promtail      1s
loki-grafana-test  1s
loki               1s

==> v1/Secret
NAME          TYPE    DATA  AGE
loki          Opaque  1     1s
loki-grafana  Opaque  3     1s

==> v1/Service
NAME           TYPE       CLUSTER-IP      EXTERNAL-IP  PORT(S)       AGE
loki           ClusterIP  10.109.216.219  <none>       3100/TCP      1s
loki-grafana   NodePort   10.100.203.138  <none>       80:30002/TCP  1s
loki-headless  ClusterIP  None            <none>       3100/TCP      1s

==> v1/ServiceAccount
NAME               SECRETS  AGE
loki               1        1s
loki-grafana       1        1s
loki-grafana-test  1        1s
loki-promtail      1        1s

==> v1/StatefulSet
NAME  READY  AGE
loki  0/1    1s

==> v1beta1/PodSecurityPolicy
NAME               PRIV   CAPS      SELINUX           RUNASUSER  FSGROUP    SUPGROUP  READONLYROOTFS  VOLUMES
loki               false  RunAsAny  MustRunAsNonRoot  MustRunAs  MustRunAs  true      configMap,emptyDir,persistentVolumeClaim,secret,projected,downwardAPI
loki-grafana       false  RunAsAny  RunAsAny          RunAsAny   RunAsAny   false     configMap,emptyDir,projected,secret,downwardAPI,persistentVolumeClaim
loki-grafana-test  false  RunAsAny  RunAsAny          RunAsAny   RunAsAny   false     configMap,downwardAPI,emptyDir,projected,secret
loki-promtail      false  RunAsAny  RunAsAny          RunAsAny   RunAsAny   true      secret,configMap,hostPath,projected,downwardAPI,emptyDir

==> v1beta1/Role
NAME          AGE
loki-grafana  1s

==> v1beta1/RoleBinding
NAME          AGE
loki-grafana  1s

 

創建完成後,通過暴露的svc訪問Grafana:

[root@Centos8 loki-stack]# kubectl get svc -n loki 
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
loki            ClusterIP   10.109.216.219   <none>        3100/TCP       113s
loki-grafana    NodePort    10.100.203.138   <none>        80:30002/TCP   113s
loki-headless   ClusterIP   None             <none>        3100/TCP       113s

 

二、開始使用Loki

通過服務器ip+30002訪問,登錄成功後,有一點需要注意的地方,也是非常容易踩坑的地方!!!

如果是安裝Loki時採用的以上方法,開啟了Grafana,那系統會自動配置好Data sources,應該不會有什麼問題。

但是,如果是手動搭建的Grafana,需要手動添加Data Sources時,一定注意:

數據源名稱中的Loki,L一定要是大寫!!!

如果不是大寫,會導致連接不到Loki源,一般回報錯:Error connecting to datasource: Loki: Bad Gateway. 502

 

 

如果是Loki,L大寫,結局完全不一樣

 

 

數據源添加完畢後,開始查看日誌

點擊Explore,可以看到選擇labels的地方

 

 

以下是labels的展現形式

 

 

選擇一個app:grafana的標籤查看一下

 

 

默認Loki會將stdout(正常輸出)類型和stderr(錯誤輸出)類型全部展示出來

 

如果只想查看stderr錯誤輸出類型的日誌,可以點擊stderr旁邊的放大鏡來展示:

 

 

 此時顯示的全部為錯誤日誌

 

除了這種辦法,還可以直接通過上邊的搜索欄,進行自定義的篩選,具體的語法問題,可以再自行查詢學習。

還可以查看 Prometheus 的 metrics 信息:

 

Loki的搭建及簡單的使用就到此結束了