近年、ソフトウェア開発におけるObservability(観測性)の重要性が一層高まっていますよね(肌感)。
私個人の開発としても、HTTPリクエストのデバッグにトレースが活用しています。トレースを使うことで、エラーが発生した具体的な箇所が明確になり、ログを頼りに推測する手間が省けます。
また、ユーザーから「エラーが出た」と報告を受けた際、フロントエンドで表示されるtraceIDを教えてもらうことで、迅速にバグ修正に取り掛かることが可能です。
しかし、トレースを見るためには専用の環境が必要です。トレースに依存しすぎてログの出力を減らしてしまうと、ローカル開発環境では何が起こっているのか把握しづらくなるという問題もあります。
そこで最近、私個人なりに今回はDocker Composeを用いてGrafana、Prometheus、Loki、Tempoをローカル環境で立ち上げ、本番環境と同じダッシュボードを使える開発環境をぼちぼち整えていたのですが、一通りまとまったかなといったところなのでサンプルとしてまとめ、公開・解説してみようと思います。
あくまでも私個人が試行錯誤した結果なので、もっと便利な方法があるかもしれません。一例として受け取っていただければ幸いです。
フォルダ構成をガチガチにしてもよかったのですが、今回はテンプレートではなくサンプルということでベタっと置いてあります。
サンプルプロジェクト
こちらで公開しています。
https://github.com/totegamma/go-lgtm-sample
最初に、dockerにlokiプラグインをインストールする必要があります。
|
|
このプラグインを使うこととで、標準出力のログをlokiに送信してくれます。k8sにデプロイするアプリケーションでは通常ログを標準出力に出すことが多いと思うので、必須のプラグインですね。
このプラグインがインストールできたら、docker compose up
コマンドで立ち上げます。
立ち上がれば、localhost:3000
でgrafanaが見れるようになるはずです。
サンプルとして組み込みのダッシュボードbasic
が追加してあります。このようによく使うダッシュボードのjsonを組み込んでおくと便利ですね。
サンプルのアプリケーションとして、次の2つのAPIが実装してあります
GET: /wait
ランダムな秒数遅延するだけのAPIPOST: /calc
四則演算の計算木となるjsonを受け取り、計算結果を返す謎のAPI
以下に、本サンプルの見どころをざっくりとまとめています。それぞれの見出し間はできるだけ独立させているので、気になるところだけ読んでみてください。
trace活用ハンズオン
/calc API
にリクエストを送って、1+1
を計算させてみます。このとき、iオプションをつけてレスポンスヘッダも表示するようにしておきます。
|
|
result: 2
で正しく計算できていることはどうでもよくて、今回の主役はレスポンスヘッダのTrace-Id: 5fbd6acdef6ab11d727d9f0769fc3bec
です。
grafanaのexplorerビューでこれを検索してみましょう。
このように、特定のAPIリクエストのトレースを素早く検査することができました。
次に、エラーになるリクエストも送ってみましょう。ゼロ除算が起こる計算木を渡してみます。
|
|
400が帰ってきてますね。 grafanaで見てみると、次のようなトレースになっており、2回呼ばれているcalcのうち、上位にあるものがエラーになっていることが素早くわかります。
また、attributesを見てみると、A=30
, B=0
, operation="/"
となっており、たしかに0除算がここで発生していることが確信できます。
ログの確認
explorerビューでDatasourceとしてlokiを指定し、compose_service
ラベルにログを見たいdocker composeのサービス名を指定することで、そのサービスのログを確認できます。
今回はappコンテナしかありませんが、マイクロサービスの開発などで複数サービスがある場合、このクエリを切り替えつつログを確認することができます。
トレースIDをHTTPレスポンスヘッダーに含める
echoのミドルウェアで実装します。otelechoのセットアップより後に挿入しないとtraceIDが取れないのでそこだけ注意です。
|
|
また、多くの場合webフロントエンドでtraceIDを受け取るために、corsの設定が必要になるかと思います。
|
|
アプリコンテナのホットリロード
アプリケーションはコンテナの内部で動かすようにしてみました。
また、内部ではairを使っていて、ソースを書き換えると自動で再コンパイル・再起動が行われるようになっています。
別にコンテナにいれる必要はなくて、手元でそのままairを実行してもよかったのですが、今回のcomposeの方針としてできるだけポートを外部にさらしすぎないようにしていて、tempoやprometheusに外部からアクセスできないためコンテナ内部に閉じ込めるようにしてみました。
Grafanaのセットアップ
いちいちログインするのは大変面倒なので、環境変数で認証無しでログインできるように設定しています。
|
|
また、データソース・ダッシュボードもあらかじめ設定ファイルをyamlで生成し、読み込むように設定しておくことで、立ち上げてすぐいつものダッシュボードを開くことができます。
Logs to Trace, Trace to Logs
トレースとログのエントリーに、対応するログ・トレースにジャンプするボタンが追加されています。
詳細は過去記事slogでログにTraceIDを出す とGoプログラムのトレースを取ってGrafanaCloudで可視化するを参照してください。
今回の新しい点としては、過去記事でGrafanaのwebUIで行っていた一部の設定をyamlで記載する必要があります。_dev/grafana-datasources.yaml
にjsonDataとしてtraceToLogsV2及びderivedFieldsの設定が行われているので、そこを確認してみてください。
テストでのトレースの確認
ローカル開発環境でも便利にトレースが見れるようにはなりましたが、問題になるのはテストです。テストでも、呼び出し先の関数で何が起こったのかを知りたいですよね。
本サンプルでは、ダミーのトレースプロバイダーと、簡易的なトレースの表示関数を用意してみました。go test .
を実行すると以下が表示されるはずです(あえて失敗するテストにしてあります)。
|
|
トレース内部のAttributes, Eventsがプリントされています。 これで、関数呼び出しのどこでエラーが発生したのか、その時のパラメーターが何だったのかが、簡単に追跡できます。
関連関数はinternal/testutil/dummyTracer.go
に記載してあります。
負荷試験との組み合わせ
負荷試験ツールであるk6のコンテナをcomposeに含めており、簡単に負荷試験が実施できるようになっています。
サンプルでの負荷試験のシナリオの格納場所は_dev/k6
です。
とはいえローカルでグラフを見て性能を測ることはいうてないとは思いますが、負荷試験のシナリオを設計する際は便利なのではないのでしょうか。
|
|
k6からのメトリクスやログもprometheus/lokiに送信するように環境変数が設定されており、実行するとbasicダッシュボードで確認できます。
おわりに
本記事では私の個人的なローカル開発環境をいくらかサンプルという形でまとめて公開し、いくつかの項目について解説しました。
個人的には何かとGrafanaのデータソースやダッシュボード周りをyamlで定義しておくのが地味にめんどくさかったので、こうしてplug and playで動くサンプルというのは何かと便利なんじゃないかな~とは思っています。ご活用いただければ幸いです。
みなさんも「これ便利だよ!」みたいなのがあればぜひぜひ教えていただけると嬉しいです。