谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

Dapr Advent Calendar 14日目 - Dapr + k8sでサービスを呼ぶ

こんにちは Dapr Advent Calendar 14日目です。皆さん、Log4J 2対応のほうは大丈夫ですか?

k8sでDaprのアプリケーション同士を連携

今回はk8sの上で2つのアプリケーションを動かし、Daprを経由してそれらを連携させてみます。

f:id:cero-t:20211213215620p:plain
今回作るアプリケーション

アプリケーションの作成

今回動かすアプリケーションは Dapr Advent Calendar 3日目 - Daprでサービスを呼ぶ で作成したものなので、そちらを先に読んでからこのエントリーを読んでください。

ソースコードgithubに置いてあります。

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側のマニフェストファイルは前回作成したものと同じです。

k8s/hello-app.yaml

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アプリケーションをデプロイするためのマニフェストファイルを作成します。

k8s/invoke-app.yaml

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.yamlhelloinvoke に変え、80808081 にしただけです。

ただ一点だけ 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-appinvoke-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を経由してアクセスしていることが分かりました。

f:id:cero-t:20211213221836p:plain
k8sで動くアプリケーションの詳細

なぜ繋がるのか?

どうやってDaprを経由して別のアプリケーションを呼び出すことができたのでしょうか。それに関するドキュメントはこれです。

docs.dapr.io

f:id:cero-t:20211213214902p:plain
Service invocation overview

この画像の「Name resolution component」としてk8sDNSが利用されているのです。ローカル環境のmDNSにせよk8sDNSにせよ、特に別の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環境ではk8sDNSが利用されます
  • ログは kubectl logs コマンドで確認することができます

だいぶk8sにも慣れてきましたかね!? それでは、また明日!