[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
だけログの内容から抽出するように設定するぐらいで始めるのでも良いかも。