(仮) ブログ

Azure サービスを使っている中で便利そうなことを

Google Kubernetes Engine (GKE) に Azure App Service (Web Apps) をデプロイしてみる

Microsoft Build 2021 で、Azure Application Services が発表されました。Azure App Service や Azure Functions、Logic App、Event Grid、API Management を Azure ではない別クラウドやオンプレミスの Kubernetes 上で実行する機能です。

azure.microsoft.com

詳しくはこちらの動画で説明されています。なおかつオンデマンドセッションは登録なしで視聴できます。

mybuild.microsoft.com

公式ドキュメントには既に Azure Kubernetes Services (AKS) に App Service をデプロイするチュートリアルが公開されていて、こちらに沿って進めば AKS 上で App Service が動きます。

この記事では AKS ではなく Google Kubernetes Engine (GKE) に Azure App Service をデプロイするまでの手順を書こうと思います。

ただし、チュートリアルのドキュメントにもあるように公式では検証されていないので、自己責任でお試しください。また、今後サービスの更新に伴って挙動が変わる可能性もありますのであらかじめご了承ください。

現在、Arc での App Service は Azure Kubernetes Service でのみ検証されているため、Azure Arc 対応クラスターは Azure Kubernetes Service 上に作成してください。

流れ

大まかな流れは下記の通りです。

  1. GKE の作成
  2. 外部 IP (Premium) の作成
  3. Storage Class の作成
  4. Arc への接続
  5. App Service Kubernetes 環境の作成

必要なツールは下記の通りです。kubectl はgcloud 側で一緒にインストールしてくれたはず

AKS に Azure App Service をデプロイするチュートリアルを実施して、拡張機能のインストールはあらかじめ済ませている前提です。

GKE の作成

まずは GKE の作成が必要です。Google Cloud のコンソールからぽちぽちで作成します。

f:id:yoshioblog:20210605163321p:plain

作成後、"接続" から gcloud のコマンドをコピーして、ローカルのターミナル上で実行します。

f:id:yoshioblog:20210605163530p:plain

kubectl get nodes などを実行して GKE に接続できていることを確認します。

$ kubectl get nodes
NAME                                       STATUS   ROLES    AGE   VERSION
gke-cluster-1-default-pool-xxxxxx-k8q3   Ready    <none>   14m   v1.19.9-gke.1900
gke-cluster-1-default-pool-xxxxxx-xlff   Ready    <none>   14m   v1.19.9-gke.1900
gke-cluster-1-default-pool-xxxxxx-zb0v   Ready    <none>   14m   v1.19.9-gke.1900

外部 IP の作成

App Service の DNS に割り当てるパブリック IP を作成します。このパブリック IP は k8sロードバランサーにも関連付けられます。

f:id:yoshioblog:20210605164853p:plain

ちゃんと Premium で作らないと下記のようなエラーが出ます。

Events:
  Type     Reason                  Age                  From                Message
  ----     ------                  ----                 ----                -------
  Normal   EnsuringLoadBalancer    79s (x7 over 6m41s)  service-controller  Ensuring load balancer
  Warning  SyncLoadBalancerFailed  78s (x7 over 6m40s)  service-controller  Error syncing load balancer: failed to ensure load balancer: requrested IP "xx.xxx.xx.xxx" belongs to the Standard network tier; expected Premium

Storage Class の作成

拡張機能のインストール時にオプションで Storage Class を指定します。この既定値は "default" なのですが、GKE で作成される既定 Storage Class には "default" という名前の Storage Class は存在しません。このため、先に "default" という名前の Storage Class を作っておきます。

もちろん名前は "default" でなくても良いです。ここではチュートリアルで指定されていた名前をそのまま利用しています。

変更した場合は Azure Arc の App Service 拡張機能のインストール時にオプション指定を忘れないようにしてください。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: default
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
kubectl apply -f default.yaml

Arc への接続、App Service のデプロイ

あとはチュートリアルに書かれているとおりにコマンドを実行していくと、GKE 上に App Service がデプロイされます。

Arc への接続の部分で下記のような Warning が発生し "Connecting" でコマンドが終了する場合がありますが放っておくとそのうち繋がります。

調べたところ、"configagent-xxxx" という Pod 内の"config-agent" コンテナが動作していませんでしたが放って置いたら接続できました。

Please check if the azure-arc namespace was deployed and run 'kubectl get pods -n azure-arc' to check if all the pods are in running state. A possible cause for pods stuck in pending state could be insufficient resources on the kubernetes cluster to onboard to arc.

Unable to install helm release: W0530 09:51:45.920390   51282 warnings.go:70] apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
W0530 09:51:46.069423   51282 warnings.go:70] apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition

気になる人は一度 Pod を削除してもいいかもしれません。

% kubectl get pods -n azure-arc                                  
NAME                                         READY   STATUS    RESTARTS   AGE
cluster-metadata-operator-7cff574c4f-rzwfb   2/2     Running   0          16m
clusterconnect-agent-6dfd867c68-vjkth        3/3     Running   3          16m
clusteridentityoperator-fd498bf96-q5598      2/2     Running   0          16m
config-agent-d9fd45cd6-rh7dg                 1/2     Running   0          5m11s -> これ
controller-manager-8676dcdc6-5d6qs           2/2     Running   0          16m
extension-manager-6c44d6967d-4zjkc           2/2     Running   0          16m
flux-logs-agent-6596f58c56-c2rk2             1/1     Running   0          16m
kube-aad-proxy-65b8b649b6-spk4b              1/2     Running   3          16m
metrics-agent-5b9b94754f-t4txr               2/2     Running   0          16m
resource-sync-agent-f8c7c6b6b-l6tc5          2/2     Running   0          16m

