$30 off During Our Annual Pro Sale. View Details »

Grafana Lokiで始めるログ管理

nutslove
February 22, 2023

Grafana Lokiで始めるログ管理

Grafana Lokiについての社内勉強会で使用した資料になります。
Grafana Lokiを使っている方や導入を検討されている方にお役に立てればと思います。
よろしくお願いいたします。

nutslove

February 22, 2023
Tweet

More Decks by nutslove

Other Decks in Technology

Transcript

  1. 本日のゴール Who • 大量のログを(安価に)管理できるツールをお探しの方 • Lokiを使っているシステム担当者 • Lokiを運用している管理者 What •

    Lokiの特性を理解したうえでLokiの導入について判断 • Lokiの(正しい)使い方を知る • Lokiの仕組みを理解して安定的に運用(できる状態を目指す)
  2. 本日話す内容 • Lokiとは • Lokiの特徴 • Lokiアーキテクチャ/コンポーネント • 処理の流れ(Write/Read) •

    data lossを防ぐ仕組み • promtailについて • APIを使ったログ送信 • Lokiでできること&使い方(LogQL) • Lokiアンチパターン • Loki/promtailのオブザーバビリティ • まとめ
  3. Lokiとは • Grafana LabsがPrometheusにインスパイアされて 開発したOSSログ管理ツール • ログを受信して保存する/クエリーに対してデータを返す側  ログ送信にはpromtailという送信用ツールが必要 ※Fluentd、Fluent

    Bit、Logstashも利用可能 ※APIにHTTP POSTメソッドで直接ログを送信することも可能 • ログの可視化にはGrafanaというダッシュボードツールが必要 ※ElasticSearch(Loki)とKibana(Grafana)のような関係 • ログ検索にはLogQLという独自のクエリー言語を使用
  4. Lokiの特徴 • Scalability  各コンポーネントが独立していてWrite/Readごとにスケールアウトが可能 • Multi tenant  Tenant

    ID(X-Scope-OrgID)でデータを分割/識別することで、 1つのLokiクラスターに複数テナントの収容を可能に • Low cost  ログ(chunk)/indexともにObject Storage(e.g. AWS S3)に保存  Metadata(Label、Timestamp)だけをindexing How to scale and secure your logs cost-effectively with Grafana Loki
  5. Lokiアーキテクチャ A guide to deploying Grafana Loki and Grafana Tempo

    without Kubernetes on AWS Fargate | Grafana Labs Stateless Stateful ログ送信 クエリー この部分がLoki
  6. Lokiのコンポーネント • Distributor  最初にログを受信し、validation checkで問題なければ(複数の)Ingesterにデータを流す • Ingester  メモリに一時的にデータを保存し、一定の条件を満たしたデータをObject

    Storageに流す メモリ上のログに対するクエリーにはログデータを返す • Query-frontend  クエリーを分割しキューイングする。また、複数Querierからの結果をまとめてGrafanaに返す • Querier  キューからクエリーをpullしログ検索を行う Ingesterに先にクエリーを投げ、Ingesterになかったらバックエンドにクエリーを投げる • Compactor (optional)  複数ファイルに分散されているIndexファイルを1つのファイルにまとめ、重複排除を行う • Ruler (optional)  Alert/Recording Rules関連コンポーネント • Components | Grafana Loki documentation • Architecture | Grafana Loki documentation
  7. 処理の流れ(Write Path) Client (e.g. promtail) Distributor Distributor Distributor Distributor Distributor

    Ingester • Validation check  RateLimit check  Label check  Log Size check  Timestamp check ・ ・ Request Validation & Rate-Limit Errors | Grafana Loki documentation • Consistent hashingにより データがhashedされ、 どのIngester(複数)に送るかが決まる • Replication factor(Defaultは3つ) の数分Ingesterに送信される • floor(Replication factor / 2) + 1 の数以上のIngesterへの書込みを保証  書込みに成功したIngesterの数が 上記未満の場合はエラーを返し、 promtail側でリトライされる • ログはIngesterのメモリとDiskに保存される ※Diskへの書込みはdata lossを防ぐため(WAL) Object Storage • 以下の条件を満たしたChunkは IngesterのメモリからObject Storage にflushされる  「chunk_idle_period」の間 chunkにupdateがなかった時  chunkサイズが「chunk_target_size」 に達した時  chunkが生成されてから 「max_chunk_age」が経過した時 • Disk上のログは定期的に削除される Configuration | Grafana Loki documentation
  8. 処理の流れ(Read Path) Grafana Distributor Distributor Query frontend Distributor Distributor Querier

    • クエリーを以下の単位で分割  「split_queries_by_interval」 で指定した間隔(defaultは30分) • 分割したクエリーをキューイング • 複数のQuerierから返ってきた データをまとめてGrafanaに返す Query Frontend | Grafana Loki documentation • キューからクエリーをpullし、クエリーを実行  複数のQuerierでパラレルで実行される パラレルの上限は「max_query_parallelism」(defaultは32)で指定可能 • 直近のログはObject Storageより先にIngesterにクエリーを投げて、 IngesterになかったらObject Storageにクエリーを投げる  現時点からマイナス何時間までIngesterに要求するかは 「query_ingesters_within」(defaultは3h)で指定可能 • 複数Ingesterからの以下3つが重複するデータを重複排除する  nanosecond timestamp, label set, and log message Object Storage Distributor Distributor Ingester クエリー (LogQL) Components | Grafana Loki documentation
  9. data lossを防ぐ仕組み 1. Replication factor  ログを複数のIngesterに送り、同じデータを複数のIngesterで持つことで 一部のIngesterが落ちてもデータが失われないようにする仕組み  いくつのIngester障害まで許容できるかは「replication_factor」に

    指定した数により異なる 例えば「replication_factor」 が5の場合は前ページのfloor(Replication factor / 2) + 1ルールにより3つのIngesterへの書込みが保証されるので、2 つのIngesterが落ちてもデータは失われない  Ingester(のPod)が複数のAZに分散されるように「podAffinity」や 「topologySpreadConstraints」などを適切に設定する • The essential config settings you should use so you won’t drop logs in Loki | Grafana Labs • Components | Grafana Loki documentation
  10. data lossを防ぐ仕組み 2. WAL(Write Ahead Log)  v2.2から導入された機能  Ingesterがcrashしてメモリ上のデータが消えても

    データを復元できるよう、データをメモリとディスク両方に書き込む  crashしたIngesterがrestartされる時、Diskからデータを復元する データ復元が完了するまでIngesterはReady状態にならない  Ingesterごとに専用のディスク(e.g. EBS)を払い出すこと  ディスク容量が一杯になってWALへの書込みができなくても Lokiへの書込み自体は失敗しないのでWAL用ディスク空き容量を監視すること • The essential config settings you should use so you won’t drop logs in Loki | Grafana Labs • Write Ahead Log | Grafana Loki documentation • Write-Ahead Logs | Grafana Loki documentation
  11. promtailのログ送信 • 1秒ごとにログを送信  「clients.batchwait」で変更可能 • positionファイル  promtailがどこまでログを送ったかを記録するファイル 

    promtailプロセス(再)起動時このファイルを参照してどこから送るかを決める • Retry  デフォルトで8.5分に渡って10回リトライし、リトライがすべて失敗したらログをDrop  「backoff_config.max_retries」を変えることでリトライ回数を変更可能 ※retry時、すべてのretry失敗時のpromtailから出力されるログ Configuration | Grafana Loki documentation Jan 27 04:09:19 ip-10-111-1-195.oci.ad promtail[15505]: level=warn ts=2023-01-27T04:09:19.841944217Z caller=client.go:369 component=client host=lee-loki-switch-test-NLB-fc4597fd6a5ad433.elb.ap-northeast-1.amazonaws.com msg="error sending batch, will retry" status=-1 error="Post ¥"http://lee-loki-switch-test-NLB-fc4597fd6a5ad433.elb.ap-northeast-1.amazonaws.com/loki/api/v1/push¥": context deadline exceeded" Jan 27 04:09:19 ip-10-111-1-195.oci.ad promtail[15505]: level=error ts=2023-01-27T04:09:19.841997354Z caller=client.go:380 component=client host=lee-loki-switch-test-NLB-fc4597fd6a5ad433.elb.ap-northeast-1.amazonaws.com msg="final error sending batch" status=-1 error="Post ¥"http://lee-loki-switch-test-NLB-fc4597fd6a5ad433.elb.ap-northeast-1.amazonaws.com/loki/api/v1/push¥": context deadline exceeded"
  12. promtailのpipeline • Lokiに連携する前にログの編集/除外等を行う • 複数のstageから構成されている  replaceステージ  特定の文字列を置換 ケース

    設定例 P/S番のユーザアカウントを****に 変換(マスキング)する - replace: expression: "([SP][0-9]{6})" replace: "****"
  13. promtailのpipeline  dropステージ  特定の文字列を含むログをdrop  multilineステージ  複数行に渡って出力されるログを1つのログとして扱う ケース

    設定例 debugという文字列を含むログをDrop - drop: expression: ".*debug.*" ケース 設定例 以下のログを[YYYY-MM-DD hh:mm:ss]から次の[YYYY- MM-DD hh:mm:ss]までを1つのログとして認識する [2020-12-03 13:59:59] ERROR in app: Exception on /error [PUT] Traceback (most recent call last): File "/home/pallets/.pyenv/versions/3.8.5/lib/python3.8/site- packages/flask/app.py", line 2447, in wsgi_app [2020-12-03 14:01:59] ERROR in app: Exception on /error [PUT] - multiline: firstline: '^¥[¥d{4}-¥d{2}-¥d{2} ¥d{1,2}:¥d{2}:¥d{2}¥]'
  14. promtailのpipeline  matchステージ  LogQLに合致するログのみdropまたはstageを適用 ケース 設定例 error_logというLabelを持つログのうち、 errorまたはfatal文字列を含まないログをDrop -

    match: selector: ‘{job=“error_log”} !~ “(error|fatal)”’ action: drop 以下のようなjson形式のapi_logというLabelを持つ ログの一部のkey(app, level)をLoki(LogQL)でLabelとして使え るようにする {“app”: “ingester”, “level”: “warn”, “msg”: “something bad”} {“app”: “querier”, “level”: “info”, “msg”: “something good”} - match: selector: ‘{job=“api_log”}’ stages: - json: expressions: app: level:
  15. promtailのpipeline  metricsステージ  特定の文字列を含む/LogQLに合致するログに対して Prometheus形式のメトリクスを生成し、promtailから開示する  ここで生成されたメトリクス名は「promtail_custom_」から始まる ケース 設定例

    pod_name=loki-distributed-gatewayのLabelを持つ ログにstatus_code, method, uriのLabelを付与し、 「promtail_custom_loki_gateway_total」という名前でメトリ クスを生成する → メトリクスにもstatus_code, method, uriのLabelが付 与されるのでstatus_code, method, uriごとに メトリクスを集計することができる - match: selector: '{pod_name=~"multi-tenant-loki-distributed-gateway-.*"}' stages: - regex: expression: "^.* (?P<status_code>[0-9]{3}) ¥"(?P<method>[A-Z].*) (?P<uri>.*?) " - labels: status_code: method: uri: - metrics: loki_gateway_total: type: Counter config: match_all: true action: inc • Pipelines | Grafana Loki documentation • Stages | Grafana Loki documentation
  16. APIを使ったログ送信 • Lokiは様々なHTTP API エンドポイントを公開しており、ログpush用 エンドポイント「/loki/api/v1/push」に直接ログを送ることも可能 • 実際の活用例 1. CloudWatch

    LogsをLokiに連携  Lokiから正式に提供されているものがあり、以下のデプロイ方法から選択 1. Terraform 2. CloudFormation 3. ソースコードをダウンロードし、build/zip化して直接アップロード (推奨) HTTP API | Grafana Loki documentation Lambda promtail Loki CloudWatchLogs Lambda Promtail | Grafana Loki documentation promtailも/pushエンドポイントを公開していて、 Lokiに直接送るかpromtailを経由するか選択可能
  17. APIを使ったログ送信 2. S3上のALB/CloudfrontログをLokiに連携 • 注意点として独自にリトライと連携失敗検知の仕組みを考える必要がある  LambdaのタイムアウトをMAXの15分に設定  CloudWatchLogsの場合はbuild前に以下コードのmaxRetriesを修正 

    Pythonの場合は標準ライブラリ(e.g. urllib3)のリトライ機能を使用 S3 Lambda SNS promtail Loki SNSを経由せずに直接Lambdaに送ることも可能 ※S3イベントを複数のところに連携したい場合はSNSを経由 Lambdaサンプルコード S3イベント通知 S3イベント通知 Lambda Promtailのリトライに関するコードの部分
  18. Lokiでできること 1. ログ検索  ログ検索は必ずLabelから入る(選択する)ので 検索したいログに付与されているLabelを把握している必要がある 2. ログからメトリクス生成  LogQLはログからメトリクスを生成するMetric

    queriesにも対応している 3. ログに対するアラート設定  Rulerを使ってAlertManagerにアラートを連携する方式と Grafanaにて直接Lokiに対してアラートを設定する方式(推奨)がある  アラートにはMetric queriesを使う必要がある ※特定文字列を含むログが何件以上というような 数値に対してアラートを設定する必要があるため Metric queries | Grafana Loki documentation
  19. LogQL 1.ログ検索 {Label selector} filter operator | parser expression |

    line format expression 1-1. filter operator operator 説明 LogQL例 |= 特定の文字列を含むログに絞る (grep) {app=“loki”}|=“fatal” != 特定の文字列を含まないログに絞る (grep -v) {app=“loki”}!=“info” |~ 特定の文字列を含むログに絞る (正規表現) {app=“loki”}|~“error|warn|quer.+” !~ 特定の文字列を含まないログに絞る (正規表現) {app=“loki”}!~“info|debug” = 検索したいログのLabelを指定 {namespace=“monitoring”,app=“loki”}| env=“stg” =~ 検索したいログのLabelを指定 (正規表現) {namespace=“monitoring”,app=“loki”}| env=~“dev.*” > >= < <= 検索したいログのLabelの値が数値の場合、 比較演算子を使うことが可能 {pod_name=“nginx”}| bytes_sent > 500 必須 任意
  20. LogQL 1-2.Parser expression Parser 説明 ログ例 LogQL例 json json形式ログの KeyをLabelに変換

    { "servers": “jenkins_slave", “method”: “PUT”, "request": { "time": "6.032", "size": "55" } } {host=“jenkins_slave”} | json | ¥ method=“PUT” and request_time > 5 logfmt “=“を区切り文字として Key=ValueのKeyを Labelに変換 app=loki component=ruler env=stg app=loki component=distributor env=stg app=loki component=distributor env=prd {host=“loki-cluster”} | logfmt | ¥ component=“distributor” and env=“prd” pattern パターンが決まって いるのログに対して 特定部分をLabelに設定 2023/02/21 15:17 204 POST /api/v1/push 2023/02/21 16:12 404 GET /api/v1/read 2023/02/21 16:15 500 PUT /api/v1/update {app=“nginx”} | ¥ pattern “<_> <_> <status_code> <method> <url>” regex 正規表現を使って 特定部分をLabelに設定 GET /api/v1/read (200) 1.5s PUT /api/v1/update (404) 2.1s {app=“api”} | regexp “(?P<method>¥¥w+) ¥ (?P<url>[¥¥w|/]+) ¥¥((?P<status_code>¥¥d+?)¥¥) ¥ (?P<duration>.*)” Syntax · google/re2 Wiki · GitHub
  21. LogQL 1-3. Line format expression • Log queries | Grafana

    Loki documentation • Query examples | Grafana Loki documentation Line format 説明 ログ例 LogQL例 line_format Labelを使って 出力するログの フォーマットを編集 app=loki component=ruler env=stg app=loki component=distributor env=stg app=loki component=distributor env=prd {host=“loki-cluster”} | logfmt | line_format ¥ “component is {{.component}} in {{.env}} env”
  22. LogQL 2.ログからメトリクス生成  Log queriesに追加で指定する形 function 説明 LogQL例 count_over_time() Time

    Range間で 発生したログ件数 count_over_time({job=“app_log”}|~”err|error|fatal”[5m]) rate() Time Range間の 1秒当たりの平均値 rate({app=“nginx”} | ¥ pattern “<_> <_> <status_code> <method> <url>”[1m]) sum() 全体合計 sum(rate({app=“nginx”} | ¥ pattern “<_> <_> <status_code> <method> <url>”[1m])) sum by() Labelごとの合計 sum(rate({app=“nginx”} | ¥ pattern “<_> <_> <status_code> <method> <url>”[1m])) by (url) topk() / bottomk() 値の大きい/小さい 順で上位n個までを 表示 topk(10, sum(rate({app=“nginx”} | ¥ pattern “<_> <_> <status_code> <method> <url>”[1m])) by (url)) • Metric queries | Grafana Loki documentation • Query examples | Grafana Loki documentation
  23. Lokiアンチパターン 1. Metric Queriesを長いTime Rangeで実行する  長いTime Range(e.g. 1週間)に対するMetric queriesは

    Querierに大きな負担がかかるため、避けるべき 特に継続的にクエリーが発生するダッシュボード/アラートでの設定は要注意  Best Practice  RulerのRecording Rulesやpromtailのmetricsステージを使って Prometheus形式のメトリクスを生成し、Prometheusに連携する  これによりLokiの保存期間ではなく、Prometheusの長期保存ツール (e.g. AMP)の保存期間分、ログに対するメトリクスを保存(確認)できる Grafana Loki top 5 query performance tips - YouTube
  24. Lokiアンチパターン 2. 広い範囲のLabel/Time Rangeでログ検索を行う  長いTime Range(e.g. 1週間)/広い範囲のLabelに対するログ検索は Querierのリソース逼迫によるQuerierの再起動、ひいては 全体的な読み込みパフォーマンス低下につながる恐れがある

     Best Practice  確認したいログの具体的なLabelを指定する  Time Rangeはできるだけ確認したい時間帯に絞る Grafana Loki top 5 query performance tips - YouTube app=“loki” app=“loki”, component=“compactor” 確認したいのがcompactorに関するログの場合、 {app=“loki”}ではなく、 {app=“loki”, component=“compactor”}のように Labelを具体的に指定する
  25. Lokiアンチパターン 3. カーディナリティの高い値をLabelに設定する  ラベルセットごとにindex/chunkが生成されるため、 ラベルセットが多い → index/chunkが多くなる・chunkサイズが小さくなる → 検索パフォーマンスの低下につながる

     chunk数がLimitを超えstream_limitエラーでログが破棄される恐れもある  例えば、HPAが設定されていて頻繁に作成/終了されるPodがあるとする  Best Practice  極力は固定/カーディナリティの低い値をLabelに設定する  filter operatorで検索できるものはLabelに設定せずfilter operatorを使う Best practices | Grafana Loki documentation {cluster=“unk”, namespace=“monitoring”} {cluster=“unk”, namespace=“monitoring”, pod_name=“abcd”} {cluster=“unk”, namespace=“monitoring”, pod_name=“efgh”} {cluster=“unk”, namespace=“monitoring”, pod_name=“ijkl”} Pod名をLabelに設定した場合は Podの数の分index/chunkが生成され、 1chunkのサイズも小さくなる。 Pod名をLabelに設定してない場合は 全体で1index/chunkになるので その分検索も早くなる
  26. Loki/promtailのオブザーバビリティ Component Metric Name Description promtail promtail_dropped_entries_total すべてのリトライが失敗しDropされたログ数 promtail promtail_sent_entries_total

    promtailがLokiに送信したログ数 distributor loki_discarded_samples_total Validation checkに引っ掛かり拒否されたログ数 distributor loki_request_duration_seconds 受信したHTTPリクエスト(status_codeを確認) distributor loki_distributor_bytes_received_total distributorが受信したログbytes distributor loki_distributor_lines_received_total distributorが受信したログ数 ingester loki_ingester_memory_streams ingesterメモリ内のstream数 ingester loki_ingester_memory_chunks ingesterメモリ内のchunk数 ingester cortex_ring_members{name="ingester", state="Unhealthy"} ring上のunhealthyなingester数 ※一定の数を超えるとLokiへの書き込みができなくなる - (k8s) kubelet_volume_stats_available_bytes Helmでデプロイし、ingesterごとにPV(EBS)を 払い出している場合、WALディスクの空き容量監視用 • promtail/Lokiが開示しているメトリクスを活用してLokiクラスターを監視 • 主要メトリクス Observability | Grafana Loki documentation
  27. まとめ • Lokiの特性(得意/不得意)を理解したうえで正しく使いましょう • すでにPrometheusでGrafanaを使っている場合は ログ管理ツールの有力な候補 • 学習コストは高いが、使いこなせると大量のログを 低コストで管理できる •

    試してみたい方はまずはスモールスタート Single Binaryモードがあり、すべてのコンポーネントを 1つのバイナリとして動かすこともできる ※Single Binaryモードでも約100GB/dayは捌ける(らしい) Deployment modes | Grafana Loki documentation