はじめに
こんにちは、サイオステクノロジーの小沼 俊治です。
「理屈はいいから、まずは実際にオブザーバビリティー(可観測性)というものを動かして体験してみたい」。 本記事は、そんな方々に向けて、システム運用に不可欠なオブザーバビリティーの基礎を手を動かしながら学習できる、実践的な入門ガイドとして用意しました。
Grafana OSS LGTM スタックを使って、メトリック・ログ・トレース・プロファイルを包括的に可視化する環境を無料で体験できるハンズオンを提供します。
本ハンズオンでは、以下のオープンソース・プロダクトのみで構成された環境でコンテナを使って一括で立ち上げ、Web アプリケーションから実際にデータを収集・可視化する流れを体験していただきます。
- Grafana: 可視化ダッシュボード
- Mimir / Loki / Tempo: メトリクス、ログ、トレースのバックエンド
- Pyroscope / Faro / Alloy: プロファイリングやフロントエンド監視、データ収集
- OpenTelemetry Collector:テレメトリーデータの中継・処理パイプライン
なお、本記事は「どのような観測ができるのか」を体験いただくことを主目的としています。そのため、Grafana OSS LGTM Stack の環境構築手順そのものの詳細解説は割愛しており、「まずは動かしてみたい」という方に最適です。もし環境構築の裏側に興味を持っていただいた場合は、ぜひ GitHub リポジトリの設定ファイルを解析してみてください。
構成概要
本ハンズオンの動作検証を行った環境(バージョン)は以下の通りです。 記事では Windows 11 上の WSL 2 (Ubuntu) で起動する流れで記載していますが、Docker が動作する環境であれば他の OS でも実施可能です。
- Windows 11 Professional
- WSL 2.5.9.0
- Ubuntu 24.04.3 LTS
- Docker Engine 28.5.1
- Grafana 12.3.2
Windows (WSL) 以外のOSをご利用の方も、条件が満たしていれば以下の手順からハンズオンを進められます。
- Ubuntu (Linux) 環境の方: WSL の構築は不要ですので、「Docker Engine 環境の構築」 章から開始してください。
- macOS 環境の方: Docker Desktop for Mac などでコンテナ実行環境が準備済みであれば、「GitHub からハンズオン用のリポジトリ取得」 章から開始してください。
ハンズオンを構成する環境は以下の通りです。


