APC 技術ブログ

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

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

AKS の KEDA アドオンが GA したので使ってみた

こんにちは、ACS 事業部の埜下です。 ついに AKS の KEDA アドオンが GA されました!

azure.microsoft.com

パブリックプレビューになったのが昨年の2022年5月だったので、約1年半越しの GA となります。 そこで今回は KEDA アドオンを使って Azure Service Bus をトリガーとしたオートスケールを試してみました。

KEDA とは

Kubernetes Event-driven Autoscaling (KEDA) は、Kubernetes 用のイベント駆動型オートスケーラーになります。 「キューにメッセージが N 件残っている」とか「ストレージにデータが格納された」とか等のイベントをトリガーに Kubernetes 上の Pod の数を増減してくれます。

https://keda.sh/img/keda-arch.png
引用元: https://keda.sh/docs/2.12/concepts/

KEDA は Microsoft 主導で開発されている CNCF 傘下のプロジェクトで、2023年8月22日に Graduated プロジェクトとして認定されました。 プロジェクトとしても Graduated になり、AKS アドオンとしても GA になったというわけです。

KEDA には 60 個以上の組み込みスケーラーがあり、Azure サービスに絞ると以下のスケーラーを利用できます。 その他にも外部スケーラーという形で KEDA を拡張することもできますが、外部スケーラーは KEDA アドオンのサポート外になっていますので外部スケーラーの利用は自己責任でお願いします。

  • Azure Application Insights
  • Azure Blob Storage
  • Azure Data Explorer
  • Azure Event Hub
  • Azure Log Analytics
  • Azure Monitor
  • Azure Pipelines
  • Azure Service Bus
  • Azure Storage Queue

KEDA の詳細な仕組みについては 公式ドキュメント をご覧ください。

keda.sh

また、弊社の吉川が KEDA について登壇した資料がありますので、あわせてご覧ください。

www.docswell.com

KEDA アドオンの何が嬉しいのか

KEDA は OSS として公開されているため、アドオンを使わずとも AKS にインストール可能です。 では、KEDA をアドオンとして使えるようになると何が嬉しいのかと言うと、私は「KEDA がサポートの対象になる」という点を挙げます。

AKS サポートポリシーによると、Microsoft はコントロールプレーンを通して以下の Kubernetes コンポーネントを管理・運用します。

  • Kubelet または Kubernetes API サーバー
  • etcd または互換性のあるキーバリュー ストア
  • DNS サービス (kube-dns や CoreDNS など)
  • Kubernetes プロキシまたはネットワーキング ( BYOCNIが使用される場合を除く)
  • kube-system 名前空間で実行されているその他のアドオンまたはシステム コンポーネント

KEDA アドオンを使うと kube-system 名前空間に KEDA 関連リソースがデプロイされますので、Microsoft によって管理・運用されることになります。

他にも「KEDA がインストールされた状態で AKS クラスタを構築できる」、「KEDA の運用負荷が減る」といったメリットも考えられますが、個人的にはサポートの有無がアドオンに対する期待として一番大きいです。

KEDA アドオンを試してみる

では、KEDA アドオンを使ってオートスケールの動作を見てみましょう。 今回は以下のような構成で動作確認しました。 この構成は KEDA アドオンに特化しているものではなく、OSS 版の KEDA でも使えます。

  • サンプルアプリケーション(クライアント)から Azure Service Bus のキューにメッセージを送信する
  • サンプルアプリケーション(ワーカー)は Azure Service Bus のキューからメッセージを受信して処理する
  • KEDA は Azure Service Bus キューのメッセージ数をトリガーとしてサンプルアプリケーションをスケールさせる
  • サンプルアプリケーション(ワーカー)と KEDA は Azure Service Bus の参照にユーザー割り当てマネージド ID を利用する
  • ユーザー割り当てマネージド ID は認証に AKS の OIDC Issuer、認可に Azure RBAC を利用する

KEDA アドオン動作確認の構成

以降の手順では次の環境変数を使用しますので、環境に応じて値を変えてご利用ください。

# 動作確認で使用する環境変数
export RESOURCE_GROUP=rg-keda-demo
export CLUSTER_NAME=aks-keda-demo
export MANAGEDID_NAME=id-keda-demo
export SERVICE_BUS_NAMESPACE=sbns-keda-demo-`date +"%Y%m%d%H%M"`
export SAMPLE_APP_NAMESPACE=keda-dotnet-sample
export SAMPLE_APP_SA_NAME=order-processor-sa

