APC 技術ブログ

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

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

Azure FunctionsをAKSで動かす

Azure Functions on AKS

はじめに

Azure FunctionsがKubernetes上で実行できるのをご存知でしょうか。 Azure Functionsの公式ドキュメントにも記載があります。

docs.microsoft.com

ドキュメントで紹介はされているけど、実際はどうなんだろう?というテーマで今回試してみました。その内容と大小さまざまなつまづきがありましたのでそうした情報も合わせて紹介したいと思います。

今回実施した内容はHTTP Triggerアプリケーションを動かす、というものです。

KEDAのインストール

ドキュメントにある通り、Azure FunctionsはKEDAを利用して実現しています。 KEDAはオートスケーリングを実現するミドルウェアで、Podを0からnまでのスケーリングを 実現することができます。ということでまずはKEDAをインストールです。。

keda.sh

このサイトで記載されている手順に沿って実施すれば簡単にインストールできます。

export NAMESPACE="keda"
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
kubectl create namespace ${NAMESPACE}
helm install keda kedacore/keda --namespace ${NAMESPACE}

func kubernetes install でKEDAをインストールすることもできるようですが、 今回はあえてHelmを使ってインストールしています。

Functionの作成

アプリケーションは今回Javaを利用することにしました。

docs.microsoft.com

こちらに書いてあるとおり、

mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=11 -Ddocker

と実行すれば作成できます。

Javaを選んだのは特に理由はなかったのですが、今回引っかかった原因の1つが言語選択にありました。

Java以外の言語の場合 func init コマンドでプロジェクトの作成ができます。また、コンテナ用のDockerfileもfunc init で作成されます。VSCode上のExtensionでも同様です。しかしJavaだけは例外でした。 アプリケーションコードは他の言語同様作成できるのですが、Dockerfileは作成してくれません。 func init --docker-only を使っても同様で、Javaの場合は .dockerignore ファイルしか作成してくれません。 Javaを利用する場合は、上記の mvn コマンドを使って作成しなければなりません。

Javaを選択される方はこの点ご注意ください。

Functionのデプロイ

特に作成されたコードを変更することもせず、デプロイを実行してみます。 デプロイにはローカル環境にDocker CLIがインストールされている必要があります。この点もご注意ください。 事前にACRもご用意ください。

# 事前にACRにログインし、dockerコマンドでPushできるようにする
az acr login -n ${ACR_NAME}


func kubernetes deploy --name java-fn --registry ${ACR_NAME}.azurecr.io

func kubernetes deployでは 1. docker build 1. docker push 1. kubernetes apply

が実行され、AKS上に - Deployment (name: <func-name>-http) - Service/Loadbalancer (name: <func-name>-http) - Secret (name: <func-name> ) といったリソースが作成されます。

ここで、お気づきの方もいるかもしれません。 KEDAどこで使っているの?そもそもPodのスケールってどうなっている?

そうなのです。ここまできて大きな落とし穴がありました。

HTTP トリガーを公開する Azure Functions は使用することはできますが、KEDA では直接管理されません。
HTTP Triggerのサポート

そうなのです。通常のFunctionsのようなスケーリングまでは func コマンドで対応していないのです。

ではどうするか。自分自身で

  • Horizontal Pod Autoscaler のYAMLを追加する
  • KEDAのHTTP Triggerの機能を利用してスケーリング設定を行う

といった方法で対応しなければなりません。 (ちゃんとはじめからドキュメントを読み込んでいれば実行する前に気づいたことではありますが・・・)

この対応であれば、HTTP以外の部分でFunctionsのBindingを行わない限り、わざわざFunctionsを使う必要性はなさそうですね。

KEDAを使ってHTTP Trigger Autoscale

KEDA本体は実はHTTP Triggerをサポートしていません。これまではPrometheus Triggerを使うのが一般的でした。 今回KEDAを調べていくと、KEDA HTTP Add-on というものが存在することに気づきました。

方式は上記Webページにあるように 前段に Ingress Serverを挿入、Backendに keda-http-interceptor を挟んで、Function Serviceに接続します。

せっかくですから、こちらのAdd-onを使ってみようと思います。

