谷本 心 in せろ部屋

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

Dapr Advent Calendar 13日目 - Dapr + k8sでHello World

こんにちは Dapr Advent Calendar 13日目です。ついに半分を超えましたよ! 人間、やればできるもんなんですね!! いや、まだ終わってないですけどね。

k8sでDaprのHello World

前回のエントリーではk8sを使わずに分散環境で使ってみようと試みましたが、今回からは公式ドキュメントでも推奨されているとおりk8sを使って試してみます。

そんなに難しくはないので、k8sをこれまで触ってこなかったという方も、この機会に気軽に試してもらえれば嬉しいです。

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

Dapr + k8s環境のセットアップ

ツールのインストール

k8s上でDaprを利用するために、次のツールをインストールする必要があります。

Dapr CLIはこれまでのエントリーでも使ってきた、Daprのセットアップや実行をするためのCLIツールです。

dockerは仮想化環境構築ツールです。

kubectlはk8sコマンドラインで操作するためのツールです。ローカル環境のk8sでも、リモートやクラウド上のk8sであっても、同じようにコマンドで操作ができます。

minikubeはローカル環境にk8sを構築するためのツールです。minikube以外にもローカル環境にk8sを構築するためのツールはあるのですが、Daprのドキュメントでminikubeが使われていたため、これを使うことにしました。

インストール手順は、それぞれのURLから確認してください。

minikubeを起動する

まずはローカル環境でk8sを動作させるため、minikubeを起動します。

minikube start --cpus=4 --memory=4096

(※2021-12-16追記) オプションを指定しないと2CPU、メモリ2GBで運用しようとするので、アプリを6つほど起動しようとするとリソース不足になります。

数十秒ほど掛かって

🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

このような表示が出たら起動は完了です。

起動したminikubeにkubectlコマンドでアクセスしてみましょう。念のため、kubectlの接続先がminikubeになっていることを確認します。

kubectl config get-contexts

kubectlコマンドの接続先の一覧が表示されます。

CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube   default

minikubeしか接続先がない場合は、このように表示されます。

複数の接続先が表示された場合は、minikubeCURRENT* がついているかを確認してください。もし別の環境に接続していた場合は、次のコマンドでminikubeを参照するようにします。

kubectl config use-context minikube

これでkubectlコマンドがminikubeに繋がるようになりました。

podの一覧を見る

kubectlコマンドを使って、minikube上にあるpodの一覧を見てみましょう。

「pod」とは、1つ〜複数のコンテナをまとめたリソースです。たとえばDaprでは、アプリケーションのコンテナとDaprのコンテナを一つの「pod」にまとめて利用します。

次のコマンドでpodの一覧を表示します。

kubectl get pods -A

次のように表示されます

NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
kube-system            coredns-558bd4d5db-h579g                     1/1     Running   0          1d
kube-system            etcd-minikube                                1/1     Running   0          1d
kube-system            kube-apiserver-minikube                      1/1     Running   0          1d
kube-system            kube-controller-manager-minikube             1/1     Running   0          1d
kube-system            kube-proxy-bgt78                             1/1     Running   0          1d
kube-system            kube-scheduler-minikube                      1/1     Running   0          1d
kube-system            storage-provisioner                          1/1     Running   0          1d
kubernetes-dashboard   dashboard-metrics-scraper-7976b667d4-9ffr8   1/1     Running   0          1d
kubernetes-dashboard   kubernetes-dashboard-6fcdf4f6d-z5vjh         1/1     Running   0          1d

何やらk8s関連のpodだけが起動しているようですね。詳しくは僕も知らないので、フーンという感じで眺めるだけにしておきます。

Daprのセットアップ

それではminikube上にDapr関連のpodを作成しましょう。次のコマンドでDaprの初期化を行います。

dapr init -k

オプションの -kk8sを対象とするという意味です。

初期化が終わったら、もう一度podの一覧を見てみましょう。

kubectl get pods -A

次のように表示されるはずです。

NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
dapr-system            dapr-dashboard-57b4db56fc-tng2j              1/1     Running   0          54s
dapr-system            dapr-operator-5b4b68b5c5-cljjl               1/1     Running   0          54s
dapr-system            dapr-placement-server-0                      1/1     Running   0          54s
dapr-system            dapr-sentry-c6b746cdf-nkvnv                  1/1     Running   0          54s
dapr-system            dapr-sidecar-injector-6f749dbf87-vr9f6       1/1     Running   0          54s
kube-system            coredns-558bd4d5db-h579g                     1/1     Running   0          1d
kube-system            etcd-minikube                                1/1     Running   0          1d
kube-system            kube-apiserver-minikube                      1/1     Running   0          1d
kube-system            kube-controller-manager-minikube             1/1     Running   0          1d
kube-system            kube-proxy-bgt78                             1/1     Running   0          1d
kube-system            kube-scheduler-minikube                      1/1     Running   0          1d
kube-system            storage-provisioner                          1/1     Running   0          1d
kubernetes-dashboard   dashboard-metrics-scraper-7976b667d4-9ffr8   1/1     Running   0          1d
kubernetes-dashboard   kubernetes-dashboard-6fcdf4f6d-z5vjh         1/1     Running   0          1d

NAMESPACEdapr-system となっているpodが5つ追加されました。これらがDaprを運用するために必要なpod群です。

Dapr CLIのコマンドでも、状況を確認できます。

dapr status -k

次のように表示されます。

  NAME                   NAMESPACE    HEALTHY  STATUS   REPLICAS  VERSION  AGE  CREATED              
  dapr-dashboard         dapr-system  True     Running  1         0.9.0    5m   2021-12-13 00:00.00  
  dapr-sidecar-injector  dapr-system  True     Running  1         1.5.1    5m   2021-12-13 00:00.00  
  dapr-placement-server  dapr-system  True     Running  1         1.5.1    5m   2021-12-13 00:00.00  
  dapr-operator          dapr-system  True     Running  1         1.5.1    5m   2021-12-13 00:00.00  
  dapr-sentry            dapr-system  True     Running  1         1.5.1    5m   2021-12-13 00:00.00  

同じように5つのpodが動いていることを確認できます。

ちなみに、これらのpodを削除したい場合は、次のコマンドを実行します。

dapr uninstall -k

とても簡単ですね。

Hello Worldアプリケーションをk8sの上で動かす

続いて、Hello Worldアプリケーションをk8s上で動かしてみましょう。Hello WorldアプリケーションのソースコードGitHubにあります。

https://github.com/cero-t/dapr-advent-2021

この中から Dapr Advent Calendar 2日目 - DaprでHello World で作成した hello アプリケーションを使います。

作成したアプリケーションのおさらい

helloアプリケーションのメインロジックである HelloControllerソースコードは次のようになっています。

(hello) HelloController.java

@RestController
public class HelloController {
    @GetMapping("/hello")
    public Map<String, String> hello() {
        return Map.of("message", "Hello, world!");
    }
}

Hello, world! を含んだメッセージを返すだけの簡単なアプリケーションです。

アプリケーションのイメージ作成

続いて、Hello Worldアプリケーションの(Docker)イメージを作成しましょう。

イメージ作成は docker build コマンドでもできるのですが、Spring Bootでは mvn spring-boot:build-image コマンドでBuildpackを使って簡単にイメージ作成をすることができるので、これを利用しましょう。できあがるイメージのサイズも最適化されているし、Javaのオプションなども適切に指定されるため、手軽に最適なイメージを作成することができます。

イメージを作成する前に、イメージ作成先がminikubeのイメージレジストリになるよう次のコマンドを実行します。

eval $(minikube docker-env)

このコマンドを実行せずにイメージ作成すると、作成したはずのイメージをminikubeが見つけられない、というハメになります。僕も何度となくハマりました。

それではイメージを作成します。次のコマンドを実行します。

cd (GitHubのディレクトリパス)/dapr-advent-2021/hello
../mvnw spring-boot:build-image

初回はビルドするためのイメージなどをダウンロードするため時間が掛かります。

作成が終わったら、イメージ一覧を見てみましょう。

docker images

次のようなイメージが見つかるはずです。

hello                                     1.0.0                 d0a362971d5c   41 years ago    261MB

41年の由緒正しい歴史あるイメージができあがりました。なぜか1980年に作られたことになってしまうみたいですね。まぁ気にしないでおきましょう。

アプリケーションをデプロイしてアクセスする

それでは、作成したイメージをk8sで動かしてみましょう。

イメージをk8sにデプロイ

まずはイメージをk8sにデプロイします。次のコマンドを実行します。

cd (GitHubのディレクトリパス)/dapr-advent-2021
kubectl apply -f k8s/hello-app.yaml

この kubectl apply -fマニフェストファイルを用いてk8sにデプロイするコマンドです。hello-app.yaml の詳しい説明は後ほど行います。

実行すると次のように表示されるはずです。

deployment.apps/hello-app created
service/hello-svc created