Azure リソースの準備

構成図にある Azure リソースを作成します。

  • AKS
  • ユーザー割り当てマネージド ID
  • Service Bus

AKS

Azure CLI を使う場合、--enable-keda オプションをつけることで KEDA アドオンが有効化された AKS クラスタを作成することができます。 また、ワークロード ID を使う場合は --enable-workload-identity オプションと --enable-oidc-issuer オプションをつける必要があります。

今回は以下のコマンドで AKS クラスタを作成しました。 構築済みの AKS に対しても az aks update -g ${RESOURCE_GROUP} -n ${CLUSTER_NAME} --enable-keda コマンドで KEDA アドオンを有効化できます。

az aks create \
    --resource-group ${RESOURCE_GROUP} \
    --name ${CLUSTER_NAME} \
    --node-vm-size Standard_B2ms \
    --node-count 1 \
    --kubernetes-version 1.27 \
    --enable-keda \
    --enable-oidc-issuer \
    --enable-workload-identity

AKS クラスタの作成が完了したら、後ほど利用する OIDC 発行者 (OIDC Issuer) の情報を取得しておきます。

export OIDC_ISSUER="$(az aks show -g ${RESOURCE_GROUP} -n ${CLUSTER_NAME}  --query "oidcIssuerProfile.issuerUrl" -o tsv)"

ユーザー割り当てマネージド ID

今回の構成では AKS 上のサンプルアプリケーションと KEDA はそれぞれ Azure Service Bus にアクセスするための権限が必要になります。 今後主流の権限管理になると思われる ワークロード ID を使って Azure Service Bus に対するアクセス権を付与していきます。 ワークロード ID についてはこちらもご覧ください。

techblog.ap-com.co.jp

まずは、サンプルアプリケーションと KEDA がサービスアカウントが使用するユーザー割り当てマネージド ID を作成します。

az identity create \
    --resource-group ${RESOURCE_GROUP} \
    --name ${MANAGEDID_NAME} \
    --location japaneast

こちらも後ほど利用するため、作成したユーザー割り当てマネージド ID のクライアント ID を取得しておきます。

export MANAGEDID_CLIENT_ID="$(az identity show -g ${RESOURCE_GROUP} -n ${MANAGEDID_NAME} --query "clientId" -o tsv)"

作成したユーザー割り当てマネージド ID にフェデレーション資格情報を登録します。 これにより、マネージド ID と AKS 上のサービスアカウントが紐づきます。 サンプルアプリケーション と KEDA は別のサービスアカウントを利用するため 2 つのフェデレーション資格情報が必要です。

