使用讀寫分離模式擴展 Grafana Loki

Loki 由多個微服務組件構建而成,可以作爲一個可水平擴展的分佈式系統運行,Loki 的獨特設計可以將整個分佈式系統的代碼編譯成單個二進制或 Docker 映像,單個二進制文件的行爲由 -target 命令行標誌控制。

單體模式

最簡單的操作模式是設置 -target=all,這是默認的方式,不需要指定,這就是單體模式,它以單個二進制文件或 Docker 映像的形式在單個進程中運行 Loki 的所有微服務組件。

單體模式對於快速開始使用 Loki 以及每天數據量約 100GB 的讀寫量非常有用。將單體模式部署水平擴展至更多實例可以通過使用共享對象存儲,配置 memberlist_config 屬性在所有實例之間共享狀態。

可以通過使用 memberlist_config 配置和共享對象存儲運行兩個 Loki 實例來配置高可用性。以循環方式將流量路由到所有 Loki 實例。並行查詢受限於實例數量和定義的查詢並行度。

單體模式的安裝非常簡單,直接使用 grafana/loki-stack 這個 Helm Chart 包安裝即可。

讀寫分離模式

如果你每天的日誌量超過幾百 GB,或者你想進行讀寫分離,Loki 提供了簡單的可擴展部署模式。這種部署模式可以擴展到每天數 TB 甚至更多的日誌。

在這種模式下,Loki 的組件微服務被綁定到兩個目標中:-target=read-target=write,BoltDB compactor 服務將作爲讀取目標的一部分運行。

分離讀寫路徑有以下優點:

這種讀寫分離的模式需要在 Loki 前面有一個負載均衡器,它將 /loki/api/v1/push 流量路由到寫入節點,所有其他請求都轉到讀取節點,流量應該以循環方式發送。

安裝

我們同樣使用 Helm Chart 進行安裝,首先獲取讀寫分離模型的 Chart 包:

$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm pull grafana/loki-simple-scalable --untar --version 1.4.1
$ cd loki-simple-scalable

該 Chart 包支持下表中顯示的組件,Ingester、distributor、querier 和 query-frontend 都會安裝,其他組件是可選的。

zo1Jhu

這裏我們使用 MinIO 來作爲遠程數據存儲,分別配置讀和寫的 Loki 實例副本數爲 2,爲了在 Loki 前面添加一個負載均衡器,需要開啓 Gateway,對應的 Values 文件如下所示:

# ci/minio-values.yaml
loki:
  commonConfig:
    path_prefix: /var/loki
    replication_factor: 2
  authEnabled: false

# Configuration for the write
write:
  # -- Number of replicas for the write
  replicas: 3
  affinity: |
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          podAffinityTerm:
            labelSelector:
              matchLabels:
                {{- include "loki.writeSelectorLabels" . | nindent 12 }}
            topologyKey: kubernetes.io/hostname
  persistence:
    size: 1Gi
    storageClass: local-path

# Configuration for the read node(s)
read:
  # -- Number of replicas for the read
  replicas: 3
  affinity: |
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          podAffinityTerm:
            labelSelector:
              matchLabels:
                {{- include "loki.readSelectorLabels" . | nindent 12 }}
            topologyKey: kubernetes.io/hostname
  persistence:
    size: 1Gi
    storageClass: local-path

# Configuration for the gateway
gateway:
  # -- Specifies whether the gateway should be enabled
  enabled: true
# -------------------------------------
# Configuration for `minio` child chart
# -------------------------------------
minio:
  enabled: true
  accessKey: enterprise-logs
  secretKey: supersecret
  service:
    type: NodePort
    nodePort: 32000
  buckets:
    - name: chunks
      policy: none
      purge: false
    - name: ruler
      policy: none
      purge: false
    - name: admin
      policy: none
      purge: false
  persistence:
    size: 1Gi
    storageClass: local-path
  resources:
    requests:
      cpu: 100m
      memory: 256Mi

然後使用上面的 values 文件來安裝讀寫分離模式的 Loki:

