本章ではGrafana Loki(以降、Loki)について紹介します。 本章を行うにはchapter_opentelemetryを行う必要あります。 下記の流れで説明します。
- Lokiの概要
- アーキテクチャについて
- Lokiのデプロイモード
- 実践
- Lokiの構築
- Grafanaを利用したログの検索
- アラートの設定
LokiはPrometheusを参考に作成されたログの集約システムです。 LokiをベースにしたLogging Stackの利用イメージは下記の図になります。 LokiはPull型ではなくPush型を採用しており、ログを取得してLokiに送付するAgentが必要になります。 Agentとしては、Promtailやfluentbit、OpenTelemetryなどがあげられます。 また、集約したログはGrafanaやLogCLIのようなツールで参照します。
Lokiの特徴として、ログに付与されたラベルに対してのみインデックスを作成することによる、ラベルを条件にしたログ参照の高速化があげられます。
下記の図は簡単なログの格納処理になります。
Lokiでは、ログのラベルのキーと値の組み合わせの数だけ、ログストリームと呼ばれるものが作成されます。
Lokiで受け取ったログは、ラベルのキーと値をもとにログストリームへ振り分けられ、一定数たまると圧縮され、Chunkとしてオブジェクトストレージに格納されます。
たとえば、Lokiがjobラベルの値としてAAAとBBBを認識している例を考えます(以下の図)。
この場合、{job="AAA"}と{job="BBB"}の2種類のログストリームが定義され、Lokiがjobラベルの値がAAAであるログを受け取ると、{job="AAA"}のログストリームにまとめられます。
前述した通り、Lokiではラベルに対しIndexを付与します。 読み取り時は、タイムスタンプやログのラベルの情報をもとにログが含まれるChunkの位置を特定し、読み込み処理を行います。 読み込んだChunkのデータに対して、ログの検索や集計等が行われます。
Warning
とぢらも説明の簡易化のために、かなり処理を省いています。 より詳しい処理はSelect the chunks by inverted indexを参照してください。
Lokiのアーキテクチャについて説明します。
Grafana Lokiは複数のマイクロサービスで構成されており、水平スケールが可能なシステムとして設計されています。
各サービスをマイクロサービスのように個別にデプロイすることも可能ですし、1つのコンテナイメージとしてデプロイすることも可能な設計となっています。
Loki自体はシングルバイナリとなっており、実行時に-targetオプションを指定して動作を切り替えます。
まずはGrafana Lokiのアーキテクチャを見てみましょう。 以下がGrafana Lokiの簡単なアーキテクチャになります。
この図には以下のコンポーネントとストレージが図示されており、それぞれについて次節以降で説明します。
- コンポーネント
- Distributor
- Ingester
- Querier
- Query Frontend
- Ruler
- Compactor
- Consul
- ストレージ
- オブジェクトストレージ
- Memcached
Note
上記の図で示したコンポーネントは一部です。 たとえば、Loki v3.0.0からBloom CompactorとBloom Gatewayというコンポーネントが追加されました。 これはクエリの速度向上を目的に実装されたBloomフィルターを実現するためのコンポーネントです。 ここでの説明は省きますが、この機能を利用することで、ログが格納されているChunkをより効果的に見つけることができます。 詳細はQuery Acceleration with Blooms (Experimental)を参照してください。
先ほどの図のコンポーネントについて説明します。
- Distributor
- Agentからのログの書き込みリクエストを受信し、検証を行い、Ingesterにリクエストを回送するコンポーネントです
- 同じリクエストを複数のIngesterに送付し、冗長化を設定することも可能です
- 他にもログの前処理や、Ingesterを守るためのRate Limitなどの設定も可能です
- ステートレスなサービスです
- Agentからのログの書き込みリクエストを受信し、検証を行い、Ingesterにリクエストを回送するコンポーネントです
- Ingester
- Distributorからログの書き込みリクエストを受け取ると、オブジェクトストレージに格納します
- オブジェクトストレージへはログを圧縮し送付します
- 送付前にログを一時的に貯めておくことで、オブジェクトストレージの操作を削減します
- ログの読み取りリクエストを受け取ると、インメモリーに一時的に貯められたログデータを返却します
- Distributorからログの書き込みリクエストを受け取ると、オブジェクトストレージに格納します
- Querier
- Log Query Language(LogQL)の実行を担当するコンポーネントです
- Ingesterとオブジェクトストレージからログを取得します
- ログの冗長化設定が入っているとログの重複が発生する場合がありますが、その際には重複したログを削除します
- オプションでQuerier-frontend/Query-Schedulerと連携が可能です
- Query-frontend
- ログのクエリ処理を高速化するためのオプションのコンポーネントです
- Querierの前段に配置し、キューやキャッシュの機構が実装されています
- ログのクエリ処理を高速化するためのオプションのコンポーネントです
- Ruler
- rule configurationで構成されたRuleや/またはAlertを評価し管理するコンポーネント
- rule configurationはオブジェクトストレージに格納され、Ruler APIを介すか、直接アップロードすることで管理します
- Compactor
- オブジェクトストレージに格納された複数のIndexファイルをテナントや日ごとのファイルに圧縮します
- 定期的にオブジェクトストレージからファイルをダウンロードし、統合し、アップロードします
- また、ログの保持と削除も担当します
- オブジェクトストレージに格納された複数のIndexファイルをテナントや日ごとのファイルに圧縮します
- Consul
- 一部のコンポーネントで利用される情報を格納するためのKey-Value Storeです
- どのような情報がどのコンポーネントで利用されるかはHash Ringをご参照ください
- 図ではConsulとなっていますが、ほかにもetcdやInmemoryなどがサポートされています
- 一部のコンポーネントで利用される情報を格納するためのKey-Value Storeです
Note
上記のコンポーネントは一部になります。 Lokiがサポートしているコンポーネントの一覧は下記のコマンドで確認できます。 また、後述するDeploy Modeのグルーピングに関しても、こちらのコマンドで確認できます。
loki -config.file=/etc/loki/config/config.yaml -list-targets次に、先ほどの図のストレージについて説明します。
- オブジェクトストレージ
- IndexとChunkのデータを格納するために利用します
- オブジェクトストレージとして、AWS S3やGoogle Cloud Storageなどが利用できます
- 図ではオブジェクトストレージとなっていますが、ファイルシステムも利用可能です
- 推奨はされていません。詳細はManage storageを参照してください
- Memcached
- 各コンポーネントで利用されるキャッシュです
- 図ではMemcachedとなっていますが、他にもRedisなどが利用できます
- 詳細はconfigure: cache configを参照してください
前述したように、Lokiは多くのマイクロサービスで構成される分散システムであり、以下の3つのデプロイモードがあります。 どれもメリット・デメリットがあり、運用者の要件に応じて使い分けることが大切です。
- Microservice Mode
- Simple Scalable Deployment
- Monolithic Mode
このモードでは、Lokiの各コンポーネントを個々のマイクロサービスとして実行します。 個々の要件に合わせたきめ細かな設定が可能になります。 一方で、設定やメンテナンスの複雑度が一番高いモードになっており、非常に大規模なLokiクラスターや、スケーリングやオペレーションを正確に制御する必要がある場合にのみ推奨されるモードです。
LokiのHelm Chartでデフォルトの設定です。 Simple Scalable Deploymentを略してSSDと呼ばれることもあります。 1日あたり数TBのログまでスケールアップが可能であり、これを大幅に超える場合にはMicroservice Modeを検討する必要があります。 SSDでは各コンポーネントをWrite Target/Read Target/Backend Targetに分類し運用します。
- Write Target
- Kubernetesリソース: StatefulSet
- 含まれるコンポーネント: Distributor、Ingester
- Read Target
- Kubernetesリソース: Deployment
- 含まれるコンポーネント: Query front end、Queriers
- Backend Target
- Kubernetesリソース: StatefulSet
- 含まれるコンポーネント: Compactor
このモードはもっとも単純な動作モードであり、すべてのコンポーネントを単一バイナリ(またはコンテナイメージ)として単一のプロセス内で実行します。 1日あたり約20GBまでの少量のRead/Writeが発生する場合や、動作確認のために起動する場合などで便利です。 共有オブジェクトストレージを使用することで、水平スケールも可能です。
まずはLokiをKubernetesクラスターに展開してみましょう。 今回はMonolithic modeでデプロイします。
helmfile sync -f helm/helmfile.yamlWarning
今回、オブジェクトストレージとしてminIOを構築し利用する設定が入っています。 これはローカルで確認するために選定しており、推奨される設定ではないことに注意してください。 詳細はsupported-chunks-stores-not-typically-recommended-for-production-useを確認してください。
次にLokiを利用するGrafanaのDatasourceの設定をしましょう。
http://grafana.example.com/connections/datasourcesにアクセスしてください。
「Connections」-> 「Data sources」-> 「Add new data source」をクリックし、Lokiを選択します。
下記の設定値をいれて、「Save & test」をクリックしてください。
URL:http://loki-gateway.monitoring.svc.cluster.localHeader:X-Scope-OrgIDvalue:tenant1
URLはGrafanaがアクセスするLokiのURLです。
Lokiはマルチテナントにも対応しており、Headerとvalueで使い分けられます。
この値は、Lokiにログを送付するAgentに設定してあります。
今回の場合、manifest/log-collector.yamlのexporters.loki.headersに設定している値と同じ値をHeaderとvalueとして設定します。
設定が完了すると、http://grafana.example.com/connections/datasourcesにLokiが表示されます。
また、Grafana Exploreの画面でLokiが選択できるようになります。
Tip
今回はDatasouceの設定をGUIから行いましたが、本来であれば設定はコードに起こして管理した方が良いでしょう。
たとえば、Helm Chartのkube-prometheus-stackでデプロイしたGrafanaのDatasourceに同様の設定をするのであれば、
下記のような設定をvalue.yamlに記載することで、コードとして管理ができます。
additionalDataSources:
- name: loki
access: proxy
basicAuth: false
editable: true
jsonData:
httpHeaderName1: "X-Scope-OrgID"
secureJsonData:
httpHeaderValue1: "tenant1"
type: loki
url: http://loki-gateway.monitoring.svc.cluster.localこの節では、OpenTelemetry Collectorを利用して各Nodeのログを収集し、Lokiに格納する設定を行います。 また、格納したログをGrafana経由で確認します。
Note
Lokiは、バージョン3.0(2024年)から、OTLP(OpenTelemetry Protocol)のエンドポイントとしてネイティブサポートされました。 OpenTelemetryでは、データそのものだけでなく、key:valueの属性(attribute)も付与して送付でき、バックエンドストレージでの検索のしやすさに貢献します。
OTLPでログを受信する際、Lokiはデフォルトで特定のラベル(例:service_name、log_file_nameなど)をインデックスとして保持します。
しかし、デフォルトのラベル以外の属性(例:exporter)をインデックスとして使用したい場合は、Lokiの設定で明示的に指定する必要があります。
追加のラベルをインデックス化するために、本ハンズオンでは以下を設定しています。
# `loki-values.yaml`
loki:
limits_config:
allow_structured_metadata: true
# OTLP設定: resource attributesをラベルとして使用
otlp_config:
resource_attributes:
attributes_config:
# exporter属性をラベルとしてインデックス化
# これにより、Grafanaのラベルフィルタでexporter=OTLPで検索可能になる
- action: index_label
attributes:
- exporterこの設定により、exporter属性がLokiのラベルとしてインデックス化され、Grafanaのラベルフィルタでexporter=OTLPのようなクエリが可能になります。
まずは、OpenTelemetry CollectorをアプライしてLokiにログを送付します。
kubectl apply -f manifest/log-collector.yaml次にログを発生させます。
サンプルアプリはhttp://app.example.com/にアクセスしてみてください。
色がついたパネルが発生するたびに、バックエンドのアプリケーションにログが出力されます。
Note
Podのログは下記のコマンドで確認できます。 下記コマンドをコンソールで実行しておくと、実際にブラウザでアクセスした際にログが表示されていることを確認できます。
kubectl logs -n handson -l app=handson -fそれでは実際にGrafanaからログを確認してみましょう。
http://grafana.example.com/exploreにアクセスします。
Label filtersにexporterとOTLPを設定して検索ボタンを押します。
すると、exporterラベルの値がOTLPログの数や、内容を確認できます。
Note
Lokiではログの検索や集約にLogQLを利用します。
さきほど、exporterラベルの値がOTLPを指定すると、{exporter="OTLP"} |= ""という文字列が表示されていると思います。
これがLogQLになります。
他にもさまざまなことがLogQLでできます。詳細はLogQL: Log query languageをご確認ください。
Grafana Exploreの画面からよく使われるクエリのパターンを確認することもできます。 「Kick start your query」をクリックしてみてください。 下記の2種類のグループが表示されると思います。
- Log Query Starters:ログの検索や集計等に利用できるパターン
- Metrics Query starters:ログからメトリクスを生成する際に利用できるパターン
Log Query Startersには、特定の文字列でフィルターしたログをlogfmtで解析する例や、ラベルやログの内容の書き換えなどの例が確認できます。 また、Metrics Query startersには、ログをもとにメトリクスを生成するパターンが紹介されています。 たとえば、特定の文字列を持つログの数やラベルの数などのメトリクスに変換して視覚化できます。 どのパターンもそのまま適用することはできませんが、クエリを書く際のヒントとして利用できます。
試しに、Metric Query startersのTotal requests per label of streamsをクリックしてみましょう。
選択肢が表示される場合、「Apply to query」を選択します。
すると、LogQLが自動で構築されるので、追加で下記の設定をしてください。
Line contains:blueRange:5m
この場合、sum(count_over_time({exporter="OTLP"} |= "blue" [5m]))がLogQLになります。
このLogQLの意味は下記となり、直近5分間でexporterラベルの値がOTLPであるログのうちblueを含むログの数を表示します。
{exporter="OTLP"}:exporterラベルの値がOTLPであるログを選択|= "blue": blueという文字列が含まれるログを選択count_over_time(...)[5m]: 直近5分間のログの数をカウントsum(): 合計する
http://grafana.example.com/alerting/listにアクセスしてアラートを設定してみましょう!
Warning
Grafanaの章で設定したContact PointとNotification Policyを利用します。
New alert ruleをクリックして、下記の項目を入力します。
入力が終わるとSave rule and exitをクリックして適用します。
Enter alert rule nameName...SampleGrafanaAlertLoki
Define query and alert conditionDatastore...LokiLabel filter... 以下を順に設定exporter=OTLPに設定Line contains...blueに設定Operationsをクリックし、以下を設定Range Functions > Count over timeをクリックし、Rangeを5mに設定
Operationsをクリックし、以下を設定 ※さらに条件を追加しますAggregations > Sumを設定
Alert condition- WHEN
LastOF QUERYIS ABOVE100- 最新値が 100より大きい場合、アラートを発報します
- WHEN
- 右上部の
Run queriesをクリックすると、実際のクエリの結果をグラフ化して確認できます - グラフ下部に、「Firing」とフラグのあるレコードが表示され、実際に発報されるアラートをシミュレートできます。