- オブザーバビリティーを構成する Grafana LGTM Stack と OpenTelemetry Collector、および観測対象の Web アプリケーションはそれぞれコンテナで構築します。
- Web アプリケーションをユーザーの立場として操作し、Grafana を運用担当の立場として操作してロールプレイングしながら進めます。
- Web アプリケーションと Grafana LGTM Stack 間で、メトリクス、ログ、およびトレースは OpenTelemetry Collector を介し、プロファイルと Real-time User Monitoring (RUM) は Alloy を介して連携します。
- Web アプリケーションから OpenTelemetry Collector を介して連携するデータと送出元は以下の通りです。
- メトリクス:Micrometer (Prometheus 形式)
- メトリクス:Micrometer (OpenTelemetry 形式)
- メトリクス:Node Exporter
- ログ:OpenTelemetry
- トレース:OpenTelemetry
- Web アプリケーションから Alloy を介して連携するデータと送出元は以下の通りです。
- プロファイル:Pyroscope Java Agent
- RUM:Faro Web SDK from CDN
- 汎用的な OpenTelemetry Collector でも Grafana LGTM Stack に連携できることを表したいので、できる限り OpenTelemetry Collector を介した連携で構成しています。
- Web アプリケーションから OpenTelemetry Collector を介して連携するデータと送出元は以下の通りです。
- オブザーバビリティーの Grafana LGTM Stack は、Grafana、Mimir、Loki、Tempo、Pyroscope、Faro、および Alloy で構成します。
- ユーザーインターフェースは Grafana が担います。
- Mimir、Loki、Tempo、および Pyroscope は、それぞれの前段に Nginx で構築するロードバランサーを配置します。
- Mimir、Loki、および Tempo は、それぞれの後段に MinIO で構築する永続ストレージを構成します。
- Tempo の構成における主な考慮事項は以下の通りです。
- OpenTelemetry Collector から送信されてくるトレースを受け取るポート番号(4318)と、Grafana で表示のために取得するポート番号(80)が異なるため、それぞれのロードバランサーを構成します。
- Grafana でサービスグラフを表示するために、Tempo のメトリクスジェネレーターでトレースからメトリクスを生成し Mimir へ送信します。
- メトリクスジェネレーターを始めとする Tempo の内部構造のアーキテクチャーについては「Tempo architecture | Grafana Tempo documentation 」を参照してください。
- Alloy はプロファイルと RUM の中継のみを担っています。本ハンズオンでは、汎用的な OpenTelemetry Collector でも Grafana LGTM Stack に連携できることを表したいので、あえてプロファイルと RUM のみの扱いに限定しています。
- 観測対象の Web アプリケーションは、WebUI、WebAPI、および MySQL で構成します。
- WebUI と WebAPI の構成における主な考慮事項は以下の通りです。
- Spring Boot で実装したアプリケーションで構成します。
- メトリクス、ログ、トレース、およびプロファイルは、自動計装で収集しているので、ビジネスロジックに手動計装のロジックは一切実装していません。
- WebUI と WebAPI の構成における主な考慮事項は以下の通りです。
- 異常検知のアラートは、アラートメールの通知先もハンズオン環境内で完結するように、SMTP サーバーと受信したメールが閲覧できる Web メールを搭載した MailDev コンテナを利用します。
環境構築や各種設定に使用するそれぞれのファイルは、以下の GitHub リポジトリで公開しています。
- Toshiharu-Konuma-sti/hands-on-grafana :本ハンズオンで使うメインのリポジトリです。
$ tree ~/handson/hands-on-grafana/
hands-on-grafana/
|-- container/ …… 「環境構築」章でコンテナの作成で使う素材
| |-- docker-compose.yml
| :
|
`-- webapp/ …… 観測対象となる Web アプリケーションを構成するリポジトリを参照するサブモジュール- Toshiharu-Konuma-sti/hands-on-rollingdice-webapp :観測対象となる Web アプリケーションのリポジトリで、ハンズオンのリポジトリからサブモジュールで参照されているので、意図的に Clone する必要はありません。
$ tree ~/handson/hands-on-webapp-rolling-dice/
hands-on-webapp-rolling-dice/
:
|-- mysql …… Web 三層アーキテクチャのデータ層に該当するデータベースを構成する素材
| |-- config
| | `-- my.cnf
| `-- init
| `-- init.sql
|
|-- webapi …… Web 三層アーキテクチャのプレゼンテーション層に該当するフロントエンドサーバーを構成する素材
| |-- build.gradle
| :
|
`-- webui …… Web 三層アーキテクチャのアプリケーション層に該当する API サーバーを構成する素材
|-- build.gradle
:環境構築
WSL 環境の構築
Windows PC の場合には、 以下手順を参考に WSL と Linux ディストリビューション(Ubuntu)環境を用意します。
Docker Engine 環境の構築
コンテナ環境を使うため、以下手順を参考に Ubuntu へ Docker Engine 環境を用意します。
GitHub からハンズオン用のリポジトリ取得
ハンズオンを進めるための環境構築用の設定ファイルやスクリプトを含んだリポジトリを GitHub からダウンロードして取得します。
本章ではターミナルを用いてハンズオンのフォルダ領域を作成して作業を実施します。
$ mkdir -p ~/handson/
$ cd ~/handson/「$ git clone」コマンドで本ハンズオン用のリポジトリを取得します。
$ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-grafana.git
$ cd hands-on-grafana/コンテナ構築スクリプトの実行
本章ではターミナルを用いて以下のディレクトリで作業を実施します。
$ cd ~/handson/hands-on-grafana/container/コンテナ構築用に用意してあるスクリプトを実行して、オブザーバビリティー環境の各種コンテナを構築します。初回はイメージのダウンロードと作成に時間がかかりますので、コンソールの表示を見守りつつ、処理が完了するまでしばらくお待ちください。
$ ./CREATE_CONTAINER.shコンテナが構築されてから info オプションを付けてスクリプトを実行すると、ハンズオンに必要なアプリケーションの URL などを表示することができます。
$ ./CREATE_CONTAINER.sh info
/************************************************************
* Information:
* - Access to Grafana Web ui tools with the URL below.
* - Grafana: http://localhost:3000
* - dashboard(Prometheus): https://grafana.com/grafana/dashboards/4701-jvm-micrometer/
* - dashboard(OpenTelemetry): https://grafana.com/grafana/dashboards/20352-opentelemetry-jvm-micrometer/
* - dashboard(Node Exporter): https://grafana.com/grafana/dashboards/1860-node-exporter-full/
:Grafana コンテナが稼働するのでブラウザでアクセスします。

Web アプリケーションのコンテナが稼働するのでブラウザでアクセスします。

Web アプリケーションが起動したかどうかは、以下のコマンドでアプリケーションログを確認し、「Spring」のロゴが出力されていたら Web アプリケーションが利用できる状態の目安になります。「docker logs」に続くコマンド末尾は「webapp-webui」「webapp-webapi」のそれぞれのコンテナを指定して確認します。
$ docker logs webapp-webui
> Task :bootRun
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.3)
2025-04-17T14:36:32.114Z INFO 412 --- [webui-app-yml] [ restartedMain] ...
2025-04-17T14:36:34.926Z INFO 412 --- [webui-app-yml] [ restartedMain] ,,,
2025-04-17T14:36:35.048Z INFO 412 --- [webui-app-yml] [ restartedMain] ...
2025-04-17T14:36:35.895Z INFO 412 --- [webui-app-yml] [ restartedMain] ...
2025-04-17T14:36:37.925Z INFO 412 --- [webui-app-yml] [ restartedMain] ...
2025-04-17T14:36:41.572Z INFO 412 --- [webui-app-yml] [nio-8081-exec-1] ...なお、コンテナ構築スクリプトで実行する内容は以下を参照してください。
Web アプリケーションの説明
オブザーバビリティーの実演に移る前に、観測対象となる Web アプリケーションの画面構成と機能を説明します。

Web ページからのリクエストごとにサイコロを振り、結果と履歴を表示します。 「Roll a dice」メニューには、単純にサイコロを振る以外にも、トラブルシューティングの練習用に特殊な挙動をするリンクも用意しています。
- Roll normally: 特別な処理を行わず、通常通りサイコロを振ります。
- Sleep n sec at API: サイコロを振る前に、処理時間を意図的に延ばすために指定秒間スリープします。
- Loop n sec at API: サイコロを振る前に、システムへ負荷を掛けるために指定秒間ループします。
- Trigger 5xx error at API: サーバー側で意図的に例外を発生させ、HTTP ステータス 500 エラーを起こし、サイコロを振るのを中断します。
- Force specific value: サイコロの出目を、指定した値で強制的に固定します。
- RUM Simulation at Browser: RUM 関連の動作確認が行えます。
- Trigger JS error at Browser: サーバーへのリクエストは行わず、ブラウザ(フロントエンド)側で JavaScript の例外を意図的に発生させます。
- Fetch API: Normal: 画面遷移を行わず、JavaScript (Fetch API) から非同期通信で サイコロを振る WebAPI を実行します。
- Fetch API: Sleep 2 sec: 2秒間のスリープも指示した非同期通信を実行します。
観測対象となる Web アプリケーションのシーケンスとコンポーネント仕様を説明します。

Web アプリケーションは、WebUI、WebAPI、および Database の3つのコンポーネントで構成します。WebUI と WebAPI は Spring Boot のフレームワークで実装し、Database は MySQL を利用します。
各コンポーネントの仕様:
- WebUI:WebUI から WebAPI へ以下2つの API をコールして画面表示を行います。
- WebAPI の「/api/v1/dices」を POST でコールしてサイコロを振ります。
- WebAPI の「/api/v1/dices」を GET でコールしてサイコロ出目の一覧を取得します。
- WebAPI:以下2つの API を持ちます。
- POST: /api/v1/dices
- サイコロを振り、出目を Database に保存します。WebAPI を呼び出す際に、パラメータを付与することで観測向けの機能も実行します。
- sleep パラメータを付与すると、指定秒間スリープを行います。
- loop パラメータを付与すると、指定秒間ループし、ループ毎にファイルを開いて1行読むディスクアクセスの処理を行います。
- error パラメータに true を付与すると、サイコロを振らずに処理を中断し、HTTP ステータスコード 500 エラーで API をレスポンスします。
- GET: /api/v1/dices
- Database からサイコロ出目の全履歴を、振った日時の降順で取得します。
- サイコロを振り、出目を Database に保存します。WebAPI を呼び出す際に、パラメータを付与することで観測向けの機能も実行します。
- POST: /api/v1/dices
- Database
- サイコロの出目、日時、およびシーケンス番号のフィールドで構成するテーブルを保持します。
オブザーバビリティーの実演
メトリクス閲覧 (Mimir)
観測対象の Web アプリケーション(Micrometer 実装)、および Node Exporter から収集されたメトリクスが、Mimir へと送信されています。まずは Grafana のダッシュボードを使って、これらのメトリクスを可視化してみましょう。
Grafana 公式サイトでは、データソースや用途に合わせた数多くのダッシュボードテンプレートが公開されています。今回はその中から、Micrometer (Prometheus 形式) のメトリクスを表示するのに適した「JVM (Micrometer) | Grafana Labs 」を利用して、メトリクスを眺められるようにしてみます。
左ペインのメニューから「Dashboard」を選択して Dashboards 画面を表示し、画面右側の「New」ボタンをクリックすると表示されるメニューから「Import」を選びます。

画面中央のテキストボックスにインポートする「JVM (Micrometer)」ダッシュボードテンプレートの URL を入力し「Load」ボタンをクリックします。
- ダッシュボードテンプレートの URL:https://grafana.com/grafana/dashboards/4701-jvm-micrometer/

インポートするダッシュボードの設定画面が表示(ダッシュボードにより設定内容は異なる)されるので、本テンプレートではデータソースとなる Prometheus を選択して「Import」ボタンをクリックします。
- Prometheus 項目:「Mimir for Metrics」

ダッシュボードが表示されました。これで Web アプリケーションから送信されたメトリクス情報を確認できます。 なお、Grafana や Web アプリケーションを起動した直後の場合、デフォルトの表示期間(Last 24 hours)ではデータ幅が小さすぎてグラフが表示されないことがあります。その場合は、画面右上の時間設定を「Last 15 minutes」などに変更し、表示期間を狭めてみてください。

本ハンズオンの Grafana でメトリクスを表示できるダッシュボードテンプレートと、設定画面で設定する値を紹介します。なお、公式サイトには紹介するテンプレート以外にも様々なテンプレートが紹介されているので、検索してご自分に合ったテンプレートを探してみてください。
- JVM (Micrometer) | Grafana Labs
- Micrometer (Prometheus 形式) のメトリクスを表示します。
- Prometheus 項目:「Mimir for Metrics」
- Micrometer (Prometheus 形式) のメトリクスを表示します。
- OpenTelemetry JVM Micrometer | Grafana Labs
- Micrometer (OpenTelemetry 形式) のメトリクスを表示します。
- Mimir 項目:「Mimir for Metrics」
- Loki 項目:「Loki」
- Micrometer (OpenTelemetry 形式) のメトリクスを表示します。
- Node Exporter Full | Grafana Labs
- Node Exporter のメトリクスを表示します。
- Mimir 項目:「Mimir for Metrics」
- Node Exporter のメトリクスを表示します。
ログ閲覧 (Loki)
Web アプリケーションが出力するログを Loki で収集・集約しています。これにより、各サーバーやコンテナへ個別にログインすることなく、Grafana の画面上だけでログの検索・閲覧が完結します。
それでは、実際に Web アプリケーションを操作し、その挙動に合わせてリアルタイムにログが出力される様子を確認してみましょう。
左ペインのメニュー「Explore」を選択して Explore 画面を表示し、データソースのドロップダウンから「Loki」を選び「Label browser」ボタンをクリックします。

Label browser 画面にて、検索対象のラベルとして「service_name」、値として「webapp-webui」の順番で選択してから「Show logs」ボタンをクリックすることで、webapp-webui コンテナのアプリケーションログを表示できます。

- 値として「webapp-webapi」を選択すれば、webapp-webapi コンテナのアプリケーションログ表示を指定できます。
青色「Run query」ボタン右端の下矢印をクリックすると表示するリロード間隔一覧から「5s」を選択することで、ログ表示が5秒間隔で自動的にリロードされるため、リアルタイムでログを確認することができます。

ログがリアルタイムに表示することを確認したいので、ユーザーの立場で Web アプリケーションをアクティブにしてサイコロを振ります。

再び運用担当の立場で Grafana に戻るとサイコロを振ったログが表示されていて、ログを眺めてみると先ほど Web アプリケーションで表示されていたサイコロの出目と同じ値を示すメッセージが存在することも確認できるはずです。

ログから一つメッセージを選んでクリックすると展開表示され詳細情報が確認できます。

「+ Operations」を使うことでログの表示を絞り込むこともできます。

よく使う検索指定を例示します。
- 「+ Operations > Label filters > Label filter expression」:ラベル、演算子、値の組み合わせで検索します。(例:「detected_level = error」は、エラーレベルで絞り込むことを意味します)
- 「+ Operations > Line filters > Line contains」:大文字と小文字を区別した完全一致で文字列検索します。
- 「+ Operations > Line filters > Line contains case insensitive」:大文字と小文字を区別せず文字列検索します。
ログからトレース閲覧 (Loki to Tempo)
アプリケーションログの確認ができたら、次はログとトレースの連携機能を体験してみましょう。
ログ一覧から任意の一行を選択し、そこから紐付いているトレース情報へジャンプします。 トレースを閲覧することで、ユーザーのリクエストからレスポンスに至るまでの一連の処理フローや、各処理ごとの所要時間をウォーターフォール形式で視覚的に把握できます。「それぞれの処理で、どのくらい時間がかかっているか」が一目瞭然になる様子を確認してください。
ログ表示の中から気になるログを選びます。

選んだログをクリックして詳細情報を表示します。

詳細情報を下にスクロールすると「Links」のセクションに(閉じている場合はクリックして展開すると)「Tempo」への連携ボタンがあるので、クリックすることで該当ログのトレースが画面をスプリットして右半分に表示されます。

元から表示されている画面左半分のログ表示側で、右上部にある三点リーダーをクリックすると表示する「Close」メニューをクリックするとトレースが全面表示になります。

Web アプリケーションの1リクエストで、WebUI から WebAPI、そして WebAPI からデータベースへ連携して実行される一連の処理フローと、各フローで掛かった処理時間を確認することができます。

表示したトレースとアプリケーション仕様で説明したシーケンス図を比較すると、実際に実行された処理フローがシーケンス仕様と一致していることが確認できます。

別のリクエスト・レスポンス事例として、Web アプリケーション初回起動時の1回目にサイコロを振った際のトレースと、2回目以降のトレースを比べてみます。プログラム初回起動時には、オブジェクトの生成や外部リソースの接続準備などで処理のオーバーヘッドが高く処理時間が掛かると一般的に言われていますが、実際にトレースを比べてみるとその事実が確認することができます。

再度トレース機能の説明に戻りますが、
いずれかのトレースをクリックして詳細情報を展開すると「Log for this span」ボタンが表示され、クリックするとトレースからログへの表示も可能です。

また、画面上部にある「Service Graph」タブをクリックすることで「Node Graph」が表示され、各コンポーネント間で生じる通信の関係性が確認できます。
本ハンズオンでは user が webapp-webui だけではなく、webapp-webui と webapp-webapi の両方にもアクセスが生じているのは、OpenTelemetry Collector が Prometheusプロトコルで両方のコンポーネントへ定期的にメトリクスを取得するアクセスが発生しており、これらが user からのアクセスとして認識されているためです。

メトリクスからトレース閲覧 (Mimir to Tempo)
続いて、メトリクス(数値の変動)からトレース(詳細な処理フロー)への連携を確認します。
Web アプリケーションから送信される Micrometer(Prometheus 形式)のメトリクス、特に HTTP リクエストのパフォーマンスを示す http_server_requests_seconds_bucket などには、Exemplar と呼ばれる機能によって Trace ID が紐付けられています。
Grafana のグラフパネル上に表示される Exemplar(点やひし形のマーク)をクリックすることで、その時点の具体的なリクエストのトレースへ直接ジャンプすることができます。グラフで「遅い」と感じた箇所の裏側を即座に特定できる便利さを体験してみましょう。
Explore でデータソースに「Mimir for Metrics」を選択し、「Code」入力モードに切り替えてから下に掲載する Prom QL クエリーを入力します。クエリーを入力したら、「Exemplar」スイッチをオンにして「Run Query」ボタンをクリックすると、Exemplar が有効となったメトリクスのグラフが表示されます。

入力する Prom QL クエリーは以下の通りです。
histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[$__rate_interval])) by (le))今度はユーザーの立場で Web アプリケーションをアクティブにします。Grafana でメトリクスのグラフ変化が分かりやすいようにするため、Web アプリケーションの「Roll a dice」メニューで、「2. Sleep 05 sec at API」から「4. Sleep 30 sec at API」までを上から順番に、一つクリックしたら少し間隔を置いてから次をクリックしてを繰り返してサイコロを振ってみます。

続いて運用担当の立場で Grafana に戻ります。メトリクスのグラフに小中大の3つの山が現れています。Exemplar を有効にしているため、それぞれの山頂の左横には HTTP リクエストを示す点が存在しています。

気になる HTTP リクエストに対してマウスオーバーすると詳細情報が掲載されたウィンドウが現れ、Tempo へ連携できる「Query with Tempo」ボタンも表示されています。

ウィンドウ内の「Query with Tempo」ボタンをクリックすると、詳細表示していたリクエストのトレースを画面右半分に表示することができ、「ログからトレース閲覧 (Loki to Tempo)」章で説明したようにトレースで分析を行うことも可能です。

プロファイルで解析 (Pyroscope)
メトリクスやトレースで「遅い処理」を見つけたとしても、具体的なコードのどこに原因があるかまでは分からないことがあります。そこで Pyroscope を用いたプロファイリングを行います。
ここでは、CPU 使用率やメモリ割り当て状況を可視化する「フレームグラフ (Flame Graph)」を利用して、アプリケーションの内部状態を解析します。リソースを過剰に消費している関数やメソッドをピンポイントで特定する手順を体験してみましょう。
Explore にてデータソースに「Pyroscope」を選択し、直下のドロップダウンからリソースとして「process_cpu > cpu」の順番で CPU の利用時間を選択します。ドロップダウン横のテキストボックスに「{service_name="webapp-webapi"}」の条件式を入力して「Run Query」ボタンをクリックすると WebAPI を対象としたプロファイルが表示されます。

- 入力する条件式は以下の通りです。
- WebUI を対象とする場合には「
{service_name="webapp-webui"}」を入力します。 - WebAPI を対象とする場合には「
{service_name="webapp-webapi"}」を入力します。
- WebUI を対象とする場合には「
「Run Query」ボタン右わきをクリックし一覧から「5s」を選択すると、5秒間隔でリロードされるのでリアルタイムでプロファイルを確認できます。

プロファイル表示の左上にあるテキストボックスで絞り込みを行うことができます。試しに「java」で入力してみると、Java 言語のクラスやメソッドがリソースを利用した値が表示されます。お馴染みな Java 標準クラス名やメソッド名があれば、それらがリソースを費やしていると言うことを表します。

今度は「jp/sios」で絞り込んでみると、ハンズオン用に実装した Web アプリケーションのビジネスロジックがリソースに費やした値を表示することができます。値を確認して他の値より抜きんでて大きいシンボルがある場合には、ハードウェアリソースを占有して利用している可能性があるため、ビジネスロジックの実装を見直して頂くことも検討します。キャプチャーした実績では大きな値では無いので、問題ないと言ってよいでしょう。

プロファイルの基礎的な表示方法が分かりましたので、プロファイルの活用事例を試してみます。ユーザーの立場で Web アプリケーションをアクティブにして、メニュー「2. Sleep 5 sec at API」から「4. Sleep 30 sec at API」を上から順にクリックして、WebAPI で時間が掛かる処理を実行してみます。

運用担当の立場で Grafana に戻り、プロファイルを確認します。リアルタイム更新されるさまを暫く眺めますが、Sleep 処理ではそれほど CPU を使っていないため、大きく値が変わっていないことが確認できるはずです。

再度ユーザーの立場で Web アプリケーションをアクティブにして、今度はメニュー「5. Loop 5 sec at API」から「7. Loop 30 sec at API」を上から順にクリックして、WebAPI で時間が掛かる処理を実行してみます。

運用担当の立場で Grafana に戻り、プロファイルを確認します。引き続き、リアルタイム更新されるさまを暫く眺めると、今度は「ms(ミリ秒)」から「s(秒)」へ単位が変わり、値が大きく増えたプロセスが存在することが確認できるはずです。もう少し詳細に迫ってみましょう。

大きく値が増えたプロセスを抽出します。
- jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.readFile:47.5 s
- jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.lambda$loop$1:47.9 s
- jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl$$Lambda_.accept:47.9 s
- jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.loop:47.9 s
- jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.rollDice:48.0 s
- jp/sios/apisl/handson/rollingdice/webapp/webapi/controller/WebApiController.rollDice:48.0 s
WebAPI のシーケンス図に大きく値が増えたプロセスをマッピングしてみると、一番深い階層でコールされる「WebApiServiceImpl.readFile」が要因であることが想像できます。

プロファイル画面で示すと赤枠の一行に絞り込めることになります。

実際にソースコードを見てみると、ループ処理の中で、都度ファイルを開いて読み込む readFile メソッドが呼ばれていることが分かります。これにより、ループ回数分のファイルアクセス(=ディスクアクセス)が発生し、それに伴って CPU リソースも消費されていたことがコードレベルでも裏付けられます。
:
while (System.currentTimeMillis() < endTime) {
line = this.readFile(FILE_PATH_IN_LOOP);
:
}
: private String readFile(final String filePath) {
String line = null;
try (InputStream inputStream = new ClassPathResource(filePath).getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
LOGGER.debug("Successfully loaded a file.");
line = reader.readLine();
:改めてシーケンス図で解析結果を整理すると、指定時間内にディスクアクセス処理がループで連続実行されたため、CPU 利用時間が肥大化していたと言えます。 今回はプロファイルを用いてボトルネックを特定する手順を学ぶ「意図的な実装」のため修正は行いませんが、実際の運用コードであれば、ファイル読み込みをループの外に出す、あるいはキャッシュを利用してディスクアクセスを減らすといったパフォーマンスのチューニングを行うと良いでしょう。

オリジナルのダッシュボード作成
ここまでの手順では、Explore 画面を使用して、その都度データソースやクエリを指定する「アドホックな分析」を行ってきました。
しかし実際の運用では、重要な指標や頻繁に確認するログを常に表示させ、定点観測することが求められます。そこで、よく見る検索条件やグラフをパネルとして保存し、いつでも即座にシステムの状態を把握できるオリジナルのダッシュボードを作成してみましょう。
Explore で表示しているメトリクスをダッシュボード化したいとします。メトリクスを表示している状態で、画面上部にある「Add」ボタンをクリックすると表示するメニューから「Add to dashboard」をクリックします。

表示された「Add panel to dashboard」ダイアログで「Open dashboard」ボタンをクリックします。

先ほどまで Explore で表示ていたメトリクスがパネル化して載った新たなダッシュボードが生成されました。

ブラウザで新たなタブを立ち上げて、次にダッシュボードに載せたいログを Explore で表示させ、メトリクス同様に「Add > Add to dashboard」を選択してダッシュボード化します。

メトリクス同様に、Explore で表示ていたログがパネル化して載った新たなダッシュボードが生成されましたが、ログのパネルをメトリクスのダッシュボードにコピー&ペーストするため、パネル右上角の三点リーダーをクリックしすると表示するメニューから「More… > Copy」をクリックして、ログのパネルをクリップボードにコピーします。

最初に作ったメトリクスのダッシュボードに戻り、「Add」ボタンクリックで表示するメニューから「Paste panel」をクリックします。

メトリクスとログのパネルが載ったダッシュボードが出来上がりました。このような流れで、頻繁に確認したい項目をまとめた簡易的なオリジナルダッシュボードを作成することもできます。

ここまでの手順と Variables を使ってサンプルとして作ったダッシュボードを用意してありますので、Dashboards 画面から確認してみてください。

メトリクス、ログ、ノードグラフのパネルが載ったダッシュボードで、Server Address フィールドで「webapp-webui」「webapp-webapi」を選択することで表示を切り替えることができます。「Edit」ボタンを使って構成や仕組みを確認してみてください。

フロントエンドのユーザー体験観測 (Faro)
これまでの手順ではサーバーサイドの内部状態を観測してきましたが、ユーザーが実際に触れるブラウザ上(フロントエンド)での体験も同様に重要です。 本環境には Grafana Faro Web SDK が組み込まれており、実際のユーザー体験(Real User Monitoring: RUM)を可視化できます。ページの読み込み速度などのパフォーマンス指標(Web Vitals)や、ブラウザで発生した JavaScript エラー、そしてユーザーが利用しているブラウザの種類や OS といった環境ごとのアクセス傾向を確認してみましょう。
RUM の観測用にダッシュボードを用意してあります。ダッシュボード一覧から「My Real-time User Monitoring」を選択します。

ダッシュボードに遷移すると、まず始めに「Performance」のパネルでパフォーマンスに関する各種値が確認できます。

- 確認できる代表的な値は以下を確認ください。
- TTFB (Time to First Byte):サーバーの応答待ち時間です。ブラウザがリクエストを送ってから、最初のデータが返ってくるまでの時間を指します。(参照先:Time to First Byte (TTFB) | Articles | web.dev )
- FCP (First Contentful Paint):「何かが表示されるまでの時間」です。テキストや画像など、ページの一部が初めて描画された時点を指し、この値には TTFB の時間も含みます。(参照先:First Contentful Paint (FCP) | Articles | web.dev )
- LCP (Largest Contentful Paint):「メインコンテンツが表示されるまでの時間」です。そのページで最も大きな画像やテキストブロックが表示された時点を指し、この値には TTFB や FCP の時間もすべて含んだトータルタイムです。(Largest Contentful Paint (LCP) | Articles | web.dev )
- CLS (Cumulative Layout Shift):「視覚的なズレの量」です。画像の遅延読み込みなどでレイアウトがガクッとズレる度合いを示します。数値が低いほど、画面が安定しています。(参照先:Cumulative Layout Shift (CLS) | Articles | web.dev )
- FID (First Input Delay):「操作可能になるまでの遅延」です。ユーザーがボタンをクリックしてから、ブラウザが実際にその処理を開始できるまでの待機時間を指します。(参照先:First Input Delay (FID) | Articles | web.dev )
次に「Meta」のパネルでクライアントのOSやブラウザなどのメタ情報が確認できます。

次に「Exceptions」のパネルでクライアントサイドで発生した例外(エラー)に関する各種値を参照するために、Web アプリケーションで例外を発生させます。ブラウザの検証ウィンドウ(デベロッパーツール)を表示している状態で、Web アプリケーションの Roll a dice メニューから「10. RUM Simulation at Browser > Trigger JS error」のリンクをクリックすると、画面中央にエラー発生を知らせるメッセージ(トースト通知)が一瞬表示され、裏側では例外が発生したことが検証ウィンドウでも確認できるはずです。

ダッシュボードに戻り「Exceptions」のパネルで例外の発生件数や例外時に出力されたメッセージなどが確認できます。

次に「Events」のパネルでユーザー操作やライフサイクルの変化が発生した回数が参照できます。

次に「Faro log」のパネルでここまでの集計の元となっている実際のログが確認できます。

アラートからエラー原因の探索 (Alerting)
実際の運用現場において、24時間365日ずっとダッシュボードを監視し続けることは現実的ではありません。システム内で異常が発生した際には、自動的に通知を受け取る仕組みが必要です。
アラートを設定することで、監視の負荷を下げつつ、トラブル発生時の初動対応をいかに迅速化できるかを確認してみましょう。
本セクションでは、Alerting を利用した以下のインシデント対応フローを体験します。
- Web アプリケーションのログ (Loki) を監視し、「ERROR」レベルのログ出力を検知する。
- 運用担当者へ即座にアラートメールを送信する。
- メールのリンクから Grafana へ遷移し、前述のログやトレース機能を活用して原因を特定する。
アラーティングを機能させるには、アラートの通知先を定義する Contact points と、監視条件を定義する Alert rules の設定が必要となりますが、まず始めに Contact points の定義から説明します。
左ペインのメニューから「Contact points」を選択し、表示された Contact points 画面には2つの通知先が登録されています。1つ目の「grafana-default-email」は Grafana がデフォルトで登録している通知先で、2つ目の「Hands-on Receive E-mail」がハンズオン用に用意した通知先です。

「Hands-on Receive E-mail」の設定内容は以下の通りです。

- Name: 通知先定義の名称として「Hands-on Receive E-mail」を入力しています。
- Integration: ハンズオンではメールで通知するため「Email」を選択しています。
- Address: 通知先のメールアドレスに MailDev コンテナで受信できる「target-address@test.com」を入力しています。
- Test: Test ボタンをクリックすることで「Address」の宛先へテストメールを送信します。
続いて Alert rules で定義している監視条件を説明します。
左ペインのメニューから「Alert rules」を選択し、表示された Alert rules 画面で「Hands-on Training > Eval Interval – 10s」フォルダ内に登録されている「Error level – WebUI log」が該当します。

「Error level – WebUI log」の設定内容は以下の通りです。

- 1. Enter alert rule name:
- Name: 監視条件の名称として「Error level – WebUI log」を入力しています。
- 2. Define query and alert condition:
- アラートを発動する監視条件を定義します。
- 条件A:
- データソース: 「Loki」を選択しています。
- Options:
- 第1条件: 「webapp-webui」から送出されたログを監視対象にします。
- 第2条件: エラーレベルのログを意図する、「detected_level」ラベルに大文字小文字問わず「error」の値を持ったログを抽出します。
- 第3条件: 過去1分間のログを監視対象とします。
- 第4条件: 第1~3条件に該当するログを対象に( 「message」ラベルの)件数をカウントします。
- 条件C:
- 条件Aが0件を超過(=1件以上存在)した場合にアラート判定とします。
- 3. Add folder and labels:
- Folder: 「Hands-on Training」フォルダを選択しています。
- 4. Set evaluation behavior:
- Evaluation group and interval: 監視条件の評価間隔を「Eval Interval – 10s」名称で10秒間隔で評価します。
- Pending period: 監視条件が1件でも一致したら即アラートにしたいので「None」を設定しています。
- 5. Configure notifications:
- Contact point: アラートの送信先に「Hands-on Receive E-mail」を設定しています。
実演に入る前に、Contact Points で送信するメールを受信する MailDev をブラウザでアクセスします。既に「[FIRING:1] DatasourceNoData ~」などの件名のアラートメールが数通届いています。これはコンテナ起動時の Web アプリケーションがまだ起動していない最中の監視が拾った結果なので、コンテナを構築した時間帯付近のアラートメールは無視してください。

ここからが本格的なアラートの実演で、ユーザーの立場として Web アプリケーションにアクセスし、「Roll normally」をクリックしてサイコロを振ります。
その状況で運用担当として MailDev にアクセスし、暫く経過してもアラートメールが届かないことを確認します。

再びユーザーの立場として Web アプリケーションにアクセスし、「Trigger 5xx error at API」リンクをクリックして Web アプリケーションでエラーが発生した状況にします。

運用担当として MailDev を確認すると、Web アプリケーションでエラー発生から約1分以内に件名が「FIRING」と書かれたアラートメールが届きます。これが障害発生の通知です。メール本文内の「View alert」ボタンをクリックし、Grafana のアラート詳細画面に遷移します。

エラー発生から1分以内に Grafana へ遷移できると、画面下部に「Firing」(発報中)の状態を示すラベルが表示されています。(遷移に1分を超えてラベル表示が解除されていたとしても履歴は残っているので、そのまま進めます。)画面上の「View in Explore」をクリックして Explore に遷移しエラーの原因調査を開始します。

画面下部の「Logs sample」領域で監視に引っ掛かったエラーログを確認しますが、エラー発生から1分を超えてエラーログが表示されていない場合は、画面中央の「Count over time」を「Rang=1m」から「Rang=5m」などにレンジを拡大してエラーログを表示させます。ログが確認できたら、ログ表示の右上にある「Open logs in split view」ボタンをクリックすると画面をスプリットして右半分にログを表示させます。

左半分のログ表示はアラーティングから遷移直後の Explore で機能が制限されているため、左半分のログを閉じ「Tempo」への連携ボタンが表示される右半分のログ表示を全画面表示にします。

ログをクリックして画面右側に詳細情報を展開し、下側へスクロールすると「Tempo」ボタンが現れます。これをクリックしてトレースに遷移します。

トレースを表示させると1つ目の WebUI から WebAPI への呼び出しにおいて、WebAPI 側でエラーが発生していることが一目で分かります。

トレースの中からエラーの発生源と思われる WebAPI のスパンをクリックし、出現したパネル内の「Logs for this span」をクリックします。

画面右半分がトレース表示から、「Logs for this span」をクリックしたスパンに関連するログだけにフィルタリングされた WebAPI のログに切り替わります。

ログを注視していると手掛かりになりそうなメッセージが見つかります。

ログに記録されているメッセージとソースコードを対比すると、例外が発生している箇所が見つかり、ここがアラートの原因であることが把握できます。
private void error(final Optional<Boolean> optError) {
UtilEnvInfo.logStartClassMethod();
if (optError.isPresent() && optError.get()) {
LOGGER.error(
"!!! Intentional exception triggered: '{}' !!!",
"HandsOnException");
throw new HandsOnException("Intentional error triggered by request parameter.");
}
}実際の運用では、把握した原因に対して改修などで応急処置や恒久対応を行うことで事故完了となりますが、今回はデモとして意図的にエラーを発生させているので、該当箇所は修正せずに、今後は同じ操作をしないと言う誓いで事故完了の扱いとします。
エラー発生から5分を経過すると、MailDev にエラー発生が解消されたことを示す「RESOLVED」メールが届いていることが確認できるので、これでエラー探索の一連の流れが終了となります。

以上で、オブザーバビリティーの実演はすべて終了です。お疲れ様でした! メトリクスの可視化からアラートを起点としたトラブルシューティングまでを体験することで、オブザーバビリティーが実際の運用でどう役立つのか、その「手触り」を掴んでいただけたなら幸いです。
環境のクリーンアップ
オブザーバビリティーの実現が一通り終わったら、これまでに利用した環境をクリーンアップしてハンズオンを終了します。
コンテナ削除スクリプトの実行
ターミナルを用いて、以下のディレクトリへ移動します。
$ cd ~/handson/hands-on-grafana/container/環境構築時にも使用したスクリプトに down オプションを指定して実行して、オブザーバビリティー環境の各種コンテナを停止・削除します。
$ ./CREATE_CONTAINER.sh downスクリプトの実行が終わったら、list オプションを付けてスクリプトを実行し、起動しているコンテナが存在しない(一覧に表示されない)ことを確認します。
$ ./CREATE_CONTAINER.sh list
### START: Show a list of container ##########
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES以上で環境のクリーンアップは完了です。お疲れ様でした。
続く Appendix では、このハンズオン環境を裏で支えている設定ファイルやスクリプトについて解説します。「どうやってこの環境を作ったのか詳しく知りたい!」という方は、ぜひこのままご覧ください。
Appendix
ハンズオン環境の構築や設定手順で利用している各種スクリプトや設定ファイルの実装内容について解説します。
コンテナ構築スクリプトの解説
「コンテナ構築スクリプトの実行」章で使用する「CREATE_CONTAINER.sh 」スクリプトの処理内容について解説します。
コンテナの構築
本ハンズオン用のリポジトリは、別リポジトリ「Toshiharu-Konuma-sti/hands-on-rollingdice-webapp 」で管理されている Web アプリケーションを参照するサブモジュールを含んでいるので、サブモジュール配下のソースコードを最新版で取得します。
$ git submodule update --init
$ git submodule update --remote webapp用意した「docker-compose.yml」と「docker-compose-webapp.~.yml」のコンテナ定義ファイルで一気にコンテナを構築します。
$ docker-compose \
-f docker-compose.yml \
-f docker-compose-webapp.base.yml \
-f docker-compose-webapp.mode.grafana.yml \
up -d -V --remove-orphans
Creating network "intra-net" with driver "bridge"
Creating network "hands-net" with driver "bridge"
Creating grafana ... done
:
Creating webapi ... doneコンテナ構築に利用しているコンテナ定義ファイルの内容は以下を参照してください。
オブザーバビリティー環境の構成ファイル解説
オブザーバビリティー環境のコンテナを構築する各種ファイルについて説明します。
コンテナ定義(docker-compose)
オブザーバビリティー環境のコンテナを構築する「docker-compose.yml」と環境変数ファイルです。
- docker-compose.yml
- 「x-healthcheck-minio」ノードでは、MinIO に対して実施するヘルスチェック処理として、9000番ポートが TCP 接続できるかどうかの確認を実装しています。
- 「grafana」コンテナの特記事項は以下の通りです。
- 「environment」ノードの「GF_SMTP_HOST」にて、アラート通知でメール送信する際の SMTP サーバーを指定します。
- .env
- 「MINIO_ROOT_PASSWORD」にて、MinIO にアクセスするコンテナが接続時に必要となるパスワードを指定します。
Grafana 設定ファイル
データソースの定義ファイルです。
- datasources.yaml
- メトリクス表示用に「Mimir for Metrics」データソース、ログ表示用に「Loki」データソース、トレース表示用に「Tempo」と「Mimir for Trace」のデータソースを用意しています。
ダッシュボードの定義ファイルです。
- dashboards.yaml
- 「My Explore Collection」「My Real-time User Monitoring」の2つのダッシュボードが認識されるように定義しています。
- my-explore-collection.json
- 「My Explore Collection」ダッシュボードの定義ファイルで、Exemplar を有効にしたメトリクス、ログ、ノードグラフ、および CPU 利用時間とメモリ割当て量のプロファイルを確認することができます。
- 配置位置を示す「gridPos」ノードの考え方は以下の通りです。
- 「x」は横幅を表し、画面横幅全体を仮想的な24ブロックで考えます。
- 「y」と「h」の関係は、縦にA→B→Cとパーツが並んでいた場合、最初のパーツは「0」から始まり「y+h」が次に続くパーツの「y」になるため、
Aが「A:y=0, h=2」の場合、次に続くBは「B:y=2, h=3」、その次に続くCは「C:y=5, h=4」となります。
- my-real-time-user-monitoring.json
- 「My Real-time User Monitoring」ダッシュボードの定義ファイルで、Faro から送信されてきた RUM を確認することができます。
アラートの定義ファイルです。
- contact-points.yaml
- 「アラートからエラー原因の探索 (Alerting)」章で説明している Contact points を定義しているファイルです。
- alert-rules.yaml
- 「アラートからエラー原因の探索 (Alerting)」章で説明している Alert rules を定義しているファイルです。
Mimir 設定ファイル
Mimir に適用する設定ファイルです。
- mimir.yaml
- 「common.storage.backend」と「common.storage.s3」ノードで、ストレージに S3(実際には互換ストレージの MinIO)を指定します。
Loki 設定ファイル
Loki に適用する設定ファイルです。
- loki.yaml
- 「common.storage.s3」ノードで、ストレージに MinIO を指定します。
Tempo 設定ファイル
Tempo に適用する設定ファイルです。
- tempo.yaml
- 「metrics_generator」ノードで、サービスグラフの描画用にトレースからメトリクスの生成と、生成されたメトリクスの送信先を指定します。
- 「storage.trace.backend」と「storage.trace.s3」ノードで、ストレージに MinIO を指定します。
Alloy 設定ファイル
Alloy に適用する設定ファイルです。
- config.alloy
- 「pyroscope.receive_http」ノードで Pyroscope から送信されてくるデータの受信ポートと、「pyroscope.write」ノードで送信先サーバーを指定します。
- 「faro.receiver」ノードで Faro から送信されてくるデータの受信ポートと、「faro.receiver.output」ノードで送信先を定義するノードを指定します。送信先として指定された「loki.writ」と「otelcol.exporter.otlp」ノードで送信先のサーバーアドレスを指定します。
ロードバランサー設定ファイル
Mimir、Loki、Pyroscope の前段に配置するロードバランサーの Nginx に適用する設定ファイルです。
Tempo の前段に配置するロードバランサーの Nginx に適用する設定ファイルです。
- nginx-tempo-otlp.conf
- nginx-tempo-view.conf
- Tempo では、OpneTelemetry Collector からトレースが送られてくるポート番号(4318)と、Grafana から表示用にアクセスしてくるポート番号(80)が異なっているため、それぞれのロードバランサーを建てています。
OpenTelemetry Collector 設定ファイル
OpenTelemetry Collector コンテナに適用する設定ファイルです。
- otel-collector-config.yaml
- 「receivers.otlp.protocols.http」ノードで、Web アプリケーションから OpenTelemetry 形式のメトリクスを HTTP で受信できるように指定します。
- 「receivers.prometheus.config.scrape_configs」ノードの「job_name: “webapp”」設定で、OpenTelemetry Collector コンテナから Web アプリケーションへ Prometheus 形式でメトリクスを取得するように指定します。
- 「receivers.mysql」ノードで、MySQL サーバーからメトリクスを取得するように指定します。
プロダクション環境の構成ファイル解説
プロダクション環境のコンテナを構築する各種ファイルについて説明します。
コンテナ定義(docker-compose)
アプリケーションを構成する各コンテナを構築する「docker-compose-webapp.mode.grafana.yml」ファイルです。
- docker-compose-webapp.mode.grafana.yml
- 「webapp-webui」と「webapp-webapi」コンテナが起動する際に実行するコマンドを指定する「command」ノードでは、Web アプリケーションを Pyroscope の自動計装でプロファイリングさせたいので、Web アプリケーションの起動に「
./gradlew bootRun」は使わずに「java -javaagent:pyroscope.jar」コマンドを使い該当のライブラリをロードしながら起動します。
- 「webapp-webui」と「webapp-webapi」コンテナが起動する際に実行するコマンドを指定する「command」ノードでは、Web アプリケーションを Pyroscope の自動計装でプロファイリングさせたいので、Web アプリケーションの起動に「
まとめ
実際に手を動かしてログやメトリクスを追うことで、「オブザーバビリティー(可観測性)」という言葉の解像度がぐっと上がったのではないでしょうか?
複雑な理論を学ぶことも大切ですが、こうして実際の画面でシステムの挙動を見る体験こそが、DevOps 実践への第一歩だと考えており、今回の体験を通して、「意外と自分でも構築できそうだ」「この機能は今の現場でも使えそうだ」と、少しでも身近に感じていただけたなら本望です。
なお、本記事で紹介した OSS 版の活用はもちろんのこと、運用負荷を軽減したい場合には Grafana Cloud を選択肢に入れることも可能です。「まずは OSS で小さく始めて、規模が大きくなったら Cloud へ」といった柔軟な使い分けも検討いただけます。
このデモ環境での体験を切っ掛けに、ぜひ次は皆様自身のアプリケーションやインフラ環境でも、この「見える化」に挑戦するモチベーションに繋がれば幸いです。