$ helm upgrade --install loki -n logging -f ci/minio-values.yaml .
Release "loki" does not exist. Installing it now.
NAME: loki
LAST DEPLOYED: Fri Jun 17 14:53:20 2022
NAMESPACE: logging
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
 Welcome to Grafana Loki
 Chart version: 1.4.1
 Loki version: 2.5.0
***********************************************************************

Installed components:
* gateway
* read
* write

This chart requires persistence and object storage to work correctly.
Queries will not work unless you provide a `loki.config.common.storage` section with
a valid object storage (and the default `filesystem` storage set to `null`), as well
as a valid `loki.config.schema_config.configs` with an `object_store` that
matches the common storage section.

For example, to use MinIO as your object storage backend:

loki:
  config:
    common:
      storage:
        filesystem: null
        s3:
          endpoint: minio.minio.svc.cluster.local:9000
          insecure: true
          bucketnames: loki-data
          access_key_id: loki
          secret_access_key: supersecret
          s3forcepathstyle: true
    schema_config:
      configs:
        - from: "2020-09-07"
          store: boltdb-shipper
          object_store: s3
          schema: v11
          index:
            period: 24h
            prefix: loki_index_

安裝完成後查看 Pod 狀態是否正常:

$ kubectl get pods -n logging
NAME                            READY   STATUS    RESTARTS   AGE
loki-gateway-67f76958d7-bq46l   1/1     Running   0          91m
loki-minio-87c9bc6f5-jxdcn      1/1     Running   0          70m
loki-read-0                     1/1     Running   0          81s
loki-read-1                     1/1     Running   0          81s
loki-read-2                     1/1     Running   0          81s
loki-write-0                    1/1     Running   0          81s
loki-write-1                    1/1     Running   0          81s
loki-write-2                    1/1     Running   0          81s

可以看到分別部署了兩個副本的 write 和 read 的 Loki,另外還有一個 gateway 的 Pod,gateway 實際上就是一個 nginx 應用,用來將 /loki/api/v1/push 請求路由到 write 節點去,我們可以查看 gateway 的配置來驗證:

$ kubectl get cm -n logging loki-gateway -o yaml
apiVersion: v1
data:
  nginx.conf: |
    worker_processes  5;  ## Default: 1
    error_log  /dev/stderr;
    pid        /tmp/nginx.pid;
    worker_rlimit_nofile 8192;

    events {
      worker_connections  4096;  ## Default: 1024
    }

    http {
      client_body_temp_path /tmp/client_temp;
      proxy_temp_path       /tmp/proxy_temp_path;
      fastcgi_temp_path     /tmp/fastcgi_temp;
      uwsgi_temp_path       /tmp/uwsgi_temp;
      scgi_temp_path        /tmp/scgi_temp;

      default_type application/octet-stream;
      log_format   main '$remote_addr - $remote_user [$time_local]  $status '
            '"$request" $body_bytes_sent "$http_referer" '
            '"$http_user_agent" "$http_x_forwarded_for"';
      access_log   /dev/stderr  main;

      sendfile     on;
      tcp_nopush   on;
      resolver kube-dns.kube-system.svc.cluster.local;

      server {
        listen             8080;

        location = / {
          return 200 'OK';
          auth_basic off;
        }

        location = /api/prom/push {
          proxy_pass       http://loki-write.logging.svc.cluster.local:3100$request_uri;
        }

        location = /api/prom/tail {
          proxy_pass       http://loki-read.logging.svc.cluster.local:3100$request_uri;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
        }

        location ~ /api/prom/.* {
          proxy_pass       http://loki-read.logging.svc.cluster.local:3100$request_uri;
        }

        location = /loki/api/v1/push {
          proxy_pass       http://loki-write.logging.svc.cluster.local:3100$request_uri;
        }

        location = /loki/api/v1/tail {
          proxy_pass       http://loki-read.logging.svc.cluster.local:3100$request_uri;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
        }

        location ~ /loki/api/.* {
          proxy_pass       http://loki-read.logging.svc.cluster.local:3100$request_uri;
        }
      }
    }
