Dapr Advent Calendar 14日目 - Dapr + k8sでサービスを呼ぶ
こんにちは Dapr Advent Calendar 14日目です。皆さん、Log4J 2対応のほうは大丈夫ですか?
k8sでDaprのアプリケーション同士を連携
今回はk8sの上で2つのアプリケーションを動かし、Daprを経由してそれらを連携させてみます。
アプリケーションの作成
今回動かすアプリケーションは Dapr Advent Calendar 3日目 - Daprでサービスを呼ぶ で作成したものなので、そちらを先に読んでからこのエントリーを読んでください。
https://github.com/cero-t/dapr-advent-2021
また Dapr Advent Calendar 13日目 - Dapr + k8sでHello World で説明したツールなどがセットアップ済みであることを前提で説明を書いています。
作成したソースコードのおさらい
以前のエントリーで作成したアプリケーションのソースコードをおさらいしておきましょう。
まずは呼び出される側のhelloアプリケーションです。
(hello) HelloController.java
@RestController public class HelloController { @GetMapping("/hello") public Map<String, String> hello() { return Map.of("message", "Hello, world!"); } }
/hello
にアクセスすると Hello, world!
を含んだJSONを返すだけの処理です。このアプリケーションはポート8080番で起動します。
これを呼び出すinvokeアプリケーションのコードは次のようになっています。
(invoke) InvokeController.java
@RestController public class InvokeController { private RestTemplate restTemplate; @Value("${baseUrl}") private String baseUrl; public InvokeController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @GetMapping("/invokeHello") public Map<String, ?> invokeHello() { Map<?, ?> result = restTemplate.getForObject(baseUrl + "/hello", Map.class); return Map.of("baseUrl", baseUrl, "remoteMessage", result); } }
helloアプリケーションの /hello
を呼び出すだけの簡単な処理です。
また、設定ファイルは次のようになっています。
(invoke) application.properties
server.port=8081 baseUrl=http://localhost:8080
このアプリケーションはポート8081番で起動します。
また baseUrl
は次のファイルで上書きしています。
(invoke) application-dapr.properties
baseUrl=http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
Springのプロファイルが dapr
の時は baseUrl
の値として http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
が使われるようになっています。
この DAPR_HTTP_PORT
は、k8s上のDaprでは 3500
が環境変数で渡されるようになっています。
アプリケーションのイメージ作成
それでは、invokeアプリケーションのイメージを作成しましょう。
イメージ作成先がminikubeのイメージレジストリになるよう次のコマンドを実行します。
eval $(minikube docker-env)
そして、イメージを作成するために次のコマンドを実行します。
cd (GitHubのディレクトリパス)/dapr-advent-2021/invoke ../mvnw spring-boot:build-image
これでイメージの作成は完了です。
もし hello
のイメージを作成していなければ、同じように hello
ディレクトリで ../mvnw spring-boot:build-image
コマンドを実行してください。
マニフェストファイルの作成
それでは、これらのアプリケーションをデプロイするためのマニフェストファイルを作成します。
helloアプリケーションのマニフェストファイル
hello側のマニフェストファイルは前回作成したものと同じです。
apiVersion: apps/v1 kind: Deployment metadata: name: hello-app labels: app: hello spec: replicas: 1 selector: matchLabels: app: hello template: metadata: labels: app: hello annotations: dapr.io/enabled: "true" dapr.io/app-id: "hello-app" dapr.io/app-port: "8080" spec: containers: - name: hello image: hello:1.0.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 --- kind: Service apiVersion: v1 metadata: name: hello-svc labels: app: hello spec: selector: app: hello ports: - protocol: TCP port: 8080 targetPort: 8080 type: LoadBalancer
今回は hello-svc
は使わないので削除しても構いませんが、念のため残しておきます。
invokeアプリケーションのマニフェストファイル
つづいて、invokeアプリケーションをデプロイするためのマニフェストファイルを作成します。
apiVersion: apps/v1 kind: Deployment metadata: name: invoke-app labels: app: invoke spec: replicas: 1 selector: matchLabels: app: invoke template: metadata: labels: app: invoke annotations: dapr.io/enabled: "true" dapr.io/app-id: "invoke-app" dapr.io/app-port: "8081" spec: containers: - name: invoke image: invoke:1.0.0 ports: - containerPort: 8081 imagePullPolicy: IfNotPresent env: - name: spring.profiles.active value: dapr --- kind: Service apiVersion: v1 metadata: name: invoke-svc labels: app: invoke spec: selector: app: invoke ports: - protocol: TCP port: 8081 targetPort: 8081 type: LoadBalancer
基本的には hello-app.yaml
の hello
を invoke
に変え、8080
を 8081
にしただけです。
ただ一点だけ spec.template.spec.containers.env
に環境変数に関する設定を追加しています。
- name: spring.profiles.active value: dapr
Springのプロファイルに dapr
を指定し、application-dapr.yaml
が利用されるようにしています。これで baseUrl
の値が http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
になります。
別の方法として、次のように指定することもできます。
- name: baseUrl value: http://localhost:${DAPR_HTTP_PORT}/v1.0/invoke/hello-app/method
Spring Bootは環境変数でパラメータを上書きできるため、このように baseUrl
の値を直接指定しても構いません。Dapr関連の設定をk8sのマニフェストに任せるという方針の場合は、このような設定を使うほうが良いでしょうね。
アプリケーションをデプロイしてアクセスする
イメージをk8sにデプロイ
それでは作成したイメージをk8sにデプロイします。
cd (GitHubのディレクトリパス)/dapr-advent-2021 kubectl apply -f k8s/invoke-app.yaml
次のように表示されるはずです。
deployment.apps/invoke-app created service/invoke-svc created
これでinvokeアプリケーションがk8s上にデプロイされました。
もしhelloアプリケーションをデプロイしていなければ kubectl apply -f k8s/hello-app.yaml
でデプロイしてください。
起動したpodを次のコマンドで確認しましょう。
kubectl get pods
次のように表示されるはずです。
NAME READY STATUS RESTARTS AGE hello-app-fb644cd5d-tsxmh 2/2 Running 0 3h8m invoke-app-745b5f4bf6-chj5w 2/2 Running 0 65m
hello-app
と invoke-app
の2つのpodが実行中であることが分かります。
k8s上のアプリケーションにアクセスする
それでは、起動したアプリケーションにアクセスしてみましょう。
次のコマンドを実行します。
kubectl port-forward service/invoke-svc 8081:8081
次のように表示されます。
Forwarding from 127.0.0.1:8081 -> 8081 Forwarding from [::1]:8081 -> 8081
これでローカルPCの8081番ポートから、minikubeの8081番ポートにアクセスできるようになりました。
それでは、別のコンソールからアクセスしてみましょう。
curl localhost:8081/invokeHello
次のメッセージが取得できるはずです。
{ "baseUrl": "http://localhost:3500/v1.0/invoke/hello-app/method", "remoteMessage": { "message": "Hello, world!" } }
baseUrl
がポート3500番のDaprにアクセスするURLになっており、helloアプリケーションが返すメッセージも取得できました。
k8s上で作成した2つのpodが、DaprのInvoke APIを経由してアクセスしていることが分かりました。
なぜ繋がるのか?
どうやってDaprを経由して別のアプリケーションを呼び出すことができたのでしょうか。それに関するドキュメントはこれです。
この画像の「Name resolution component」としてk8sのDNSが利用されているのです。ローカル環境のmDNSにせよk8sのDNSにせよ、特に別のDNSなどを使わなくても別アプリケーション呼び出しができるのは、とても楽ですね。
ログを確認する
ところで、これまでコンソールに出ていたログはどこに出ているのでしょうか。k8sではログを見るコマンドがあるので、そちらを使ってログを確認することができます。
まずはpodの一覧を取得します。
kubectl get pods
NAME READY STATUS RESTARTS AGE hello-app-fb644cd5d-tsxmh 2/2 Running 0 3h26m invoke-app-745b5f4bf6-chj5w 2/2 Running 0 83m
このうち、hello-appのログは、次のコマンドで見ることができます。
kubectl logs hello-app-fb644cd5d-tsxmh hello
次のようなログが表示されるはずです。
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.6.1) 2021-12-12 06:23:28.001 INFO 1 --- [ main] n.c.e.d.advent.hello.HelloApplication : Starting HelloApplication v1.0.0 using Java 11.0.13 on hello-app-fb644cd5d-tsxmh with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace) 2021-12-12 06:23:28.004 INFO 1 --- [ main] n.c.e.d.advent.hello.HelloApplication : No active profile set, falling back to default profiles: default 2021-12-12 06:23:29.986 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-12-12 06:23:29.996 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-12-12 06:23:29.997 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55] 2021-12-12 06:23:30.096 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-12-12 06:23:30.096 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1925 ms 2021-12-12 06:23:30.681 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-12-12 06:23:30.702 INFO 1 --- [ main] n.c.e.d.advent.hello.HelloApplication : Started HelloApplication in 3.316 seconds (JVM running for 3.826) 2021-12-12 06:23:30.891 INFO 1 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2021-12-12 06:23:30.892 INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2021-12-12 06:23:30.893 INFO 1 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
Spring Bootアプリケーションのログが出ていますね。
また、helloアプリケーションのDapr側のログは次のコマンドで見ることができます。
kubectl logs hello-app-fb644cd5d-tsxmh daprd
time="2021-12-12T09:54:35.62529075Z" level=info msg="starting Dapr Runtime -- version 1.5.1 -- commit c6daae8e9b11b3e241a9cb84c33e5aa740d74368" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:35.625323485Z" level=info msg="log level set to: info" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:35.625564411Z" level=info msg="metrics server started on :9090/" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.metrics type=log ver=1.5.1 time="2021-12-12T09:54:35.625927922Z" level=info msg="loading default configuration" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:35.625951378Z" level=info msg="kubernetes mode configured" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:35.625958635Z" level=info msg="app id: hello-app" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 (略) time="2021-12-12T09:54:35.721210634Z" level=info msg="application protocol: http. waiting on port 8080. This will block until the app is listening on that port." app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:35.721505377Z" level=info msg="starting workload cert expiry watcher. current cert expires on: 2021-12-13 10:09:35 +0000 UTC" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime.grpc.internal type=log ver=1.5.1 time="2021-12-12T09:54:43.551406906Z" level=info msg="application discovered on port 8080" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:44.133351345Z" level=info msg="application configuration loaded" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:44.133506244Z" level=info msg="actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime.actor type=log ver=1.5.1 time="2021-12-12T09:54:44.13353591Z" level=info msg="dapr initialized. Status: Running. Init Elapsed 8507.587964ms" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime type=log ver=1.5.1 time="2021-12-12T09:54:44.222137967Z" level=info msg="placement tables updated, version: 0" app_id=hello-app instance=hello-app-fb644cd5d-tsxmh scope=dapr.runtime.actor.internal.placement type=log ver=1.5.1
Dapr側のログも確認ができました。
アプリケーションの起動に失敗したり、うまく繋がらなかったり、例外が起きてしまったりした時には、前回説明した kubectl describe pods
コマンドや、この kubectl logs
コマンドで詳細を確認することで状況を把握することができるでしょう。
まとめ
- k8s上にDaprアプリケーションを複数デプロイすれば、Invoke APIを使って相手のapp-idを指定してアクセスすることができます
- k8s環境では
DAPR_HTTP_PORT
は3500番で固定になります - k8s環境ではk8sのDNSが利用されます
- ログは
kubectl logs
コマンドで確認することができます
だいぶk8sにも慣れてきましたかね!? それでは、また明日!