こんにちは、ACS 事業部の埜下です。 ついに AKS の KEDA アドオンが GA されました!
パブリックプレビューになったのが昨年の2022年5月だったので、約1年半越しの GA となります。 そこで今回は KEDA アドオンを使って Azure Service Bus をトリガーとしたオートスケールを試してみました。
KEDA とは
Kubernetes Event-driven Autoscaling (KEDA) は、Kubernetes 用のイベント駆動型オートスケーラーになります。 「キューにメッセージが N 件残っている」とか「ストレージにデータが格納された」とか等のイベントをトリガーに Kubernetes 上の Pod の数を増減してくれます。
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 について登壇した資料がありますので、あわせてご覧ください。
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 を利用する
以降の手順では次の環境変数を使用しますので、環境に応じて値を変えてご利用ください。
# 動作確認で使用する環境変数 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 についてはこちらもご覧ください。
まずは、サンプルアプリケーションと 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 はすでにサポートが終了しています。
このアプリケーションには Azure Service Bus のキューにデータを追加するクライアントと、キューを参照して処理するワーカーが用意されています。 ワーカーはコンテナイメージが公開されているので AKS クラスタにデプロイして利用します。 一方、クライアントは Azure Service Bus の接続文字列を設定したうえでローカル環境でビルドして利用します。
ワーカー
まずはワーカーの Pod とサービスアカウントを作成します。
AKS でワークロード ID を利用する場合、以下の作業が必要です。
- サービスアカウント
azure.workload.identity/client-id
アノテーションで関連付けるユーザー割り当てマネージド ID のクライアント ID を指定
- Pod
azure.workload.identity/use: "true"
ラベルを付与
以下のマニフェストで名前空間とサービスアカウント、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.cs
の ConnectionString
に設定します。
リポジトリ直下で dotnet run --project .\src\Keda.Samples.Dotnet.OrderGenerator\Keda.Samples.Dotnet.OrderGenerator.csproj
を実行するとデータ投入数を尋ねられるので任意の数を入力します。
接続文字列の場合は特に権限設定もなくキューにデータを投入することができ、Azure Portal 上でもキューの状況を確認することができます。 すでにワーカーを AKS クラスタ上で動かしているのでキューが 1 つずつ処理されており、画像ではメッセージ数が 75 になっていますね。
以上で KEDA 動作確認の準備は完了です。
動作確認
お待たせしました! いよいよ KEDA の動作確認です。
KEDA でサンプルアプリケーション(ワーカー)の Pod をスケールさせるためにカスタムリソース TriggerAuthentication と ScaledObject を作成します。
TriggerAuthentication には KEDA がトリガー対象サービスにアクセスする際の認証情報を定義します。
今回はワークロード ID を使うため spec.podIdentity 配下に provider: azure-workload
と identityId: <ユーザー割り当てマネージド 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 にすることができます。
この状態でサンプルアプリケーション(クライアント)から Azure Service Bus に対して 100 件メッセージを送信してみましょう。 すると、Azure Service Bus をチェックしていた KEDA がキューの増加を検知してサンプルアプリケーション(ワーカー)の Pod 数をスケールアウトしていきます。 Pod 数 0 の状態から増えていき、最終的に 10 個の Pod が起動します。
Azure Service Bus のキューがなくなってもしばらくは 10 個のままですが、5 分ほどすると Pod が削除されていって最後にはまた 0 個になります。
以上が 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 を使ってみてください!