kind: ConfigMap
metadata:
  annotations:
    meta.helm.sh/release-name: loki
    meta.helm.sh/release-namespace: logging
  creationTimestamp: "2022-06-17T06:53:22Z"
  labels:
    app.kubernetes.io/component: gateway
    app.kubernetes.io/instance: loki
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: loki
    app.kubernetes.io/version: 2.5.0
    helm.sh/chart: loki-simple-scalable-1.4.1
  name: loki-gateway
  namespace: logging
  resourceVersion: "4968787"
  uid: ba9ba1c0-8561-41cb-8b55-287f352b5ee8

上面就是一個典型的 Nginx 配置,從配置可以看出會把請求 /api/prom/push/loki/api/v1/push 這兩個 Push API 代理到 http://loki-write.logging.svc.cluster.local:3100$request_uri;,也就是上面的兩個 loki-write 節點,而讀取相關的接口被代理到 loki-read 節點,然後 loki-write 啓動參數配置 -target=writeloki-read 啓動參數配置 -target=read,這樣去實現讀寫分離。不過讀寫的應用是共享同一個配置文件的,如下所示:

$ kubectl get cm -n logging loki -o yaml
apiVersion: v1
data:
  config.yaml: |
    auth_enabled: false
    common:
      path_prefix: /var/loki
      replication_factor: 2
      storage:
        s3:
          access_key_id: enterprise-logs
          bucketnames: chunks
          endpoint: loki-minio.logging.svc:9000
          insecure: true
          s3forcepathstyle: true
          secret_access_key: supersecret
    limits_config:
      enforce_metric_name: false
      max_cache_freshness_per_query: 10m
      reject_old_samples: true
      reject_old_samples_max_age: 168h
      split_queries_by_interval: 15m
    memberlist:
      join_members:
      - loki-memberlist
    ruler:
      storage:
        s3:
          bucketnames: ruler
    schema_config:
      configs:
      - from: "2022-06-17"
        index:
          period: 24h
          prefix: loki_index_
        object_store: s3
        schema: v12
        store: boltdb-shipper
    server:
      grpc_listen_port: 9095
      http_listen_port: 3100
......

其中 common.storage.s3  指定的是 MinIO 的相關配置,通過 memberlist.join_members 來指定成員,其實就是所有的讀寫節點:

$ kubectl get svc loki-memberlist -n logging -o wide
NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE   SELECTOR
loki-memberlist   ClusterIP   None         <none>        7946/TCP   54m   app.kubernetes.io/instance=loki,app.kubernetes.io/name=loki,app.kubernetes.io/part-of=memberlist
$ kubectl get pods -n logging -l app.kubernetes.io/part-of=memberlist
NAME           READY   STATUS    RESTARTS   AGE
loki-read-0    1/1     Running   0          32s
loki-read-1    1/1     Running   0          72s
loki-read-2    1/1     Running   0          115s
loki-write-0   1/1     Running   0          4s
loki-write-1   1/1     Running   0          55s
loki-write-2   1/1     Running   0          116s

到這裏我們就完成了 Loki 讀寫分離模式的部署。

Promtail 寫數據

爲了驗證應用是否正常,接下來我們再安裝 Promtail 和 Grafana 來進行數據的讀寫。

獲取 promtail 的 Chart 包並解壓:

$ helm pull grafana/promtail --untar
$ cd promtail

創建一個如下所示的 values 文件:

# ci/simple-values.yaml
rbac:
  pspEnabled: false
config:
  lokiAddress: http://loki-gateway/loki/api/v1/push

注意我們需要將 Promtail 中配置的 Loki 地址爲 http://loki-gateway/loki/api/v1/push,這樣就是 Promtail 將日誌數據首先發送到 Gateway 上面去,然後 Gateway 根據我們的 Endpoints 去轉發給 write 節點,使用上面的 values 文件來安裝 Promtail:

$ helm upgrade --install promtail -n logging -f ci/simple-values.yaml .
Release "promtail" does not exist. Installing it now.
NAME: promtail
LAST DEPLOYED: Fri Jun 17 16:01:08 2022
NAMESPACE: logging
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
***********************************************************************
 Welcome to Grafana Promtail
 Chart version: 5.1.0
 Promtail version: 2.5.0
***********************************************************************

Verify the application is working by running these commands:

* kubectl --namespace logging port-forward daemonset/promtail 3101
* curl http://127.0.0.1:3101/metrics

正常安裝完成後會在每個節點上運行一個 promtail:

$ kubectl get pods -n logging -l app.kubernetes.io/name=promtail
NAME             READY   STATUS    RESTARTS   AGE
promtail-5r9hl   1/1     Running   0          2m25s
promtail-85mk4   1/1     Running   0          2m25s
promtail-qlfnv   1/1     Running   0          2m25s

正常 promtail 就已經在開始採集所在節點上的所有容器日誌了,然後將日誌數據 Push 給 gateway,gateway 轉發給 write 節點,我們可以查看 gateway 的日誌:

$ kubectl logs -f loki-gateway-67f76958d7-bq46l -n logging
10.244.1.170 - - [17/Jun/2022:08:09:03 +0000]  204 "POST /loki/api/v1/push HTTP/1.1" 0 "-" "promtail/2.5.0" "-"
10.244.1.170 - - [17/Jun/2022:08:09:04 +0000]  204 "POST /loki/api/v1/push HTTP/1.1" 0 "-" "promtail/2.5.0" "-"
10.244.2.205 - - [17/Jun/2022:08:09:05 +0000]  204 "POST /loki/api/v1/push HTTP/1.1" 0 "-" "promtail/2.5.0" "-"
......

可以看到 gateway 現在在一直接接收着 /loki/api/v1/push 的請求,也就是 promtail 發送過來的,正常來說現在日誌數據已經分發給 write 節點了,write 節點將數據存儲在了 minio 中,可以去查看下 minio 中已經有日誌數據了,前面安裝的時候爲 minio 服務指定了一個 32000 的 NodePort 端口:

登錄信息爲:

可以看到 minio 的 chunks 這個 bucket 中並沒有日誌數據,這是因爲上面我們創建的 bucket 默認只有讀取的權限,我們可以將該 bucket 修改爲具有讀寫的權限:

正常修改後就會產生一個 fake 的目錄了,這是默認沒有提供多租戶的數據目錄,該目錄下面存儲着日誌的 chunk 數據:

這是 Loki 日誌的寫入的路徑。

Grafana 讀數據

下面我們來驗證下讀取路徑,安裝 Grafana 對接 Loki:

$ helm pull grafana/grafana --untar
$ cd grafana

創建如下所示的 values 配置文件:

# ci/simple-values.yaml
service:
  type: NodePort
  nodePort: 32001
rbac:
  pspEnabled: false
persistence:
  enabled: true
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  size: 1Gi

直接使用上面的 values 文件安裝 Grafana:

$ helm upgrade --install grafana -n logging -f ci/simple-values.yaml .
Release "grafana" has been upgraded. Happy Helming!
NAME: grafana
LAST DEPLOYED: Fri Jun 17 17:00:24 2022
NAMESPACE: logging
STATUS: deployed
REVISION: 2
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace logging grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana.logging.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:
export NODE_PORT=$(kubectl get --namespace logging -o jsonpath="{.spec.ports[0].nodePort}" services grafana)
     export NODE_IP=$(kubectl get nodes --namespace logging -o jsonpath="{.items[0].status.addresses[0].address}")
     echo http://$NODE_IP:$NODE_PORT


3. Login with the password from step 1 and the username: admin

可以通過上面提示中的命令獲取登錄密碼:

$ kubectl get secret --namespace logging grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

然後使用上面的密碼和 admin 用戶名登錄 Grafana:

登錄後進入 Grafana 添加一個數據源,這裏需要注意要填寫 gateway 的地址 http://loki-gateway

保存數據源後,可以進入 Explore 頁面過濾日誌,比如我們這裏來實時查看 gateway 這個應用的日誌,如下圖所示:

如果你能看到最新的日誌數據那說明我們部署成功了讀寫分離模式的 Loki,讀寫分離模式可以大大提高 Loki 的性能和容量,如果這種模式還不能滿足你的數據量,那麼可以使用微服務模式來部署 Loki 了,未完待續......

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/G6ohE7U-lPtMAZFFmCkL2w