Spring Boot 3.0でMicrometer対応が強化されたとか、トレース情報を収集するSpring Cloud SleuthがMicrometerに入ったと聞き、この辺りはしばらく追いかけてなかったので、この機会にまとめて学び直すことにしました。
今回作ったものはGitHubに置いてあります。 github.com もう2023年になってしまいましたが、作り始めたのが2022年なので spring-store-2022 となっています。
イキってREADMEを英語で書く習慣がついてしまったので、代わりにこのブログで日本語の説明を書きたいと思います。工夫したところやハマったポイントなどの話はまた別途ブログを書くとして、今回はこのアプリケーションの概要と動かすところまで説明します。
1. ゴール
マイクロサービスのアプリケーションをこんな風に可視化するところがゴールです。
2. サンプルアプリケーションの概要
サンプルアプリケーションは、いわゆるECサイト的なマイクロサービスを想定したものです。商品一覧を見る、カートに入れる、購入するなどができ、購入すると非同期で手配やクレジットカードの決済が行われるという動きを模倣しています。
サービスの構成は次の図の通りです。
なお今回作るのはサーバサイドのみです。フロントエンドは2019年にvueで作ったものを再利用したかったのですが、残念ながらすでにコンパイルが通らない状況になってしまっていたので諦めてしまいました。誰か助けて。
利用する技術スタック
- Java 17
- Spring Boot 3.0 (without Spring Cloud)
- Grafana、Loki、Tempo、Prometheus、Promtail
- Kubernetes(k8sがなくても動く)
可視化はGrafanaスタックのみでメトリクス、ログ、トレースを見られるようになったので、これに一本化することにしました。
また令和のマイクロサービスアプリケーションは一般的にコンテナとしてデプロイされるものであり *1 もはやデファクトスタンダードな基礎技術となっているk8s *2 にもデプロイできるようにしておきました。
3. アプリケーションをローカルで実行する
ローカル環境ではDockerでミドルウェアを立ち上げ、各マイクロサービスアプリケーションをIDEから起動する形で実行します。
(0) 必要なもの
ソースコードは https://github.com/cero-t/spring-store-2022 からダウンロードするなりcloneするなりしてください。
(1) ミドルウェア(RabbitMQとGrafanaスタック)の起動
spring-store-2022/docker
ディレクトリに移動します
cd docker
docker-composeでミドルウェアを起動します
docker-compose up -d
(2) アプリケーションの起動
IDEを使ってアプリケーションを起動してください。
Mavenの spring-boot:run
コマンドはプロジェクトの構成上、使えません。僕これSpring Maven Pluginのバグだと思ってるんですけどね。
(3) アプリケーションへのアクセス
BFFでSwagger UIを使えるようにしているため、これを利用します。
http://localhost:9000/swagger-ui.html
- catalog-controller: GET
/catalog
- 商品一覧の名前や値段、画像などを取得できます
- cart-controller: POST
/cart
- カートを作成して
cartId
を取得できます
- カートを作成して
- cart-controller: POST
/cart/{cartId}
- カートに商品を追加します
- 追加する
itemId
は/catalog
で取得できる商品のIDを指定してください
- cart-controller: GET
/cart/{cartId}
- カートに入った商品の詳細と合計金額を確認できます
- order-controller: POST
/order
- 商品を購入できます。と言ってもメールが送られたりクレジットカードの決済が発生することはありません
cardExpire
はMM/yy
形式で年月を入れてくださいcartId
は POST/cart
で取得したIDを取得してください
/order
にPOSTするボディの例
{ "name": "Shin Tanimoto", "address": "Tokyo", "telephone": "0123456789", "mailAddress": "hello@example.com", "cardNumber": "0000111122223333", "cardExpire": "12/24", "cardName": "Shin Tanimoto", "cartId": "1" }
これでエラーが出なければ、アプリケーションの操作は完了です。
(4) Grafanaのダッシュボードを確認
アプリケーションがどう動いたのかをGrafanaで確認します。
左メニューのコンパスのアイコン(Explore)をクリックします
上のドロップダウンリストから Loki を選択します
Label filters に app
= bff
を指定して右上の Run query ボタンを押します。アプリからLokiにログが送られるまで1分ほど掛かるので、選択できない場合やログが出ない場合は少し待ってから実行してください。
おそらく一番上にある CHECKOUT が含まれるログをクリックします
TraceID のところに Tempo のボタンがあるのでこれをクリックします
トレースが表示されましたね!
これで分散トレーシングの情報を可視化することができました。他にもPrometheusを使ったメトリクスの可視化などもできますが、説明はいったん割愛します。
(5) アプリケーションを終了させる
それぞれのアプリケーションをIDEで停止させてください
続いて、ミドルウェアを停止させます
docker-compose down
これで環境が綺麗になりました。
4. アプリケーションをKubernetes上で実行する
続いて、k8s上でも実行してみましょう。アプリケーションや設定ファイルは何も変更することなく、k8sのマニフェストで環境変数を使って設定を上書きする形で起動します。
k8sにデプロイする際に application-k8s.properties
などを別に作ってプロファイルを切り替える方法のもよく使われていと思いますが、僕としては「アプリケーションが特定のインフラを意識しない」というか、「k8s → アプリケーションの参照は良いけど、アプリケーション → k8sに相互参照させない」ということを意識しているので、k8sのマニフェストで上書きすることにしています。
(0) 必要なもの
- Java 17以上
- Kubernetes環境(minikubeやDocker Desktopのkubernetes機能など)
- kubectl
- Helm
(1) k8sを起動する(minikubeの場合のみ。Docker Desktopの場合はk8sを有効にするだけでOK)
minikubeを十分なリソースで起動します
minikube start --cpus=4 --memory=4096
環境変数を設定して、minikubeのイメージリポジトリが利用されるようにします
eval $(minikube docker-env)
(2) アプリケーションのイメージをビルドする
spring-store-2022/services
ディレクトリに移動します
cd services
アプリケーションイメージを作成します
sh build-image.sh
(3) k8sにミドルウェアをインストールする
spring-store-2022/k8s
ディレクトリに移動します
cd k8s
ネームスペースを作成します(必須ではありません。他のコンテナなどもk8s上に置いていて環境を分けたい場合のみ作成してください)
kubectl create ns spring-store-2022
Helmのリポジトリを追加します
helm repo add grafana https://grafana.github.io/helm-charts
Tempoをインストールします(ネームスペースを使う場合は -n spring-store-2022
を引数に追加してください)
helm install tempo grafana/tempo -f helm/tempo-config.yml
loki-stackをインストールします。これはGrafana、Prometheus、Loki、Promtailをまとめてインストールできるものです(ネームスペースを使う場合は -n spring-store-2022
を引数に追加してください)
helm install loki-stack grafana/loki-stack -f helm/loki-stack-config.yml
Lokiは起動に1〜2分くらい掛かるようで、それまではコネクションエラーなどが起きることがありますが、しばらく待てば正常に動き始めます。
(4) アプリケーションをk8sにデプロイする
アプリケーションをデプロイします(何度も言いますが、ネームスペースを使う場合は -n spring-store-2022
を引数に追加してください)
kubectl apply -f ./deploy
これでまとめてアプリケーションとRabbitMQがk8s上にデプロイされます。
(5) ポートフォワードを設定する
ローカル環境からアクセスできるよう、ポートフォワードを設定します。ネームスペースを使っている場合は -n spring-store-2022
を引数に追加してください。
Grafanaへのポートフォワードを設定します
kubectl port-forward svc/loki-stack-grafana 3000:80
minikubeを使っている場合は、BFFアプリケーションにポートフォワードを設定します。Docker Desktopの場合はこの手順をスキップしてもアクセスできるようになっています。
kubectl port-forward svc/bff-svc 9000:9000
これでk8s上でもローカル環境と変わらずアプリケーションを利用でき、モニタリングされていることを確認できるはずです。
アプリケーションの使い方やGrafanaの使い方は上で説明した通りですので割愛します。
(6) アプリケーションを終了させる
アプリケーションをk8sから削除します
kubectl delete -f ./deploy
ミドルウェアをアンインストールします
helm uninstall loki-stack
helm uninstall tempo
ネームスペースを作成していた場合はネームスペースを削除します
kubectl delete ns spring-store-2022
これで環境が綺麗になりました。
5. 所感
Grafanaでメトリクス、ログ、トレースのすべての可視化ができるようになっており、またSpring Bootからも簡単に情報が送れるようになっていることはなかなか体験が良く、今後はこれを自分の中での標準技術にしていきたいという気持ちです。
ただ今回、Spring BootのMicrometer対応や、Grafanaスタック、あとHelmも初めて使ってみたわけですが、思った以上に細かいところでハマってしまい、トータル2週間くらい格闘し続けた形になりました。
特にGrafanaスタックはアップデートが早くてHelm Chartがそれに追従していないとか、Helm Chartの設定に問題があって回避しなければいけないなどがあり、「コマンド一発で動くようになる」という状況ではありませんでした。デバッグに近いことをやり続けたおかげで、ずいぶんと知識はついたのですが、良くも悪くもbreaking changeが発生するくらい活発に開発中ということなんですね。
冒頭にも書きましたが、その辺りの細かい話や工夫したところは、また改めてブログを書きたいと思います。
(余談)過去に作っていたアプリケーション
ちなみにこれまで同じアプリケーションをJava 8や11でも作っていました。
2016年バージョン(Java 8 + Spring Boot 1.x + Spring Cloud Stream、Eureka、Hystrix、Sleuth + Zipkin) github.com
2019年バージョン(Vue.js + Java 11 + Spring Boot 2.x + Spring Cloud Stream、Eureka、Sleuth + Elasticsearch + Kibana + Elastic APM + Zipkin) github.com
その当時に僕が興味を持っていた技術がそのまんまスタックに現れてくるのがちょっと面白いですね。昨年、Dapr版を作ればよかったですね。
参考サイト
今回のアプリケーションを作成するに辺り、Grzejszczakさんのブログエントリーを参考にさせていただきました。 spring.io
GrzejszczakさんはSpringのObservability関連のプロダクトを開発されている方です。今後も活動を応援したいと思います!