# サンプルアプリケーション用フェデレーション
az identity federated-credential create \
  --name sample-app-federated-identity \
  --identity-name ${MANAGEDID_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --issuer ${OIDC_ISSUER} \
  --subject system:serviceaccount:"${SAMPLE_APP_NAMESPACE}":"${SAMPLE_APP_SA_NAME}"

# KEDA 用フェデレーション
az identity federated-credential create \
  --name keda-federated-identity \
  --identity-name ${MANAGEDID_NAME} \
  --resource-group ${RESOURCE_GROUP} \
  --issuer ${OIDC_ISSUER} \
  --subject system:serviceaccount:kube-system:keda-operator

Azure Service Bus

KEDA がスケールの判断基準として参照する Azure Service Bus 名前空間とキューを作成します。 Azure Service Bus 名前空間のリソース名はグローバルで一意である必要があるので、今回はリソース名末尾にタイムスタンプを付与しています。 キューの名前にはサンプルアプリケーションで指定されている orders を設定しています。

# 名前空間
az servicebus namespace create \
    --resource-group ${RESOURCE_GROUP} \
    --name ${SERVICE_BUS_NAMESPACE} \
    --location japaneast \
    --sku Basic

# キュー
az servicebus queue create \
    --resource-group ${RESOURCE_GROUP} \
    --namespace-name ${SERVICE_BUS_NAMESPACE} \
    --name orders

Azure Service Bus のキューをサンプルアプリケーションと KEDA が受信できるようにするために、ユーザー割り当てマネージド ID に Azure Service Bus Data Receiver のロールを付与します。

az role assignment create \
    --role 'Azure Service Bus Data Receiver' \
    --assignee ${MANAGEDID_CLIENT_ID} \
    --scope $(az servicebus namespace show -g ${RESOURCE_GROUP} -n ${SERVICE_BUS_NAMESPACE} --query "id" -o tsv)

サンプルアプリケーション

サンプルアプリケーションには以下で公開されている .NET Core 3.1 ベースのアプリケーションを利用します。 ※ .NET Core はすでにサポートが終了しています。

github.com

このアプリケーションには Azure Service Bus のキューにデータを追加するクライアントと、キューを参照して処理するワーカーが用意されています。 ワーカーはコンテナイメージが公開されているので AKS クラスタにデプロイして利用します。 一方、クライアントは Azure Service Bus の接続文字列を設定したうえでローカル環境でビルドして利用します。

ワーカー

まずはワーカーの Pod とサービスアカウントを作成します。

AKS でワークロード ID を利用する場合、以下の作業が必要です。

  • サービスアカウント
    • azure.workload.identity/client-id アノテーションで関連付けるユーザー割り当てマネージド ID のクライアント ID を指定
  • Pod
    • azure.workload.identity/use: "true" ラベルを付与

learn.microsoft.com

以下のマニフェストで名前空間とサービスアカウント、Deployment をデプロイします。

# 名前空間
kubectl create namespace ${SAMPLE_APP_NAMESPACE}

# サービスアカウント
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ${SAMPLE_APP_SA_NAME}
  namespace: ${SAMPLE_APP_NAMESPACE}
  annotations:
    azure.workload.identity/client-id: ${MANAGEDID_CLIENT_ID}
EOF

# Deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-processor
  namespace: ${SAMPLE_APP_NAMESPACE}
  labels:
    app: order-processor
spec:
  selector:
    matchLabels:
      app: order-processor
  template:
    metadata:
      labels:
        app: order-processor
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: ${SAMPLE_APP_SA_NAME}
      containers:
      - name: order-processor
        image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest
        env:
        - name: KEDA_SERVICEBUS_AUTH_MODE
          value: WorkloadIdentity
        - name: KEDA_SERVICEBUS_HOST_NAME
          value: ${SERVICE_BUS_NAMESPACE}.servicebus.windows.net
        - name: KEDA_SERVICEBUS_QUEUE_NAME
          value: orders
EOF

この時点でサンプルアプリケーション(ワーカー)は Azure Service Bus のキューを参照してメッセージを受信・処理できる状態となっているはずです。 kubectl logs -l app=order-processor -n ${SAMPLE_APP_NAMESPACE} コマンドの結果、以下のようなログが出力されていれば問題ありません。

[04:59:39]info: Keda.Samples.Dotnet.OrderProcessor.OrdersQueueProcessor[0]
      Starting message pump on queue orders in namespace sbns-keda-demo-202311211339.servicebus.windows.net
[04:59:40]info: Keda.Samples.Dotnet.OrderProcessor.OrdersQueueProcessor[0]
      Message pump started
[04:59:40]info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
[04:59:40]info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
[04:59:40]info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

クライアント

つづいて Azure Service Bus のキューにデータを投入するクライアントの準備です。 こちらはソースコードに Azure Service Bus の接続文字列を設定したりビルドしたりと少し手間がかかります。

また、既にサポートが終了している .NET Core 3.1 ベースで作られているため、.NET 6 や 7 で動かす場合は手直しが必要です(とは言っても TargetFramework を変更するだけで動きます)。

まずは Azure Service Bus の接続文字列を取得します。

az servicebus namespace authorization-rule keys list \
    --resource-group ${RESOURCE_GROUP} \
    --namespace-name ${SERVICE_BUS_NAMESPACE} \
    --name RootManageSharedAccessKey \
    --query primaryConnectionString \
    --output tsv

こちらで表示された文字列を src\Keda.Samples.Dotnet.OrderGenerator\Program.csConnectionString に設定します。

リポジトリ直下で dotnet run --project .\src\Keda.Samples.Dotnet.OrderGenerator\Keda.Samples.Dotnet.OrderGenerator.csproj を実行するとデータ投入数を尋ねられるので任意の数を入力します。

クライアントから Azure Service Bus にデータ投入

接続文字列の場合は特に権限設定もなくキューにデータを投入することができ、Azure Portal 上でもキューの状況を確認することができます。 すでにワーカーを AKS クラスタ上で動かしているのでキューが 1 つずつ処理されており、画像ではメッセージ数が 75 になっていますね。

Azure Portal からキューを確認

以上で KEDA 動作確認の準備は完了です。

動作確認

お待たせしました! いよいよ KEDA の動作確認です。

KEDA でサンプルアプリケーション(ワーカー)の Pod をスケールさせるためにカスタムリソース TriggerAuthentication と ScaledObject を作成します。

TriggerAuthentication には KEDA がトリガー対象サービスにアクセスする際の認証情報を定義します。 今回はワークロード ID を使うため spec.podIdentity 配下に provider: azure-workloadidentityId: <ユーザー割り当てマネージド ID のクライアント ID> を指定しました。 他にも多くの認証方法がサポートされています。

また、TriggerAuthentication は名前空間毎に作成されますが、クラスタ全体で共通の認証方法を使いたい場合は ClusterTriggerAuthentication リソースが適していますので適宜使い分けてください。

# TriggerAuthentication
cat <<EOF | kubectl apply -f -
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: trigger-auth-service-bus-orders
  namespace: ${SAMPLE_APP_NAMESPACE}
spec:
  podIdentity:
    provider: azure-workload
    identityId: ${MANAGEDID_CLIENT_ID}
EOF

ScaledObject はスケールターゲットやスケーラーを指定するリソースになります。 今回は最大 10 個まで Pod をスケールさせる設定にしています。

# ScaledObject
cat <<EOF | kubectl apply -f -
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-scaler
  namespace: ${SAMPLE_APP_NAMESPACE}
spec:
  scaleTargetRef:
    name: order-processor
  maxReplicaCount: 10
  triggers:
  - type: azure-servicebus
    metadata:
      namespace: ${SERVICE_BUS_NAMESPACE}
      queueName: orders
      messageCount: '5'
    authenticationRef:
      name: trigger-auth-service-bus-orders
EOF

それでは上記マニフェストをデプロイしてみてください。 Azure Service Bus のキューにメッセージがない状態だとサンプルアプリケーション(ワーカー)の Pod が 0 にスケールされているはずです。 Horizontal Pod Autoscaler (HPA) では Pod 数を 0 にできないのですが、KEDA を使うことで Pod 数を 0 にすることができます。

KEDA が Pod 数を 0 にスケールイン

この状態でサンプルアプリケーション(クライアント)から Azure Service Bus に対して 100 件メッセージを送信してみましょう。 すると、Azure Service Bus をチェックしていた KEDA がキューの増加を検知してサンプルアプリケーション(ワーカー)の Pod 数をスケールアウトしていきます。 Pod 数 0 の状態から増えていき、最終的に 10 個の Pod が起動します。

Azure Service Bus のキューを見て KEDA が Pod をスケールアウト

Azure Service Bus のキューがなくなってもしばらくは 10 個のままですが、5 分ほどすると Pod が削除されていって最後にはまた 0 個になります。

Azure Service Bus のキューが 0 件のため KEDA が Pod をスケールイン

以上が KEDA アドオンの動作確認になります。 ワークロード ID と組み合わせる場合は準備するものが少し増えますが、簡単に KEDA を使ったスケールする環境を用意できましたね。

うまく動かない場合

動作確認をする中で KEDA がうまく動かないことがありましたので、その時の対処法を共有します。

KEDA Operator の Pod を再起動する

KEDA アドオンを有効化すると kube-system 名前空間に KEAD Operator がデプロイされます。 AKS でワークロード ID を有効化する前に KEDA アドオンを有効化していると、KEDA Operator の Pod にワークロード ID に関連する環境変数が設定されておらず KEDA が認証エラーとなってしまいます。

この場合は kubectl rollout restart deployment keda-operator -n kube-system コマンドを実行して KEDA Operator の Pod を再起動することで、環境変数が反映されます。 こちらは 公式ドキュメント にも記載されている対処法です。

再起動前

Environment:
  WATCH_NAMESPACE:               
  KEDA_HTTP_DEFAULT_TIMEOUT:     3000
  KUBERNETES_PORT:               tcp://aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io:443
  KEDA_HTTP_MIN_TLS_VERSION:     TLS12
  KUBERNETES_PORT_443_TCP_ADDR:  aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io
  KUBERNETES_PORT_443_TCP:       tcp://aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io:443
  KUBERNETES_SERVICE_HOST:       aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io
  POD_NAME:                      keda-operator-cb79ccc59-b9jq4 (v1:metadata.name)
  POD_NAMESPACE:                 kube-system (v1:metadata.namespace)
  OPERATOR_NAME:                 keda-operator

再起動後

Environment:
  KEDA_HTTP_MIN_TLS_VERSION:     TLS12
  KUBERNETES_PORT_443_TCP_ADDR:  aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io
  KUBERNETES_PORT:               tcp://aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io:443
  KUBERNETES_SERVICE_HOST:       aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io
  POD_NAME:                      keda-operator-cb79ccc59-bj7dc (v1:metadata.name)
  OPERATOR_NAME:                 keda-operator
  KEDA_HTTP_DEFAULT_TIMEOUT:     3000
  KUBERNETES_PORT_443_TCP:       tcp://aks-keda-demo-dns-x1xo405a.hcp.japaneast.azmk8s.io:443
  WATCH_NAMESPACE:               
  POD_NAMESPACE:                 kube-system (v1:metadata.namespace)
  AZURE_CLIENT_ID:               
  AZURE_TENANT_ID:               00000000-0000-0000-0000-000000000000
  AZURE_FEDERATED_TOKEN_FILE:    /var/run/secrets/azure/tokens/azure-identity-token
  AZURE_AUTHORITY_HOST:          https://login.microsoftonline.com/

TriggerAuthentication リソースの identityId を確認する

動作確認する中で KEDA Operator で以下のようなエラーが出ることがありました。

ERROR   scale_handler   error getting scale decision    
{
"scaledObject.Namespace": "keda-dotnet-sample", 
"scaledObject.Name": "order-scaler", 
"scaler": "azureServiceBusScaler", 
"error": "ChainedTokenCredential authentication failed POST https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
--------------------------------------------------------------------------------
RESPONSE 400 Bad Request
--------------------------------------------------------------------------------
{
  \"error\": \"unauthorized_client\",
  \"error_description\": \"AADSTS700016: Application with identifier 'https://japaneast.oic.prod-aks.azure.com/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000/' was not found in the directory 'テナント名'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: 00000000-0000-0000-0000-000000000000 Correlation ID: 00000000-0000-0000-0000-000000000000 Timestamp: 2023-11-22 03:44:00Z\",
  \"error_codes\": [
    700016
  ],
  \"timestamp\": \"2023-11-22 03:44:00Z\",
  \"trace_id\": \"00000000-0000-0000-0000-000000000000\",
  \"correlation_id\": \"00000000-0000-0000-0000-000000000000\",
  \"error_uri\": \"https://login.microsoftonline.com/error?code=700016\"
}
--------------------------------------------------------------------------------
"}

ログは「テナントに ID 'https://japaneast.oic.prod-aks.azure.com/(snip)/' のアプリケーションは存在しませんよ」という内容で、この ID というのは AKS の OIDC Issuer の値でした。 「なぜ ID の箇所に OIDC Issuer の値が入っているんだ?」と疑問に思いつつ、前述の KEDA Operator Pod の再起動も試しましたが改善しませんでした。

このエラーは TriggerAuthentication リソースの spec.podIdentity.identityId を設定していなかったことが原因でした。 KEDA 公式ドキュメント に「identityId はオプション」と書いてあるため、「identityId を設定しない場合、KEDA の認証・認可にはスケールターゲットのサービスアカウントを利用される」と勘違いしてしまい、TriggerAuthentication リソースの spec.podIdentity.identityId を設定せずに動作確認していました。

OSS 版 KEDA では Helm によるインストール時に identityId をフラグで設定することができますが、KEDA アドオンで作られるコンポーネントは直接編集することはできません。 そのため、KEDA アドオンでは TriggerAuthentication リソースで IdentityId を必ず指定する必要があるようです。

おわりに

GA されたばかりの KEDA アドオンについて紹介しました。 アドオンとして公開されたということで、安心して KEDA を使えるようになりましたね。 ぜひ皆さまも AKS で KEDA を使ってみてください!