[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 を調べてきて、
適当なディレクトリにダウンロードします。
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 のログをステータスコードやパスでフィルタして見たりできます。

pipeline_stages では他にもいろいろ利用可能です。詳細は下記にまとまっています。
https://github.com/grafana/loki/blob/v1.2.0/docs/clients/promtail/pipelines.md
systemd journal ログを収集
Promtail は systemd の journal ログも収集できます。scrape_configs の journal で指定します。
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 のユニットごとのログが見れたりします。

なお、下記によるとラベルは元の 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_stages で metrics 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_configs と pipeline_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 だけログの内容から抽出するように設定するぐらいで始めるのでも良いかも。