インストールは helm で実行できます。ここで注意事項。Add-onとアプリケーションは同じnamespaceにデプロイしてください。(HTTP add-onの制約事項です)

helm install --create-namespace -n ${NAMESPACE} http-add-on kedacore/keda-add-ons-http

前段に Ingressやinterceptorが挟まるので Functions の service-type をCluster IPに変更してデプロイし直します。

# 前にデプロイしたFunctionを削除
kubectl delete deploy <func-name>-http
kubectl delete service <funct-name>-http
kubectl delete secret <func-name>

# あらためて ${NAMESPACE} にデプロイ
func kubernetes deploy --name java-fn --registry ${ACR_NAME}.azurecr.io --service-type ClusterIP --namespace ${NAMESPACE}

Service TypeがLoadBalancerからCluster IPに変わっていると思います。

続いてAdd-on用の設定です。設定内容はAdd-onのサンプルを参考にさせていただきました。

github.com

最初は公開用のIngressの設定です。こちらからAdd-onのinterceptorにリクエストを転送します。 (name, host等はご自身の環境に合わせて変更してください)

ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: <func-name>-ingress
  namespace: ${NAMESPACE}
  annotations:
    kubernetes.io/ingress.class: addon-http-application-routing
    # nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: yourhost.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: keda-add-ons-http-interceptor-proxy
            port:
              number: 8080

続いてAdd-onの設定です。HTTPScaleObjectの設定内容の詳細は ドキュメント を参照してください。

httpscaledobject.yaml

kind: HTTPScaledObject
apiVersion: http.keda.sh/v1alpha1
metadata:
  name: <func-name>
  namespace: ${NAMESPACE}
spec:
    host: yourhost.example.com
    scaleTargetRef:
      deployment: <func-name>-http
      service: <func-name>-http
      port: 80
    replicas:
      min: 0
      max: 5

設定が正しければ、先にデプロイしていたFunctionのPodが0にスケールインすると思います。 うまくいかの場合は以下の点を見直してください。

  • HTTPScaleObjectのdeploymentやservice, portの内容
  • それぞれをデプロイしたnamespace (同一でなければなりません)

Podが0の状態でリクエストを受け付けた場合、検証した環境ではPodされるまで15秒程度かかりました。 1からのスケールアウトの時とは起動までの時間が異なり長くなりますのでその点はご注意ください。

スケール数を変えてみる(そして不具合に遭遇)

最後におまけです。Scale数をMinimum 1に変更してみましょう。やり方は簡単。 httpscaledobject.yamlの内容を書き換えるだけ。

    replicas:
      min: 1

これをデプロイしたら完了・・・。のはずでした。ところがいくらやってもPodが0にスケールインしてしまう。 この問題を調べてみましょう。

kubectl describe HTTPScaledObject <func-name> -n ${NAMESACE} で確認してもなかなかわかりません。 実はHTTPScaledObjectは内部でScaledObjectを作成しています。レプリカ数等はこちらで管理しています。

kubectl get ScaledObject -n ${NAMESPACE}で確認すると <func-name>-app という名称で作られているのがわかります。こちらを確認すると・・・HTTPScaledObjectの変更内容が反映されていない。

これを手がかりにAdd-onのIssueを探してみると、ありました。

httpscaledobjects does not trigger a change in the corresponding scaledobjects. · Issue #323 · kedacore/http-add-on · GitHub

修正は完了しているようですが、まだ公開はされていないようです。 今回はとりあえず手動で一度ScaledObjectを削除してから再度HttpScaledObjectをデプロイします。

kubectl delete ScaledObject <func-name>-app -n ${NAMESPACE}
kubectl apply -f httpscaledobject.yaml

これで期待通りの値になりました。

HTTP Add-on は実環境で使うにはまだ少し早いかもしれません。 しかし、簡単にHTTP Triggerを使ったスケーリングが実現できるAdd-onとして今後に期待したいと思います。

いかがだったでしょうか。 Azure FunctionsをPaaS環境ではなく、AKS上で動かすという機会はあまりないと思いますので、これが何かの参考になれば幸いです。