こんにちは Dapr Advent Calendar 18日目です。気づけばあと2週間で年明けじゃないですか。歳を取ると1年が早くなるって言いますけども、それとは別に、この2年はやっぱり早かったですよねぇ。
k8sとDaprで分散トレーシング
さて、今回はk8s + Dapr上のアプリケーションの分散トレーシングを行います。主にDaprのパイプライン設定ファイルをk8sに適用するところや、それをアプリケーション側で有効にする辺りが、今回のポイントとなります。
アプリケーションの作成
動かすアプリケーションは Dapr Advent Calendar 8日目 - DaprとZipkinで分散トレーシング で作成したものです。そちらを先に読んでからこのエントリーを読んでください。
https://github.com/cero-t/dapr-advent-2021
また Dapr Advent Calendar 13日目 - Dapr + k8sでHello World で説明したツールなどがセットアップ済みであることを前提で説明を書いています。
作成したソースコードのおさらい
以前のエントリーで作成したアプリケーションのソースコードをおさらいします。
TracingController.java(抜粋)
public class TracingController { private RestTemplate restTemplate; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method") private String helloUrl; @Value("http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/publish-app/method") private String publishUrl; public TracingController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/invokeHello2") public Map<String, ?> invokeHello(@RequestHeader("traceparent") String traceparent) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("traceparent", traceparent); HttpEntity<?> request = new HttpEntity<>(httpHeaders); Map<?, ?> result = restTemplate.exchange(helloUrl + "/hello", HttpMethod.GET, request, Map.class).getBody(); return Map.of("baseUrl", helloUrl, "remoteMessage", result); } @PostMapping("/invokePublish") public void invokePublish(@RequestBody Object message, @RequestHeader("traceparent") String traceparent) { System.out.println(traceparent); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("traceparent", traceparent); HttpEntity<?> request = new HttpEntity<>(message, httpHeaders); restTemplate.exchange(publishUrl + "/publish2", HttpMethod.POST, request, Void.class); }
Hello Worldを呼ぶ /invokeHello2
と、メッセージのpublishを呼ぶ /invokePublish
というエンドポイントを提供しています。分散トレーシングに用いる traceparent
というHTTPヘッダを伝播させるために、少しだけ実装が複雑になっています。
アプリケーションのイメージ作成
続いて、このアプリケーションのイメージを作成しましょう。
イメージ作成先がminikubeのイメージレジストリになるよう次のコマンドを実行します。
eval $(minikube docker-env)
そしてイメージ作成のコマンドを実行します。
cd (GitHubのディレクトリパス)/dapr-advent-2021/tracing ../mvnw spring-boot:build-image
これでイメージが作成できました。
また、hello
publish
subscribe
のアプリケーションも必要となるため、それぞれイメージを作成します。
cd (GitHubのディレクトリパス)/dapr-advent-2021 ./mvnw spring-boot:build-image -pl hello,publish,subscribe
これでイメージ側の準備は完了です。
マニフェストファイルの作成
次に、このアプリケーションをデプロイするためのマニフェストファイルを作成します。
apiVersion: apps/v1 kind: Deployment metadata: name: tracing-app labels: app: tracing spec: replicas: 1 selector: matchLabels: app: tracing template: metadata: labels: app: tracing annotations: dapr.io/enabled: "true" dapr.io/app-id: "tracing-app" dapr.io/app-port: "8087" spec: containers: - name: tracing image: tracing:1.0.0 ports: - containerPort: 3500 imagePullPolicy: IfNotPresent --- kind: Service apiVersion: v1 metadata: name: tracing-svc labels: app: tracing spec: selector: app: tracing ports: - protocol: TCP port: 3500 targetPort: 3500 type: LoadBalancer
以前作成した hello.yaml
とほとんど同じ構成ですが、今回はアプリケーションのポートを外部公開するのではなく、Daprのポートを外部公開するため3500番ポートを公開する設定にしています。Daprを経由しなければ最初の traceparent
を取得できないため、このような構成にしています。
アプリケーションのポートを公開すべきか、Daprのポートを公開すべきかは、あとで論じたいと思います。
Zipkinのマニフェストファイルの作成
続いて、分散トレーシングの収集とUIを提供するZipkinをデプロイするためのマニフェストファイルを作成します。
apiVersion: apps/v1 kind: Deployment metadata: name: zipkin-deploy labels: app: zipkin spec: replicas: 1 selector: matchLabels: app: zipkin template: metadata: labels: app: zipkin spec: containers: - name: zipkin image: openzipkin/zipkin imagePullPolicy: IfNotPresent ports: - containerPort: 9411 --- kind: Service apiVersion: v1 metadata: name: zipkin-svc labels: app: zipkin spec: type: LoadBalancer selector: app: zipkin ports: - protocol: TCP port: 9411 targetPort: 9411
イメージを使ってZipkinをデプロイしています。内容は以前作成したPostgreSQL用のものとほとんど同じです。外部から9411番ポートにアクセスできるようServiceも作成しています。
Daprのパイプライン設定ファイルでZipkinへのトレースを指定する
続いて、Daprのパイプライン設定ファイルを作成して、Zipkinにトレーシング情報を送るようにします。
apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: tracing spec: tracing: samplingRate: "1" zipkin: endpointAddress: http://zipkin-svc:9411/api/v2/spans
これは Dapr Advent Calendar 8日目 - DaprとZipkinで分散トレーシング で説明した ~/.dapr/config.yaml
とほとんど同じ内容で、接続先だけが localhost
ではなく zipkin-svc
を参照するように修正しています。
samplingRate
には 1
を設定して、すべてのトレーシング情報をZipkinに送るようにしています。
またこの設定には metadata.name
で tracing
という名前をつけています。
アプリケーション側で分散トレーシングを有効にする
さて、上でHTTPのパイプライン設定ファイルを作成しましたが、このパイプライン設定がすべてのアプリケーションで有効になる、というわけではありません。Daprでは設定ファイルをk8sにapplyすると、わりと自動で全体的に有効化されることが多かったのですが、パイプラインのConfigurationに関しては勝手に全体で有効化されません。全体に有効化すると弊害が大きいというか、アプリケーションによってパイプライン設定が異なるのが当たり前ですからね。
そのため、アプリケーション側がこのパイプライン設定を使うように設定する必要があります。
アプリケーション側のマニフェストファイルを次のように修正します。
annotations: dapr.io/enabled: "true" dapr.io/app-id: "tracing-app" dapr.io/app-port: "8087"
これを次のように変更します。
annotations: dapr.io/enabled: "true" dapr.io/app-id: "tracing-app" dapr.io/app-port: "8087" dapr.io/config: "tracing"
dapr.io/config
に tracing
を指定しています。この tracing
というのは上のパイプライン設定ファイルの metadata.name
で指定した名前です。これで、アプリケーション側がパイプライン設定を利用するようになり、分散トレーシングが有効になるわけです。
同じように k8s/hello-app.yaml
k8s/publish-app.yaml
k8s/subscribe-app.yaml
にも dapr.io/config: "tracing"
を追加してください。
これでマニフェストファイルの作成は完了です。
アプリケーションをデプロイしてアクセスする
イメージをk8sにデプロイ
それでは作成したイメージをk8sにデプロイします。
cd (GitHubのディレクトリパス)/dapr-advent-2021 kubectl apply -f k8s/zipkin.yaml kubectl apply -f k8s/tracing.yaml kubectl apply -f k8s/tracing-app.yaml kubectl apply -f k8s/hello-app.yaml kubectl apply -f k8s/publish-app.yaml kubectl apply -f k8s/subscribe-app.yaml
まずZipkinをデプロイし、HTTPパイプライン設定ファイルをデプロイし、アプリケーションのデプロイという順番に行っています。
RabbitMQなどをデプロイしていない場合には、そちらもデプロイする必要があります。面倒なので次のコマンドでまとめてやってしまったほうが良いでしょう。
kubectl apply -f k8s
これでk8sフォルダにあるすべてのマニフェストファイルに対して kubectl apply
が行われます。
k8s上のアプリケーションにアクセスする
それでは、起動したアプリケーションにアクセスしてみましょう。
ポートフォワードの設定
まずはローカルPCからminikube内のpodにアクセスできるよう、ポートフォワードの設定を行います。
次のコマンドを実行します。
kubectl port-forward service/zipkin-svc 9411:9411
これでローカルPCの9411番ポート経由でZipkinにアクセスできるようになりました。
また、別のコンソールで次のコマンドを実行します。
kubectl port-forward service/tracing-app 3500:3500
これでローカルPCの3500番ポート経由で、tracing-appのDaprにアクセスできるようになりました。
Hello Worldを呼び出す処理にアクセスする
それでは、別のコンソールからアクセスしてみましょう。
curl localhost:3500/invokeHello2 -H "dapr-app-id:tracing-app"
せっかくなのでDapr 1.4から使えるようになった方法でアクセスしてみました。これは curl localhost:3500/v1.0/invoke/tracing-app/method/invokeHello2
と同じ意味になります。
次のような結果が表示されます。
{ "baseUrl": "http://localhost:3500/v1.0/invoke/hello-app/method", "remoteMessage": { "message": "Hello, world!" } }
正しくメッセージが返ってきました。
Zipkin側でトレース情報を確認してみましょう。
トレーシング情報は、上から順番に
- Dapr → tracing-app (/invokeHello2)
- tracing-app → Dapr (Invoke APIでhello-appの /hello)
- Dapr → hello-app (/hello)
となっています。Daprを通過する部分で分散トレーシングの情報が取得されていることが分かります。
Pub/subを呼び出す処理にアクセスする
続いて、Pub/subを行うエンドポイントにもアクセスしてみましょう。
curl -XPOST "localhost:3500/invokePublish" -H "dapr-app-id:tracing-app" -H "Content-type:application/json" -d '{ "name": "Shin Tanimoto", "twitter": "@cero_t" }'
特にエラーなどが起きなければメッセージングは成功しているはずです。
Zipkin側でトレース情報を確認してみましょう。
トレーシング情報は、上から順番に
- Dapr → tracing-app (/invokePublish)
- tracing-app → Dapr (Invoke APIでpublish-appの /publish2)
- Dapr → publish-app (/publish2)
- publish-app → Dapr (Pub/sub APIでrabbitmq-pubsubのmy-message)
- (Dapr -> RabbitMQ -> Dapr)
- Dapr → subscribe-app (my-messageの受信)
となっており、やはりDaprを通過する部分で分散トレーシングの情報が取得されています。
RabbitMQにエンキュー、デキューするところ自体は分散トレーシングの対象外になっています。本当はここも表示されれば嬉しいんですけどね。
いずれにせよ、k8s上でも問題なく分散トレーシングができることが分かりました。
外部からアプリケーションにアクセスすべきか、Daprにアクセスすべきか
今回はアプリケーションのポートではなく、Daprのポートに外部からアクセスする形としました。実際に運用する際、アプリケーション側を外部に公開すべきか、Dapr側を公開すべきか、どちらが良いのか改めて考えてみましょう。
基本的にはアプリケーション側のポートを外部公開するべきだと思いますし、実際に僕はそのように運用しています。
ただ、Dapr側を公開することで、次のようなメリット/デメリットがあります。
- メリット
- デメリット
- Daprが管理するすべてのアプリケーションやステートストア、メッセージング、シークレットストアにアクセスされてしまう危険性がある
デメリットが大きすぎるため、原則としてアプリケーション側のポートを外部公開すべきなのです。
ただ今回の例のように -H "dapr-app-id:app-name"
を指定することで、任意のアプリケーションのDaprにのみアクセスさせることは可能です。k8sの前段にあるロードバランサなどでヘッダを固定し、仮に元のリクエストに dapr-app-id
が含まれていてもそれを確実に無視するような仕組みが利用できるのであれば、daprのポートを安全に外部公開することも可能でしょう。そうすれば、認可や分散トレーシングなどの機能をうまく活用することができます。
実は僕もいま実運用しているシステムで、ちょうど同じ課題に直面しており、外部からのリクエストを分散トレーシングに含めるために、Daprのポートを外部公開するか、それともアプリケーション側のポートを外部公開して、アプリケーション側で traceparent
を発行するのか、あるいはk8sまでのどこかで traceparent
を発行するのかを迷っているところです。
僕自身は「迷ったときは安全側に倒したほうが良い」と考えているので、アプリケーション側のポートを公開し、一方で多少は雑に扱っても良い traceparent
はクライアント側で発行するとか、k8sの前段のどこかで発行する、くらいが良いのではないかと考えています。
この辺りは様々なポリシーがあるでしょうから、Daprの識者たちともディスカッションしてみたいですね。
まとめ
- Dapr + k8sの環境でも問題なく分散トレーシングができました
- アプリケーション側の
metadata.annotations
で利用するHTTPパイプラインを指定する必要があります - 外部公開するのは、アプリケーション側のポートか、Dapr側のポートか、慎重に検討する必要があります
だいぶDaprとk8sの運用に慣れてきて、構成も考えられるようになってきましたね。
これでいったんDapr + k8sの機能を紹介するのはおしまいにして、明日はAWS上にデプロイする方法を説明したいと思います。
それでは!