はじめに
Dapr(Distributed Application Runtime) をご存知でしょうか。 Microsoftも主要開発元として参画している、マイクロサービスの開発と実装を簡略化するAPIを提供するもの です。 Azureにおいては、プレビューですが AKSのadd-onとしても提供されていますし、Azure Container Apps でも採用されています。
そんなDaprですがだいぶ盛り上がりを見せつつあるようですので、「AKSで実際に使ってみよう」と考えまず最初に簡単なWebAPI Serverを作ることにしました。一通り概念をみてそれほど難しくない(だからこそ有望)と感じたので、サンプルをみながら簡単にできると考えていました。
ところが、そんなに簡単には終わりませんでした。
Daprへのアプリケーションの登録はできているのですが、Ingress Controllerの組み合わせがなかなかうまくいかず、 ブラウザから接続を試みるも503などのエラーとなってしまいました。 さらにDaprとIngress Controllerの組み合わせ方法が公式ドキュメントでは紹介されていない (検索すると2つほど例が紹介されている程度)といった状況でした。
様々な情報を収集し、トライ&エラーを繰り返しやっと解決できたので、今回はどのように解決したかを交えて作成した サンプルアプリケーションをご紹介したいと思います。
サンプルアプリケーション 全体像
全体像は以下のようなイメージです。
構成要素は4つあります。
App Name | Namespace | 備考 |
---|---|---|
helloworld | default | Dapr管理対象外 |
aggregator | web | Dapr管理、外部公開あり |
timefeed | web | Dapr管理、内部アクセスのみ |
ingress controller | ingress-system | 外部公開用Reverse Proxy(Nginx Ingress Controller) |
これらの構成要素で以下の内容を実現します。
- Ingress Controllerを使って一部アプリケーションを外部公開します。
- 実際の利用シーンを想定して、デプロイするnamespaceを分けます。
- Dapr管理するアプリケーションと管理しないアプリケーションの両方を用意します。
サンプルアプリケーションそのものはRequestに対して簡単なResponseを返すだけの簡単なものとしています。
インストール
サンプルアプリケーションシステムのためAKSクラスターを作成しDaprやNginx Ingress Controllerインストールします
- AKSのデプロイ
- Daprのインストール
- アプリケーションのインストール
- Nginx Ingress Controllerのインストール
AKSのデプロイはAzure PortalやCLIを利用して通常通り行ってください。
Daprのインストール
今回は理解促進もあって AKS add-onではなく Dapr CLIを使ってインストールすることにしました。 (Helmでもインストールできます)
Dapr CLIのインストール
インストールやその後のDaprの操作を行うため、最初にDapr CLIをインストールします。手順はこちらに記載されています。
手元の環境はMacなのでbrewを使ってインストールします。
# install brew install dapr/tap/dapr-cli # 動作確認(helpが表示される) dapr
daprコマンドが動けば完了です。
DaprをAKSにインストール
Kubernetesへのインストールは、daprコマンドで簡単にできます。
# kubectlが使えることを確認(ノード一覧を参照) kubectl get node # install dapr init -k # install確認 kubectl get pods --namespace dapr-system
驚くくらいに簡単です。ここまでは順調。
アプリケーションのインストール
Dapr管理下にアプリケーションを登録するには KubernetesのDeploymentに annotationを追加するだけで行なえます。
deploymentの例
apiVersion: apps/v1 kind: Deployment metadata: name: aggregator namespace: web labels: app: aggregator spec: selector: matchLabels: app: aggregator template: metadata: labels: app: aggregator # dapr.ioのannotationsを付与することでDaprに登録 annotations: dapr.io/enabled: "true" dapr.io/app-id: "aggregator" dapr.io/app-port: "8080" spec: containers: - name: aggregator image: ${ACR_NAME}/dapr-sample/aggregator:0.0.1-SNAPSHOT imagePullPolicy: Always ports: - containerPort: 8080 env: # 接続先アプリケーションはlocalhostのdapr sidecarに対してRequestを送信することで実現 - name: TIMEFEED_HOST value: http://localhost:3500 - name: TIMEFEED_BASE value: /v1.0/invoke/timefeed/method/ - name: SPRING_MAIN_BANNER-MODE value: "off"
ポイントは2つ。
- Daprへの登録は.spec.template.annotaionsに dapr.io関連の情報を追加します。これでアプリケーションはDaprで管理されます。
- 他アプリケーションへの接続は localhost:3500 のDapr sidecarに対して Service Invocation APIを通じて行います。
これだけです。 timefeedアプリケーションも同様の内容です。
なお、dapr管理となると、 <dapr.io/app-id名称>-dapr
(上記の例の場合は aggregator-dapr
) というClusterIPタイプのServiceが自動的に作成されます。この仕組により、アプリケーションPodに対するラウンドロビン方式の負荷分散が行わるため、ユーザーが指定する必要はありません。
Azure portalなどを使ってデプロイしたアプリケーションPodの中をみると アプリケーション
と daprd
の2つのコンテナが動いていることが確認できると思います。動いれいれば期待通りにdapr sidecarが injectされたことになります。
Nginx Ingress Controllerのインストール
先程述べたようにDaprとIngress Controllerの組み合わせでの設定方法は公式ドキュメントでは見つけられませんでした。 ネットで調べてみると概ね次の2通りの内容が紹介されていました。
Kubernetes NGINX ingress controller with Dapr - Code it Yourself...
Enable Dapr with Apache APISIX Ingress Controller | Dapr Blog
内容をまとめると
- Ingress ControllerをDapr管理として登録(SidecarがInjectされる)
- Ingress Controllerに到着したRequestをDapr Sidecarに送信
- Ingress ControllerのDapr sidecarがService Invocationを通じてアプリケーションにRequestを転送
というものです。こういうイメージ。
とうことであれば Kubernetes NGINX ingress controller with Dapr - Code it Yourself... で紹介されているようにNgnx Ingress Controllerに podAnnotation 指定を追加してインストールすればOKなはず。
config.yaml
controller: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: "nginx-ingress" dapr.io/app-port: "80"
こちらのYamlファイルを作成し、以下のコマンドで Nginx Ingress Controllerをインストールします。
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ --create-namespace --namespace ingress-system \ --values config.yaml
Ingressの登録
ここまでくればあとは Ingressを登録して外部からaggregatorにアクセスできるようにすればOKなはず。
ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aggregator-ingress namespace: ingress-system annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/use-regex: "true" # Dapr Service Invocation APIのパス名に書き換え nginx.ingress.kubernetes.io/rewrite-target: /v1.0/invoke/aggregator/method/$1 spec: rules: - host: aggregator.${DOMAIN_NAME} http: paths: - path: /(.*) pathType: Prefix backend: service: name: nginx-ingress-dapr # install時につけたannotationにより ClusterIP Serviceが自動生成されています port: number: 80
aggregator.hostname宛のリクエストをそのまま nginx-ingress のsidecar に(パス名を変換して) 転送するだけ。
うまくいかない
Daprの資料等をみてもこれでうまくいくはずなのですが、どうしてもエラーになってしまう。nginx-ingress-controllerのPodのログや kubectl describe Ingressや kubectl describe svcで登録しているサービスやIngressの内容を確認して以下の点に問題があることがわかってきました。
nginx-ingress-daprへの接続がrejectされている
転送内容を判別して nginx-ingress-darpに対して /v1.0/invoke/aggregator/method/$1 のパス名に変換して転送しようとしていますが、nginx-ingress-dapr(10.0.240.x)への接続要求がrejectされていました。
調べていくと dapr.io/sidecar-listen-addresses というannotationsに解がありました。
Daprのannotations指定のドキュメントを確認すると sidecar-listen-addressesの説明に
sidecarのlisten ipは [::1],127.0.0.1 である
と記載されています。ところが、nginx-ingress-dapr の名前解決で返ってくるのは AKS clusterのサブネットに属するIP(今回の例では10.0.240.x)です。ここが問題となって connectionがreject されていました。ということで nginx ingress controllerの設定を変更します。
config.yaml
controller: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: "nginx-ingress" dapr.io/app-port: "80" dapr.io/sidecar-listen-addresses: "0.0.0.0" # ここを追加
上記のように listen addressを変更しhelm upgradeコマンドを実行することでこの問題は解消です。
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ --create-namespace --namespace ingress-system \ --values config.yaml
aggregatorが見つけられない(invokeできない)
上記の問題を解決したので大丈夫と思ったのですが、次は 対象サービスが invokeできないというエラーでした。どうやらnamespaceが関係しているようだということはログ等から分ってきました。
ingress.yamlのnamespaceをwebに変えて実行すると、ngixn-ingress-dapr が見つけられない。 cross namespaceなingress設定でよく nginx-ingress-daprをExternalName Serviceで指定して回避しようとしましたが、 転送が無限ループしてしまうなどうまくいきません。
さらに調べてみると daprでも cross namespaceな指定方法がありました。
これを使うことにし、IngressのnamespaceはIngress Controllerと同じものを指定するようにしました。
ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aggregator-ingress namespace: ingress-system annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/use-regex: "true" # Dapr Service Invocation APIのパス名に書き換え # 転送先appの名称を app-id.namespace = aggregator.web に変更 nginx.ingress.kubernetes.io/rewrite-target: /v1.0/invoke/aggregator.web/method/$1 spec: # 以下略
rewrite-targetのapp-idの部分を app-id.namespace に変更して登録し直しすればOK。
なお、 aggregator から timefeedアプリケーションの呼び出しの際は、同一 namespace 内のため、app-idだけで転送が実現できます。
無事、ingress controller経由でアプリケーションの画面を表示することができました!!
Dapr管理外の場合はどうするか?
アプリケーションをDaprに登録しない場合、従来のやり方のままです。Serviceも自身で登録する必要があります。
deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: aks-helloworld namespace: default spec: replicas: 1 selector: matchLabels: app: aks-helloworld template: metadata: labels: app: aks-helloworld spec: containers: - name: aks-helloworld image: mcr.microsoft.com/azuredocs/aks-helloworld:v1 ports: - containerPort: 80 env: - name: TITLE value: "Welcome to Azure Kubernetes Service (AKS)" --- apiVersion: v1 kind: Service metadata: name: aks-helloworld namespace: default spec: type: ClusterIP ports: - port: 80 selector: app: aks-helloworld
ingress.yaml
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: aks-helloworld namespace: default annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" spec: defaultBackend: service: name: aks-helloworld port: number: 80 rules: - host: aks-helloworld.${DOMAIN_NAME} http: paths: - path: / pathType: Prefix backend: service: name: aks-helloworld port: number: 80
通常とまったく変わりません。Ingressもアプリケーションと同一のnamespaceでdeployして問題ありません。
最後に
今回紹介した方法は個人的検証によるものです。ご利用に際してはご自身の責任でお願いいたします。
なお、今回作成したサンプルは以下で公開しております。ドキュメント等はまだ記載しておりません。申し訳有りませんがご了承ください。