hello-app という名前のデプロイメントと、hello-svc という名前のサービスが作成されました。hello-app がpodで、hello-svc がpodに外部からアクセスできるようにするためのサービスです。

起動したpodを次のコマンドで確認しましょう。

kubectl get pods

次のように表示されるはずです。

NAME                        READY   STATUS    RESTARTS   AGE
hello-app-fb644cd5d-wf785   2/2     Running   0          20s

READY2/2 は、アプリケーションのコンテナと、Daprのコンテナの2つがそれぞれ起動していることを示しています。

起動には少し時間が掛かるため、起動中の場合は READY2/2 になっていないこともありますが、何度か kubectl get pods コマンドを実行して 2/2 になるのを確認してください。

何度実行しても次のように表示される場合は、設定や環境に問題があります。

NAME                         READY   STATUS             RESTARTS   AGE
hello-app-5fd947547b-vs8lj   0/2     ImagePullBackOff   0          17s

もしこのように表示された場合は、次のコマンドで詳細を確認しましょう。

kubectl describe pods hello-app-5fd947547b-vs8lj

たとえばイメージが見つからない場合には、次のように表示されます。

  Normal   Scheduled  27s               default-scheduler  Successfully assigned default/hello-app-5fd947547b-vs8lj to minikube
  Normal   Pulling    9s (x2 over 27s)  kubelet            Pulling image "hello:1.0.0"
  Warning  Unhealthy  9s (x3 over 21s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500
  Warning  Unhealthy  9s (x3 over 21s)  kubelet            Readiness probe failed: HTTP probe failed with statuscode: 500
  Normal   Killing    9s                kubelet            Container daprd failed liveness probe, will be restarted
  Warning  Failed     6s (x2 over 24s)  kubelet            Failed to pull image "hello:1.0.0": rpc error: code = Unknown desc = Error response from daemon: pull access denied for hello, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
  Normal   Started    6s (x2 over 24s)  kubelet            Started container daprd
  Warning  Failed     6s (x2 over 24s)  kubelet            Error: ErrImagePull
  Normal   Pulled     6s (x2 over 24s)  kubelet            Container image "docker.io/daprio/daprd:1.5.1" already present on machine
  Normal   Created    6s (x2 over 24s)  kubelet            Created container daprd
  Normal   BackOff    5s (x3 over 23s)  kubelet            Back-off pulling image "hello:1.0.0"
  Warning  Failed     5s (x3 over 23s)  kubelet            Error: ImagePullBackOff

この辺りはk8sの使いこなしの話になるので、いったんここでは詳細は割愛します。

もしこのような問題が起きたら、ここまでの手順を見直して、抜けていることなどないか確認してください。

k8s上のアプリケーションにアクセスする

それでは、起動したアプリケーションにアクセスしてみましょう。

次のコマンドを実行します。

kubectl port-forward service/hello-svc 8080:8080

次のように表示されるはずです。

Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

これでローカルPCの8080番ポートから、minikubeの8080番ポートにアクセスできるようになりました。

それでは、別のコンソールからアクセスしてみましょう。

curl localhost:8080/hello

次のメッセージが取得できるはずです。

{"message":"Hello, world!"}

無事にアクセスできましたね。

作成したpodの削除

作成したpodは次のコマンドで削除できます。

kubectl delete -f k8s/hello-app.yaml

実行すると次のように表示されます。

deployment.apps "hello-app" deleted
service "hello-svc" deleted

これでpodとServiceがそれぞれ削除されました。

また、次のコマンドでそれぞれ削除することもできます。

kubectl delete deployment hello-app
kubectl delete service hello-svc

マニフェストファイルがない場合や、マニフェストファイルを書き換えてしまった場合には、この方法を用いても良いでしょう。

やったことの解説

それでは、今回行ったことの説明です。

セットアップのおさらい

環境構築に関わる部分をおさらいします。

  • kubectlをインストールした
  • minikubeをインストールした
  • dapr init -k で環境を構築した
  • eval $(minikube docker-env) でminikubeのイメージレジストリにするイメージが作成されるようにした
  • mvnw spring-boot:build-image でイメージを作成した
  • kubectl apply -f k8s/hello-app.yaml でアプリケーションをデプロイした

直接構築に関わる部分はこれくらいですね。

アプリケーションをデプロイするためのマニフェストファイル

それでは説明を飛ばしていたマニフェストファイルについて説明します。ファイルは次のようになっています。

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

後半の --- より上がpodのデプロイの設定、下がサービスの設定です。k8sマニフェストファイルは、--- で区切ることで1ファイル内に複数の設定を書くことができます。

デプロイの設定

まずはデプロイ設定の主要な部分を説明します。デプロイ設定はpodを作成するための設定です。

metadata.namehello-app がアプリケーション名です。podの名前などに利用されます。

spec.template.metadata.annotations がDapr関連の設定です。

        dapr.io/enabled: "true"
        dapr.io/app-id: "hello-app"
        dapr.io/app-port: "8080"

dapr.io/enabledtrue にすると、このアプリケーションのサイドカーとしてDaprが適用されます。

dapr.io/app-id はアプリケーションのIDで、dapr run コマンドの --app-id に該当します。

dapr.io/app-port はアプリケーションのポート番号で dapr run コマンドの --app-port に該当します。

つまり dapr run --app-id hello-app --app-port 8080 でアプリケーションを起動するのと同等となります。

spec.template.spec.containers がコンテナ関連の設定です。

      - name: hello
        image: hello:1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080

imagePullPolicyIfNotPresent はローカルにイメージがなければDocker Hubを見に行くという設定です。ここの設定を Always にしてしまうと常にDocker Hubを見に行ってしまい、Docker Hubに hello:1.0.0 などないというエラーが発生してしまいます。よくハマりました。

ports にはアプリケーション側のポート番号を指定します。このポートをpod外に公開します。

これでこのアプリケーションがDaprとともに起動するpodができ、podの8080番ポートがpod外に公開されることになります。

サービスの設定

つづいて、サービス設定の主要部分を説明します。podを外部に公開するための設定です。

metadatea.name がサービスの名称です。

spec がポートに関する設定です。

spec:
  selector:
    app: hello
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
  type: LoadBalancer

selector.app で対象となるpodのラベルを指定します。ここでは hello-app のラベルである hello を指定します。

ports でpodのどのポートを公開するかを設定します。ここでは TCP8080 番ポートを、minikubeの 8080 番ポートとして公開するように指定しています。

type がこのサービスのタイプです。LoadBalancer を指定することで、podのレプリカを複数起動した際に、ラウンドロビン形式でリクエストを分散させることができます。

これでminikubeの8080番ポートにアクセスすると hello-app の8080番ポートにアクセスできるようになる、ということです。

ただしminikubeの各ポートは、通常そのままでは外部からはアクセスできないため kubectl port-forward service/hello-svc 8080:8080 を使うことで localhost:8080 をminikubeの8080番ポートのフォワードして、アクセスができるようになったのです。

f:id:cero-t:20211213084927p:plain
幾重にも重なるk8sのアクセス構造

この辺りは少し分かりにくいところではあるのですが、使っていくうちに慣れていくと思います。

あれ? Daprは使ってない?

ここまで説明してきましたが、もしかしたら「あれ、Daprにアクセスしてないのでは?」と気づいた方もいらっしゃるかも知れません。その通りです。

アプリケーションを8080番ポートで起動し、podの8080番ポートを公開し、サービスを使ってminikubeの8080番ポートにマッピングし、ポートフォワードでlocalhost:8080からアクセスできるようになりました。つまりアクセスしたのはあくまでもアプリケーションの8080番ポートです。

今回の手順では、アプリケーションをDaprとともに起動しましたが、あくまでもアプリケーションのポートを外部からアクセスできるようにしただけに過ぎず、Daprは通過すらしていません。Daprの機能を利用するような処理は、明日以降のブログで紹介していきます。

まとめ

  • Daprをk8sで使うために、Dapr CLI、docker、kubectl、minikubeをインストールしましょう
  • dapr init -k で環境を構築しましょう
  • eval $(minikube docker-env) でminikubeのイメージレジストリを指すようにしましょう
  • mvnw spring-boot:build-image でイメージを作成しましょう
  • kubectl apply -f k8s/hello-app.yaml でアプリケーションをデプロイしましょう
  • DeploymentとServiceの設定を使って、アプリケーションを外部からアクセスできるようにします
  • ポートフォワードを使ってlocalhostからminikubeにアクセスできるようにします

なお、今回はアプリケーションの8080番ポートにアクセスしましたが、サービスの設定などで3500番ポートを公開してアクセスすれば、外部からDaprにアクセスすることもできます。しかし、アプリケーションのポートを公開するべきか、Daprのポートを公開するべきかは慎重な設計が必要になるところです。その辺りについては、またいずれ触れたいと思います。

それでは、また明日!