APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

AKSでDaprを使ってみる(1) Ingress Controllerと組み合わせる

Dapr on AKS

はじめに

Dapr(Distributed Application Runtime) をご存知でしょうか。 Microsoftも主要開発元として参画している、マイクロサービスの開発と実装を簡略化するAPIを提供するもの です。 Azureにおいては、プレビューですが AKSのadd-onとしても提供されていますし、Azure Container Apps でも採用されています。

dapr.io

そんな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)

これらの構成要素で以下の内容を実現します。

  1. Ingress Controllerを使って一部アプリケーションを外部公開します。
  2. 実際の利用シーンを想定して、デプロイするnamespaceを分けます。
  3. Dapr管理するアプリケーションと管理しないアプリケーションの両方を用意します。

サンプルアプリケーションそのものはRequestに対して簡単なResponseを返すだけの簡単なものとしています。

インストール

サンプルアプリケーションシステムのためAKSクラスターを作成しDaprやNginx Ingress Controllerインストールします

  1. AKSのデプロイ
  2. Daprのインストール
  3. アプリケーションのインストール
  4. Nginx Ingress Controllerのインストール

AKSのデプロイはAzure PortalCLIを利用して通常通り行ってください。

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通りの内容が紹介されていました。

内容をまとめると

  1. Ingress ControllerをDapr管理として登録(SidecarがInjectされる)
  2. Ingress Controllerに到着したRequestをDapr Sidecarに送信
  3. 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な指定方法がありました。

docs.dapr.io

これを使うことにし、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して問題ありません。

最後に

今回紹介した方法は個人的検証によるものです。ご利用に際してはご自身の責任でお願いいたします。

なお、今回作成したサンプルは以下で公開しております。ドキュメント等はまだ記載しておりません。申し訳有りませんがご了承ください。

github.com