はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 「理屈はいいから、まずは実際にオブザーバビリティー(可観測性)というものを動かして体験してみたい」。 本記事は、そんな方々に向けて、システム運用に不可欠なオブザーバビリティーの基礎を手を動かしながら学習できる、実践的な入門ガイドとして用意しました。 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 を介した連携で構成しています。 オブザーバビリティーの 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 で実装したアプリケーションで構成します。 メトリクス、ログ、トレース、およびプロファイルは、自動計装で収集しているので、ビジネスロジックに手動計装のロジックは一切実装していません。 異常検知のアラートは、アラートメールの通知先もハンズオン環境内で完結するように、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)環境を用意します。 初期環境構築: WSL 環境 on Windows Docker Engine 環境の構築 コンテナ環境を使うため、以下手順を参考に Ubuntu へ Docker Engine 環境を用意します。 初期環境構築: Docker Engine on Ubuntu 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 コンテナが稼働するのでブラウザでアクセスします。 http://localhost:3000 Web アプリケーションのコンテナが稼働するのでブラウザでアクセスします。 http://localhost:8081/ 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 サイコロの出目、日時、およびシーケンス番号のフィールドで構成するテーブルを保持します。 オブザーバビリティーの実演 メトリクス閲覧 (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」 OpenTelemetry JVM Micrometer | Grafana Labs Micrometer (OpenTelemetry 形式) のメトリクスを表示します。 Mimir 項目:「Mimir for Metrics」 Loki 項目:「Loki」 Node Exporter Full | Grafana Labs Node Exporter のメトリクスを表示します。 Mimir 項目:「Mimir for Metrics」 ログ閲覧 (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 アプリケーションをアクティブにしてサイコロを振ります。 http://localhost:8081/ 再び運用担当の立場で 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"} 」を入力します。 「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 リソースも消費されていたことがコードレベルでも裏付けられます。 webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java : while (System.currentTimeMillis() < endTime) { line = this.readFile(FILE_PATH_IN_LOOP); : } : webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java 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 アプリケーションがまだ起動していない最中の監視が拾った結果なので、コンテナを構築した時間帯付近のアラートメールは無視してください。 http://localhost:1080 ここからが本格的なアラートの実演で、ユーザーの立場として 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 のログに切り替わります。 ログを注視していると手掛かりになりそうなメッセージが見つかります。 ログに記録されているメッセージとソースコードを対比すると、例外が発生している箇所が見つかり、ここがアラートの原因であることが把握できます。 webapp/webapi/src/main/java/jp/sios/apisl/handson/rollingdice/webapp/webapi/service/WebApiServiceImpl.java 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.yml プロダクション環境の構成ファイル解説 docker-compose-webapp.base.yml docker-compose-webapp.mode.grafana.yml オブザーバビリティー環境の構成ファイル解説 オブザーバビリティー環境のコンテナを構築する各種ファイルについて説明します。 コンテナ定義(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 に適用する設定ファイルです。 nginx-mimir.conf nginx-loki.conf nginx-pyroscope.conf 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 」コマンドを使い該当のライブラリをロードしながら起動します。 まとめ 実際に手を動かしてログやメトリクスを追うことで、「オブザーバビリティー(可観測性)」という言葉の解像度がぐっと上がったのではないでしょうか? 複雑な理論を学ぶことも大切ですが、こうして実際の画面でシステムの挙動を見る体験こそが、DevOps 実践への第一歩だと考えており、今回の体験を通して、「意外と自分でも構築できそうだ」「この機能は今の現場でも使えそうだ」と、少しでも身近に感じていただけたなら本望です。 なお、本記事で紹介した OSS 版の活用はもちろんのこと、運用負荷を軽減したい場合には Grafana Cloud を選択肢に入れることも可能です。「まずは OSS で小さく始めて、規模が大きくなったら Cloud へ」といった柔軟な使い分けも検討いただけます。 このデモ環境での体験を切っ掛けに、ぜひ次は皆様自身のアプリケーションやインフラ環境でも、この「見える化」に挑戦するモチベーションに繋がれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Grafana OSS LGTM スタックで体験する『オブザーバビリティー入門』 first appeared on SIOS Tech Lab .
Mermaidの見た目、もうちょっとなんとかならんか ども!Mermaidで図を作るたびに「構造は最高なんだけど、見た目がなぁ…」と思ってた龍ちゃんです。 「 Claude CodeでMermaid図を作成する 」でMermaid図の自動生成について書きました。Mermaidはコードで構造を書けるから、フローチャートやシーケンス図がサクッと作れます。AIに「こういう図作って」って言えば一発で出てくるんですよね。 ただ、デフォルトのデザインがシンプルすぎるんですよね。設計書やブログに貼るにはちょっと素朴。 HTML+Tailwind CSSで図解を丸投げする方法 も試したんですよ。装飾の自由度は高いんですけど、フロー図やシーケンス図をCSSで手動配置するのが辛い。特に矢印の表現が鬼の難易度です。Mermaidが自動でやってくれる部分を手で書くのはしんどいです。 じゃあ合体させたらどうだ、と。 Mermaidの構造表現力 + Tailwind CSSの装飾力 。Mermaidに図の構造を任せて、周りの見た目はTailwindで自由に作れます。これを1つのHTMLファイルにまとめてPNGに変換します。 今回の内容です。 コピペで使えるHTMLテンプレート2種(右サイド注釈型 / 下部グリッド注釈型) 色を揃えるカスタマイズのコツ 実際にぶつかった壁と対処法 PNG変換の手段3つ まず完成形を見せます こういうのが作れます。 左がMermaidで描画したシーケンス図、右がTailwind CSSで装飾した注釈パネル。これ、1つのHTMLファイルから出てきます。 ポイントは、Mermaid図の色とTailwind装飾の色が揃ってるところですね。青系で統一してるから、「Mermaidの部分だけ浮いてる」みたいなことにならない。 注釈パネルには設計ポイント、セキュリティ考慮、エラーハンドリングみたいな文脈情報を書けます。Mermaid図だけだと「何が起きてるか」は分かるけど「なぜこうしたか」は伝わらないんですよね。注釈パネルがその「なぜ」を補ってくれます。 関連記事 : 仕様書の図でAIの理解と人間の視認性を両立する方法に関しては「 仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン 」で詳しく書いてます。AIに図の構造を正確に伝えたい人はこちらもどうぞ。 テンプレートA: 右サイド注釈型 ここからが本題です。コピペして使えるHTMLテンプレートを2種類用意しました。 仕組みはシンプルで、1つのHTMLにTailwind CDNとMermaid CDN(ESM版)を同時に読み込んでるだけです。2つのCDNは競合しません(検証済み)。テンプレートをコピペすればそのまま動きます。色のカスタマイズについてはテンプレートの後で説明しますね。 まずはテンプレートA。冒頭で見せた完成形がこれです。左にMermaid図、右に注釈パネルを並べるレイアウトですね。 向いてる場面 : シンプルな図(ノード8個以下)に設計ポイントを3-4個添えたいとき キャンバスサイズ : 1280x720px <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Level 2: Mermaid + Tailwind CSS Decoration</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body class="w-[1280px] h-[720px] m-0 p-0 overflow-hidden bg-white"> <div class="w-full h-full flex gap-6 p-10"> <!-- 左: Mermaid図 --> <div class="flex-1 border-2 border-blue-300 rounded-lg p-6 bg-blue-50"> <h2 class="text-xl font-bold text-blue-900 mb-4">認証フローのシーケンス図</h2> <div class="mermaid"> sequenceDiagram participant C as クライアント participant S as サーバー participant DB as データベース C->>+S: ログインリクエスト S->>+DB: ユーザー照合 DB-->>-S: 認証結果 alt 認証成功 S-->>C: アクセストークン発行 else 認証失敗 S-->>C: エラーレスポンス(401) end </div> </div> <!-- 右: 注釈パネル --> <div class="w-72 flex flex-col gap-3"> <div class="bg-yellow-50 border border-yellow-300 rounded p-3"> <h3 class="text-sm font-bold text-yellow-800">設計ポイント</h3> <p class="text-xs text-yellow-700">DB直接参照を避け、サービス層を経由させることで結合度を低減</p> </div> <div class="bg-green-50 border border-green-300 rounded p-3"> <h3 class="text-sm font-bold text-green-800">セキュリティ考慮</h3> <p class="text-xs text-green-700">トークンはJWT形式、有効期限15分で自動失効</p> </div> <div class="bg-red-50 border border-red-300 rounded p-3"> <h3 class="text-sm font-bold text-red-800">エラーハンドリング</h3> <p class="text-xs text-red-700">認証失敗時は具体的なエラー原因を返さない(セキュリティ対策)</p> </div> </div> </div> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true, theme: 'base', themeVariables: { primaryColor: '#dbeafe', primaryBorderColor: '#3b82f6', lineColor: '#6b7280', fontFamily: 'system-ui, sans-serif' } }); </script> </body> </html> 使い方はシンプルで、3箇所を差し替えるだけです。 Mermaid記法の部分 ( <div class="mermaid"> の中身)を自分の図に差し替える 注釈パネルの内容 (右側の3つのパネル)を自分の設計ポイントに差し替える themeVariables で色を変えたければカラーコードを調整する 注釈パネルは増減しても大丈夫です。3個が収まりいいですけど、2個でも4個でもレイアウトは崩れません。 色の統一について 完成形の画像を見て「Mermaid図と注釈パネルの色が揃ってるな」と思った人もいるかもしれません。これはテンプレート末尾の themeVariables でMermaid図の配色をTailwindのカラーパレットに合わせてるからです。 themeVariables: { primaryColor: '#dbeafe', // Tailwind blue-100 primaryBorderColor: '#3b82f6', // Tailwind blue-500 lineColor: '#6b7280', // Tailwind gray-500 fontFamily: 'system-ui, sans-serif' } 色を変えたいときはここのカラーコードを差し替えてください。 Tailwindのカラーパレット から持ってくると注釈パネル側と揃えやすいです。 1つ注意点として、 Mermaid図の中にTailwindクラスは効きません 。Mermaid図はSVGとして生成されるんで、外部CSSが適用できないんですよね。色のカスタマイズは themeVariables 経由のみです。 テンプレートB: 下部グリッド注釈型 テンプレートAだと、図が複雑になったときに左側のスペースが足りなくなります。シーケンス図で参加者が4人以上いたり、メッセージが10本近くあると、右の注釈パネルに幅を取られて図が窮屈になるんですよね。 そういうときはテンプレートBです。図を上部に全幅で配置して、注釈パネルは下部にグリッドで並べます。 向いてる場面 : 複雑な図(10ステップ近いシーケンス、状態遷移図)で図に全幅を使いたいとき キャンバスサイズ : 1280x1080px(縦に拡張) <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OAuth 2.0 認可コードフロー</title> <script src="https://cdn.tailwindcss.com"></script> <style> .mermaid svg { transform: scale(3.2); transform-origin: center center; } </style> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true, theme: 'base', sequence: { diagramMarginX: 10, diagramMarginY: 10, actorMargin: 80, messageMargin: 40 }, themeVariables: { primaryColor: '#e0f2fe', primaryBorderColor: '#0284c7', primaryTextColor: '#0c4a6e', lineColor: '#64748b', fontFamily: 'system-ui, sans-serif' } }); </script> </head> <body class="w-[1280px] h-[1080px] m-0 p-0 overflow-hidden bg-white"> <div class="w-full h-full flex flex-col p-8 gap-6"> <!-- 上部: タイトル --> <div> <div class="text-lg font-bold text-gray-800">OAuth 2.0 認可コードフロー</div> <div class="text-sm text-gray-500">ソーシャルログイン実装の標準パターン</div> </div> <!-- 中央: 図(全幅) --> <div class="flex-1 border border-gray-200 rounded-lg p-6 bg-gray-50 flex items-center justify-center"> <div class="mermaid"> sequenceDiagram actor U as ユーザー participant App as Webアプリ participant Auth as 認可サーバー participant API as リソースサーバー U->>App: ログインボタンクリック App->>Auth: 認可リクエスト(client_id, redirect_uri, scope) Auth->>U: ログイン画面表示 U->>Auth: 認証情報入力 Auth->>App: 認可コード(code) App->>Auth: トークンリクエスト(code, client_secret) Auth->>App: アクセストークン + リフレッシュトークン App->>API: APIリクエスト(Bearer token) API->>App: リソースデータ App->>U: ユーザー情報表示 </div> </div> <!-- 下部: 注釈パネル(横並び) --> <div class="grid grid-cols-4 gap-3"> <div class="bg-blue-50 border border-blue-200 rounded-lg p-3"> <div class="text-sm font-bold text-blue-800">フロー概要</div> <div class="text-xs text-blue-700 mt-1">RFC 6749準拠の認可コードグラント。SPAではPKCE拡張を推奨。</div> </div> <div class="bg-amber-50 border border-amber-200 rounded-lg p-3"> <div class="text-sm font-bold text-amber-800">セキュリティ要件</div> <div class="text-xs text-amber-700 mt-1 space-y-1"> <div>state パラメータでCSRF対策</div> <div>client_secret はサーバー側で保持</div> <div>redirect_uri の完全一致検証</div> </div> </div> <div class="bg-green-50 border border-green-200 rounded-lg p-3"> <div class="text-sm font-bold text-green-800">実装チェックリスト</div> <div class="text-xs text-green-700 mt-1 space-y-1"> <div>トークンの安全な保存(httpOnly cookie)</div> <div>リフレッシュトークンのローテーション</div> <div>スコープの最小権限原則</div> </div> </div> <div class="bg-gray-100 border border-gray-200 rounded-lg p-3"> <div class="text-sm font-bold text-gray-700">補足</div> <div class="text-xs text-gray-600 mt-1">アクセストークンの有効期限は15分〜1時間が一般的。期限切れ時はリフレッシュトークンで自動更新。</div> </div> </div> </div> </body> </html> テンプレートAとの違いは3つです。 キャンバスが縦に拡張 (720px → 1080px)。図に余裕ができます 注釈パネルが下部グリッド 。図が全幅を使えるんで、参加者が4人いても窮屈になりません CSS transformでMermaid図を拡大 ( scale(3.2) )。Mermaidは複雑な図を自動縮小するんで、それを補正してます scaleの調整について テンプレートBの scale(3.2) は、上のOAuth 2.0の例(参加者4人・メッセージ10本)に合わせた値です。自分の図に差し替えたら、scaleも調整が必要です。 Mermaidは図の複雑度に応じて自動縮小するんですけど、その縮小率が図ごとに変わるんですよね。なので 図を差し替えるたびにscaleを微調整する のが前提の設計です。 調整の手順はシンプルで、HTMLファイルをブラウザで開いて、文字が読める大きさになるまで scale の数値を上げ下げするだけです。目安としてはこんな感じ。 図の複雑度 scale目安 シンプル(参加者2-3人、メッセージ5本以下) 1.5〜2.0 中程度(参加者3-4人、メッセージ8本前後) 2.5〜3.5 複雑(参加者4人以上、メッセージ10本以上) 3.0〜4.0 どっちを使う? 条件 テンプレート 図がシンプル(ノード8以下) A: 右サイド注釈型 図が複雑、横に広がる B: 下部グリッド注釈型 注釈が少ない(2-3個) A 注釈が多い(4個以上) B 迷ったらまずテンプレートAで試して、図が窮屈ならBに切り替える、でいいと思います。 ぶつかった壁と対処 検証してて何度かハマったので、先に共有しておきます。 ノード数には上限がある Mermaidは図の要素が増えると自動で縮小するんですよね。1280pxの幅に収めようとするから、ノードが多いと文字が潰れて読めなくなります。 実際に検証して、このくらいが上限だなというラインが見えました。 図の種類 推奨上限 超えるとどうなるか フローチャート 8-10ノード 文字が小さくなって読めない シーケンス図 4参加者・8メッセージ ラベルが長いと自動縮小される 状態遷移図 8-10状態 縦に伸びてキャンバスからはみ出す 上限を超えそうなときは、図を分割するか、テンプレートBでキャンバスを拡張するのがいいです。 サイズ問題の対策3種 実際にぶつかったサイズ問題と、それぞれの対処法です。 症状 対策 やり方 図がキャンバスからはみ出す キャンバスを拡張する h-[720px] → h-[1080px] に変更。PNG変換時も --height 1080 を指定 図が小さすぎて読めない CSS transformで拡大 .mermaid svg { transform: scale(X); } を追加 右の注釈パネルが図を圧迫する 注釈を下部に移動 テンプレートAからBに切り替える この3つの対策は組み合わせられます。テンプレートBはキャンバス拡張 + CSS transform + 下部グリッドの3つ全部入りですね。制約はあるけど対策は全部見つかってるんで、複雑な図はまずBを試してみてください。 PNG変換 — 3つの手段 HTMLファイルが完成したら、PNG画像に変換してブログや設計書に貼れます。手段は3つあって、好みと環境に合わせて選んでください。 手段 自動化 セットアップ 向いてる用途 CLIツール(html-screenshot) できる Python + Playwright 量産やCI/CD Playwright MCP できる MCP設定 Claude Code連携 ブラウザで直接開く 手動 なし 手軽に確認 CLIツール uv run html-screenshot --file template-a.html --output template-a.png 以前の記事 で紹介したhtml-screenshotツールがそのまま使えます。改修なしでMermaid CDNの読み込みにも対応してました。Playwrightの wait_until="networkidle" がCDN読み込み完了を待ってくれるんで、Mermaid図の描画が終わってからスクリーンショットを撮ってくれます。 テンプレートBのように高さを変えたい場合は --height 1080 を付けます。 Playwright MCP Claude CodeからPlaywrightのブラウザ操作を直接呼び出す方法もあります。HTMLファイルを開いてスクリーンショットを撮る操作がMCPのツールで完結します。 Playwright MCPの記事 で詳しく書いてるんで、Claude Code使ってる人はこっちが楽かもしれません。 ブラウザで直接開く 一番手軽なのはHTMLファイルをブラウザで直接開くことですね。Mermaid CDNが読み込まれて図がそのまま描画されます。PNG変換は手動(ブラウザのDevToolsやOS標準のスクリーンショット)になりますけど、「ちょっと確認したい」くらいならこれで十分です。 まとめ ノード数の上限やサイズ問題はありましたけど、全部対処法が見つかりました。制約を把握しておけばハマることはないです。 今回のポイントです。 「構造はMermaid、装飾はTailwind」の役割分担 。Mermaidが得意な構造の自動レイアウト(矢印の接続、参加者の配置、分岐の描画)はMermaidに任せて、周りの装飾(注釈パネル、色統一、レイアウト)はTailwind CSSで自由に作る。 テンプレートの使い分けはシンプルです。 条件 テンプレート シンプルな図 + 注釈少なめ A: 右サイド注釈型(1280×720) 複雑な図 + 注釈多め B: 下部グリッド注釈型(1280×1080) あと、おまけとして1つ。このテンプレートで作った図はAI連携にも強いです。Mermaidのソースコードは宣言的な記法( sequenceDiagram 、 graph TD とか)なんで、AIが構造を正確に読み取れます。注釈パネルに設計意図を書いておけば、人間が見ても、AIが読んでもどっちにも伝わる。この辺の話は 仕様書の図にソースコードを添える設計パターン で詳しく書いてるんで、興味あれば読んでみてください。 もう1つ。このテンプレートを毎回コピペするのも手ですけど、Claude Codeの SKILL機能 にreference(参考例)として登録しておくと、「シーケンス図作って」と言うだけでテンプレートに沿った図が出てきます。テンプレートのクオリティがそのままSKILLのクオリティになるんで、良いテンプレートを貯める = 図解の品質を底上げする、という流れができます。コピペ用テンプレートとSKILL化、好きなほうで使ってください。 PlantUML編も書きました。Mermaidが苦手な図種(アクティビティ図、ユースケース図、コンポーネント図)をPlantUMLでカバーする話です。「 PlantUMLの表現力をTailwind CSSで設計書品質に仕上げる 」をどうぞ。 ほなまた〜 関連ブログ 今回の記事はシリーズの一部です。Mermaid図の生成からHTML図解、仕様書への活用まで、一連の流れで読めます。 ClaudeでMermaid図作成を自動化!プロンプトテンプレート集も公開 — 今回の出発点。Mermaid記法をAIで生成するところから始めたい人はここから。プロンプトテンプレートも載せてます。 図解作成、AIに丸投げしたら「たまに自分より上手い」件 — HTML+Tailwind CSSで図解を丸ごとAIに作らせる方法。フロー図以外の図解(比較図、アーキテクチャ図とか)はこっちが向いてます。 仕様書の図はAIに読ませるな — 軽量コードを添える設計パターン — 仕様書をAIと共有してるチーム向け。図にソースコードと設計意図を添えて、AIの「推測」をなくす方法です。 Playwright MCPで始めるブラウザ自動化 — PNG変換の手段の1つとして紹介したPlaywright MCP。Claude Codeからブラウザ操作を直接呼び出せます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Mermaidのデザインが物足りない?Tailwindで拡張して設計書品質に first appeared on SIOS Tech Lab .
はじめに 今日話題に事欠かない画像生成AI。Text-to-Imageモデルを使えば、利用者の入力内容に応じた画像を生成してくれます。 最初期は遊び程度の品質でしか出力できませんでしたが、令和8年現在では、職業作品とほと […]