Note
試しにSumの集約条件を非表示にし、 Preview alert rule conditionをクリックしてみます。とんでもない量のアラートになるとわかります!
Add folder and labels
Folder- New folder で
loki-alertを作成 Folder...loki-alertに設定
- New folder で
Labels- Add labels で
alert-route=slackを設定
- Add labels で
Set evaluation behavior
Evaluation group...New evaluation groupをクリックし、Evaluation group nameをsample-grafana-alert-loki,Evaluation Intervalを1mに設定Pending period...0sに設定(即時発報)Keep firing for...0sに設定(継続発報しない)
Configure notifications
Contact point...sample-grafana-alertingを設定- chapter_grafanaで作成したContact Pointを使用する
Configure notification message
Summary,Description... 任意の文字列を追加- 他の参加者とアラートが被った場合でも、自分が設定したアラートだと識別できるように設定
このアラートは、sum(count_over_time({exporter="OTLP"} |= "blue" [5m]))が100を超えた場合にアラートを発報するというルールになっています。
アラートを設定したらhttp://app.example.com/にアクセスして、ログを増やしてみましょう。
Tip
アラートの発表状況はhttp://grafana.example.com/alerting/listから確認できます。
Datasoucesにlokiを指定すると、先ほど設定したアラートの状況が確認できます。
通常はStateがNormalとなっており、Firingとなればアラートが発報されている状態です。
Slackにアラートが連携されることを確認したら、アラートの設定を削除しておきましょう
http://grafana.example.com/alerting/list?search=datasource:lokiにアクセスし、Moreから「Delete」を選択肢削除しておきます。










