Grafana Loki を素振りしたメモ

[Loki]Grafana Loki を素振りしたメモ

Grafana Lokiちょっと前に v1.0.0 がリリースされて GA になっていた ので素振りしました。

試したバージョンは v1.2.0 です。Kubernetes とかはよくわからないのでスルーしてます。

概要

Grafana Loki は Grafana Labs で作られているログストレージ&クエリエンジンです。単体だとログを貯めるしかできませんが Grafana でデータソースとして指定して貯めたログを閲覧できます。

Loki へログを送るには Promtail というエージェントを使います(他にもいくつか公式でサポートされている方法はあります)。

似たようなプロダクトでは「EFKスタック」として知られる ElasticSearch/Fluentd/Kibana が有名です。

Grafana と Loki

Grafana と Loki を Docker でサクッと実行します。

version: '3.7'

services:
  grafana:
    container_name: grafana
    image: grafana/grafana
    ports:
      - '3000:3000'

  loki:
    container_name: loki
    image: grafana/loki
    ports:
      - '3100:3100'

Promtail

Promtail を適当な Apache が動いている Linux サーバにインストールして実行します。下記から最新版の URL を調べてきて、

https://github.com/grafana/loki/releases

適当なディレクトリにダウンロードします。

curl -O -L https://github.com/grafana/loki/releases/download/v1.2.0/promtail-linux-amd64.zip
unzip promtail-linux-amd64.zip
chmod +x promtail-linux-amd64

Promtail の設定ファイル promtail.yml を作成します。

server:
  # 9080 ポートで HTTP でリッスンする
  # ブラウザで開くとログのディスカバリの状態が見れます
  http_listen_port: 9080

  # grpc のリッスンポート、0 ならランダムに決定される・・・これなにに使っているの?
  grpc_listen_port: 0

clients:
  # Loki の URL を指定
  - url: http://localhost:3100/loki/api/v1/push

# ログのディスカバリの設定
scrape_configs:

  # Apache のアクセスログを収集
  - job_name: access_log
    static_configs:
      - targets:
          # targets は localhost または自ホスト名のどちらかを指定する必要がある
          # とドキュメントに書いてた気がするけど何でも通る? というかこれ指定できる意味あるの?
          - localhost
        labels:
          # __path__ ラベルでファイル名を指定する、ワイルドカードも指定可能
          __path__: /var/log/httpd/access_log

設定ファイルを指定して開始します。

./promtail-linux-amd64 -config.file ./promtail.yml

docker-compose で立ち上げていた Grafana にログインしてデータソースに Loki を http://loki:3100 のような URL で追加します。 上手く追加できれば、Explore の画面から Aapche のアクセスログが閲覧できます。

Apache のログをラベル付け

先程の設定ではログに filename ラベルで /var/log/httpd/access_log という値が付与されます。static_configs でログファイルを収集すればこのラベルは自動的に付与されます。__path__ にはワイルドカードが指定できますが filename は実際のファイル名になるので、複数のログにマッチしてもこのラベルで区別できます。

pipeline_stages でログの内容に基づいてもっと細かな制御ができます。

scrape_configs:
  - job_name: access_log
    static_configs:
      - targets:
          - localhost
        labels:
          __path__: /var/log/httpd/access_log
    pipeline_stages:
      - regex:
          # 正規表現で名前付きキャプチャ
          expression: |-
            ^(?P<addr>\S+)\s+\S+\s+\S+\s+\[(?P<time>.*?)\]\s+"(?P<method>\S+)\s+(?P<path>\S+)\s+\S+"\s+(?P<status>\S+)\s+
      - labels:
          # ↑でキャプチャした名前・値でラベルを付与
          addr:
          method:
          path:
          status:
      - timestamp:
          # ↑でキャプチャした値でログのタイムスタンプを上書き、デフォはログが収集された時間です
          source: time
          # golang での日時のフォーマット指定
          format: '02/Jan/2006:15:04:05 -0700'

この例では regex で正規表現で名前付きキャプチャした値を元にラベルを付与し、さらにログに出力されている日時からタイムスタンプを抽出しています。

次のように Apache のログをステータスコードやパスでフィルタして見たりできます。

f:id:ngyuki:20200103231223p:plain

pipeline_stages では他にもいろいろ利用可能です。詳細は下記にまとまっています。

https://github.com/grafana/loki/blob/v1.2.0/docs/clients/promtail/pipelines.md

systemd journal ログを収集

Promtail は systemd の journal ログも収集できます。scrape_configsjournal で指定します。

scrape_configs:
  # systemd の journal からログを収集
  - job_name: systemd
    journal:
      # Promtail の起動時にどこまで過去のログを読むかを指定
      max_age: '1h'
      # journal ログのディレクトリ
      path: /run/log/journal
    relabel_configs:
      # session-12345.scope のようなユニットは除外
      - source_labels: [__journal__systemd_unit]
        regex: ^session-\d+.scope$
        action: drop
      # ユニット名でラベルを付ける
      - source_labels: [__journal__systemd_unit]
        target_label: systemd

収集したログには __journal__systemd_unit のようなラベルが付与されます。先頭が __ のラベルは Loki へ送信する際に除去されるため、これらのラベルはそのままではラベルとして残りません。

