Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Grafana Lokiで始めるPodログ/k8s Events管理

nutslove
March 13, 2023

Grafana Lokiで始めるPodログ/k8s Events管理

2023/03/13 Kubernetes Novice Tokyo #23で発表に使った資料になります。

nutslove

March 13, 2023
Tweet

More Decks by nutslove

Other Decks in Technology

Transcript

  1. 自己紹介 Who • 李 俊起(イ ジュンギ) • KDDI情シス所属 • 一児のパパ

    What • 特権管理や監視基盤等、 共通機能(基盤)を提供/管理 • CKS取得が今年目標の1つ
  2. 本日話す内容 • Lokiとは • Lokiの特徴 • Lokiアーキテクチャ/コンポーネント • 処理の流れ(Write/Read) •

    Deployment modes • data lossを防ぐ仕組み • promtailについて • Grafana Agentを使ったk8s Eventsの連携 • Lokiでできること • まとめ • 付録
  3. Lokiとは • Grafana LabsがPrometheusにインスパイアされて 開発したOSSのログ管理ツール ➢ LabelやService Discoveryなど、Prometheusの仕組みを活用 • ログを受信して保存する/クエリーに対してデータを返す側

    ➢ ログ送信には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のDeployment modes • 以下3つのmodeから1つ選択 • Helmチャートが用意されている 1. Monolithic mode ➢

    Distributor、Ingester、Querier、Query-Frontend すべてのコンポーネントが1つのプロセスとして動く 2. Simple scalable deployment mode ➢ Distributor、Ingesterが1つのプロセス(Pod)として、 Querier、Query-Frontendが1つのプロセス(Pod)として動く 3. Microservices mode ➢ Distributor、Ingester、Querier、Query-Frontend すべてのコンポーネントが独立したプロセス(Pod)として動く Deployment modes | Grafana Loki documentation helm-charts/charts/loki-distributed at main · grafana/helm-charts (github.com) loki/production/helm/loki at main · grafana/loki (github.com) 低 高 拡張性/複雑性
  7. 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
  8. 処理の流れ(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により データのハッシュ値をもとに、 どの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
  9. 処理の流れ(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
  10. 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
  11. 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
  12. promtailのService Discovery • Prometheusと同じようにService Discoveryを使ってPodログを収集 • Scraping | Grafana Loki

    documentation • Configuration | Grafana Loki documentation 収集するログファイルが固定の場合 __path__に指定されているログを収集 Kubernetes APIを使ってターゲットを検知
  13. 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"
  14. Grafana Agentでk8s Eventsを連携 • Kubernetes EventsはPromtailでは検知できない • Grafana Agentの「eventhandler_config」を使えば Kubernetes

    EventsをLokiに連携できる • How to use Kubernetes events for effective alerting and monitoring | Grafana Labs • eventhandler_config | Grafana Agent documentation • logs_config | Grafana Agent documentation k8s eventsを検知 ログを収集しLokiに連携のための設定 設定方法はpromtailと同様
  15. Lokiでできること 1. ログ検索 ➢ ログ検索は必ずLabelから入る(選択する)ので 検索したいログに付与されているLabelを把握している必要がある 2. ログからメトリクス生成 ➢ LogQLはログからメトリクスを生成するMetric

    queriesにも対応している 3. ログに対するアラート設定 ➢ Rulerを使ってAlertManagerにアラートを連携する方式と Grafanaにて直接Lokiに対してアラートを設定する方式(推奨)がある ➢ アラートにはMetric queriesを使う必要がある ※特定文字列を含むログが何件以上というような 数値に対してアラートを設定する必要があるため Metric queries | Grafana Loki documentation
  16. promtailのpipeline • Lokiに連携する前にログの編集/除外等を行う • 複数のstageから構成されている ◼ replaceステージ ➢ 特定の文字列を置換 ケース

    設定例 P/S番のユーザアカウントを****に 変換(マスキング)する - replace: expression: "([SP][0-9]{6})" replace: "****"
  17. 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}¥]'
  18. 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:
  19. 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
  20. 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 必須 任意
  21. 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
  22. 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”
  23. 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
  24. 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を経由するか選択可能
  25. 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のリトライに関するコードの部分
  26. 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
  27. 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を具体的に指定する
  28. 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になるので その分検索も早くなる
  29. 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