App Service を GKE にデプロイするために自分が実行したコマンドをこちらに載せておきます。

適宜必要な箇所を変更してください。外部 IP を設定するのを忘れないようにしてください。

groupName=<リソースグループ名>
clusterName="${groupName}-cluster"
resourceLocation=eastus
extensionName=<Azure ポータル上で表示する拡張機能名> 
namespace=<拡張機能インストール名前空間>
kubeEnvironmentName=<App Service Kubernetes 環境の表示名> 
staticIp=<GCP External IP>
webAppName=<Web App 名>
customLocationName="google-cloud" 

az group create -g $groupName -l $resourceLocation
az connectedk8s connect --resource-group $groupName --name $clusterName
az connectedk8s show --resource-group $groupName --name $clusterName

workspaceName="${groupName}-workspace" 

az monitor log-analytics workspace create \
    --resource-group $groupName \
    --workspace-name $workspaceName

logAnalyticsWorkspaceId=$(az monitor log-analytics workspace show \
    --resource-group $groupName \
    --workspace-name $workspaceName \
    --query customerId \
    --output tsv)
logAnalyticsWorkspaceIdEnc=$(printf %s $logAnalyticsWorkspaceId | base64)
logAnalyticsKey=$(az monitor log-analytics workspace get-shared-keys \
    --resource-group $groupName \
    --workspace-name $workspaceName \
    --query primarySharedKey \
    --output tsv)
logAnalyticsKeyEncWithSpace=$(printf %s $logAnalyticsKey | base64)
logAnalyticsKeyEnc=$(echo -n "${logAnalyticsKeyEncWithSpace//[[:space:]]/}") 


az k8s-extension create \
    --resource-group $groupName \
    --name $extensionName \
    --cluster-type connectedClusters \
    --cluster-name $clusterName \
    --extension-type 'Microsoft.Web.Appservice' \
    --release-train stable \
    --auto-upgrade-minor-version true \
    --scope cluster \
    --release-namespace $namespace \
    --configuration-settings "Microsoft.CustomLocation.ServiceAccount=default" \
    --configuration-settings "appsNamespace=${namespace}" \
    --configuration-settings "clusterName=${kubeEnvironmentName}" \
    --configuration-settings "loadBalancerIp=${staticIp}" \
    --configuration-settings "keda.enabled=true" \
    --configuration-settings "buildService.storageClassName=default" \
    --configuration-settings "buildService.storageAccessMode=ReadWriteOnce" \
    --configuration-settings "customConfigMap=${namespace}/kube-environment-config" \
    --configuration-settings "logProcessor.appLogs.destination=log-analytics" \
    --configuration-protected-settings "logProcessor.appLogs.logAnalyticsConfig.customerId=${logAnalyticsWorkspaceIdEnc}" \
    --configuration-protected-settings "logProcessor.appLogs.logAnalyticsConfig.sharedKey=${logAnalyticsKeyEnc}"

extensionId=$(az k8s-extension show \
    --cluster-type connectedClusters \
    --cluster-name $clusterName \
    --resource-group $groupName \
    --name $extensionName \
    --query id \
    --output tsv)

az resource wait --ids $extensionId --custom "properties.installState!='Pending'" --api-version "2020-07-01-preview"


connectedClusterId=$(az connectedk8s show --resource-group $groupName --name $clusterName --query id --output tsv)

az customlocation create \
    --resource-group $groupName \
    --name $customLocationName \
    --host-resource-id $connectedClusterId \
    --namespace $namespace \
    --cluster-extension-ids $extensionId

az customlocation show \
    --resource-group $groupName \
    --name $customLocationName

customLocationId=$(az customlocation show \
    --resource-group $groupName \
    --name $customLocationName \
    --query id \
    --output tsv)

az appservice kube create \
    --resource-group $groupName \
    --name $kubeEnvironmentName \
    --custom-location $customLocationId \
    --static-ip $staticIp

az webapp create \
    --resource-group $groupName \
    --name $webAppName \
    --custom-location $customLocationId \
    --runtime 'NODE|12-lts'

チュートリアルでは AKS に接続するため、"envoy.annotations.service.beta.kubernetes.io/azure-load-balancer-resource-group=${aksClusterGroupName}" が入っていましたが、GKE ではこの部分は関係ないので削除しています。

確認

スクリプトの実行が問題なく終わると、Azure Portal 上にリソースが表示されます。

f:id:yoshioblog:20210605174029p:plain

f:id:yoshioblog:20210607082326p:plain

Google Cloud 側にもこの通り

f:id:yoshioblog:20210607082357p:plain

f:id:yoshioblog:20210607082412p:plain

おわりに

オンプレミスで Logic App の豊富なコネクタを利用できるのは魅力的です。

今はまだ機能としてはなさそうですが、今後の期待として Function 間の通信が Service Mesh で制御・監視できたら面白そうです。