↑の例では relabel_configs で、ラベルの値が特定のパターンに一致するログを除外したり、ユニット名も Loki へ送信するためにラベルを付け直したりしています。

Prometheus でも relabel_configs という設定がありますが、だいたい同じように使えます。

次のように、systemd のユニットごとのログが見れたりします。

f:id:ngyuki:20200103231252p:plain

なお、下記によるとラベルは元の journal のエントリのフィールド名を小文字化したものにプレフィックス __journal_ を付与して付けられるようです。

https://github.com/grafana/loki/blob/v1.2.0/pkg/promtail/targets/journaltarget.go#L307

どのようなフィールドがあるかは journalctl -o verbose とか journalctl -o json-pretty とかで見られます。

syslog

Promtail は syslog サーバとしてリッスンして syslog プロトコルでログを受信することもできます・・・のですが、最新の v1.2.0 だと未サポートで master のバージョンじゃないとまだ syslog は使えませんでした。きっと次のバージョンでは syslog も使えるようになります。

metrics stage

pipeline_stagesmetrics stage を使うと、ログを Loki に送信するのではなく、パターンにマッチしたログの出現回数をカウントし、Prometheus から参照可能な形式で /metrics で公開できます。

scrape_configs:
  - job_name: access_log
    static_configs:
      - targets:
          - localhost
        labels:
          __path__: /var/log/httpd/access_log
    pipeline_stages:
      - regex:
          expression: |-
            ^(?P<addr>\S+)\s+\S+\s+\S+\s+\[(?P<time>.*?)\]\s+"(?P<method>\S+)\s+(?P<path>\S+)\s+\S+"\s+(?P<status>\S+)\s+
      - timestamp:
          source: time
          format: '02/Jan/2006:15:04:05 -0700'
      - metrics:
          # log_lines_total というメトリクス名で公開
          log_lines_total:
            # カウンターとして公開、他にも Gauge や Histogram が指定可能
            type: Counter
            description: total number of log lines
            # パイプラインで time というフィールドがある時だけ処理する
            source: time
            config:
              # メトリクス値をインクリメントする
              # Counter なら inc 以外に add も指定できる(source の値で加算される)
              action: inc

この設定で、下記のようなメトリクスが Promtail の HTTP エンドポイントの /metrics で公開されます。

# HELP promtail_custom_log_lines_total total number of log lines
# TYPE promtail_custom_log_lines_total counter
promtail_custom_log_lines_total{filename="/var/log/httpd/access_log"} 11

例えば、ステータスコードごとの統計などをメトリクスとして Prometheus へ入れたりできます。Loki でもログのクエリで似たような集計はできると思いますが、この方法の方が圧倒的に負荷は小さそうですね。

relabel_configs と pipeline_stages

ログに対して正規表現などでアレコレする方法として relabel_configspipeline_stages の2種類ありますが次のような違いがあります。

relabel_configs はログにラベルを条件にラベルやラベルの値を置換したりラベルを付け外ししたりログをドロップしたりできます。要するにラベルにたいして作用し(ドロップもまあ全部のラベルを引っ剥がすと思えば)、ログの内容には手を出しません。

pipeline_stages はログの内容を条件にいろいろできます(ログの内容を変更したり、ラベルを変更したり、タイムスタンプを変更したり)。また、match でラベルも条件にできます。

ログを HTTP で送信

Loki へログを HTTP で直接登録できます。通常であれば Protobuf を使うようなのですが Content-Type: application/json を指定すれば JSON でも遅れます。

curl -H 'Content-Type: application/json' -XPOST -s 'http://localhost:3100/loki/api/v1/push' --data-raw '{
  "streams": [
    {
      "stream": {
        "ore": "are"
      },
      "values": [
          [ "1577433513448137047" , "this is log" ]
      ]
    }
  ]
}'

"stream" の部分でラベルを、"values" のとこでナノ秒単位のタイムスタンプとログのメッセージを指定します。

さいごに

ElasticSearch と比べると Loki はログのラベルのみにインデックスを付けるため軽量で運用が簡単とのことです。ただし、ログのテキストの内容に基づいたクエリは検索範囲内のすべてのログをロードする必要があるためその種のクエリは重くなります。

Promtail は fluentd と比べるとできることが少なそうですが、ワンバイナリで低依存でインストールできるのでとりあえず入れる分には気が楽そうです(というか fluentd 多機能すぎる・・Ruby がバコーンと入るのもどうかと、比べるなら fluentbit の方かな?)。

強いて言えば、Promtail の pipeline_stages のデバッグが辛いです。fluentd なら宛先を stdout にすることで試行錯誤もやりやすかったと思うんですけど、Promtail でもデバッグログを有効にすればパイプラインの途中経過が見られるようなのですが見難すぎる・・pipeline_stages の途中にデバッグステージみたいなのを挟んでピンポイントでログのメッセージ、ラベル、キャプチャなどを標準出力に出したりできないものですかね。

ただ、どうせ Prometheus のために Grafana を立てているならとりあえず promtail も入れておいて pipeline_stages で凝ったことはせずに timestamp だけログの内容から抽出するように設定するぐらいで始めるのでも良いかも。