APC 技術ブログ

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

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

Azure Application Gatewayを複数のAKSで共有する

f:id:thanaism:20210609112801p:plain

はじめに

Azureコンテナソリューショングループの髙井です。

私の部署ではAzureのクラウドネイティブ内製化支援サービスを提供しています。その一環として、私はAKSを中心としたAppアーキテクトを担当しています。

さて、これまで私はAZ資格取得Slackへの料金通知などについて比較的ライトな記事を書いてきましたが、今回はグッとAKS(Azure Kubernetes Service)に寄り添った内容です。

Azure Application Gatewayを複数のAKSで使いたい

f:id:thanaism:20210603185445p:plain
引用: https://docs.microsoft.com/ja-jp/azure/application-gateway/ingress-controller-overview

今回のテーマは、

(比較的お値段の張る)Application Gatewayを複数のAKSクラスタで共有で使っちゃおう!

になります。

これがどういうことかと申しますと、普通にAKSとApplication Gateway(以下AppGW)を連動させると1対1でしか使えません。するとAKSクラスタの数だけAppGWのお値段がかさんでしまうんですねー。

運用環境ならまだしも、テスト用にAKSクラスタをばんばこ立てまくって、それぞれにAppGWをおっ立てたりすると下手したらエンジニア1人分くらいの給料になってしまうかもしれません。

プランなど様々な条件にも依存しますが、イメージとしてAppGWを1か月放置してると数万円はかかると思っていただければよいかと思います。

ということで、AppGWの共有は急務です。

sharedモード

先ほど、普通にAKSとAppGWを連動させると1対1でしか使えないとお伝えしました。

「1対1でしか使えないのに『共有で使っちゃおう』って不正な方法?そんなの会社で使えないよ!」と思った方に朗報です。

ちゃんと正規の方法があります。Microsoft Docsにコッソリ書いてありますが、複数クラスタでの共有にはAGICのsharedモードというものを利用します。

さて、AGICという単語が出てきました。そもそもAKSでAppGWを普通に使う場合であっても、まずはAGICを理解する必要があります。

先ほどリンクを貼ったページ以外にも、公式のDocsではAGICの説明が複数ページにわたって盛りだくさんです。

いろいろなアップデートの歴史を経て、なんというかサクラダファミリアのようになっていますので、初見だと全体像を理解するのに苦戦するかと思います。

でもご安心ください。みなさんが迷わないよう、本記事ではsharedの利用シーンにフォーカスを当てて説明していきます。

AGICとはなにか

AGICは、Application Gateway Ingress Controllerの略です。

まず、Kubernetesにはそもそも2種類のロードバランサーの仕組みがあります。L4で動くLoadBalancerとL7で動くIngressです。AGICは、このうちのIngressリソースとしてAppGWを操作できるようにするためのものです。

もうちょっと説明します。AppGWはそもそもAzureのリソースです。Kubernetesのリソースではありません。一方、Kubernetes(AKS)ではご存じの通り、ymlファイルに定義を書いてリソースをデプロイしますね。

察しのいい読者の方なら分かったかもしれません。そうです。AGICを使えば、ymlをAKSにデプロイするだけでAppGWの設定が変更されるようになるということです。

AGICは超めんどくさい

いったんセットアップしてしまえば、普段AKSを操作するkubectlでサクッとAppGWの設定を書き換えることのできるAGICですが、そのセットアップが超ウルトラグレートデリシャスめんどくさいという問題があります。

しかも、sharedは通常よりさらにめんどくさいのです。ザッと書き出してみましょう。

  1. リソースグループを作る
  2. Virtual Networkを作成し、適切にSubnetで区切る
  3. Azure CNIを利用したAKSクラスタを2つ作成する
  4. Application Gatewayを作成する
  5. AGICのためのService Pricipalを作成する
  6. ローカルにHelmをインストールする
  7. AKSにAGICをインストールする
  8. sharedに関わる設定をする

……。

は?

いや、面倒くさすぎます。

人類はテスト用にクラスタをぽんぽん立てたり消したりしたいんです。いちいちVirtual Networkまわりの設定だとかも必要ですし、そもそもAppGWを必要としないAKSですらAzure初心者ならゲンナリすること請け負いです。

sharedうんぬんの前にまずAzureリソースが立たないことには始まりません。

退屈なことはBicepにやらせよう

そうです。もうわかりましたね。自動化です。DXです(単なる自動化はDXとは言わn)。

退屈なことはBicepにやらせましょう

Bicepについて詳しくない方は私と同じチームにいる本国Bicep開発陣とも面識のある方の書いた記事がありますので、ぜひそちらをご覧ください。

techblog.ap-com.co.jp

まあ、ざっくり言ってしまえば、今回AKSやAppGWを作るところまではBicepで自動化してしまいます。Bicepファイルは再利用性も高いので、死角はありません(個人の感想です)。

AzureにはもともとARMテンプレートというJSONファイルでデプロイを自動化するための仕組みがありますが、なんせJSONなので人類がまともに読めるようにできていません。

Bicepにより可読性は飛躍的に高まり、業務は効率化し、人生は好転しますので使わない手はないでしょう。(※Bicepは新しめの技術です。栄枯盛衰、仕様変更など各自お含みおきください)

こちらに完成したものをご用意しておきました

はい、長い前置きでしたが、今回sharedを使うにあたってのBicepファイルや必要なコマンドをまとめたリポジトリを作成しましたので、これに沿ってやっていきましょう。

github.com

【実行の前提条件】

ローカルマシンにAzure CLIがインストールされ、ログインされている。 合わせて、kubectljqおよびcurlがインストールされている。

アプリケーション名の指定

これからいくつかコマンドを叩くにあたって、基本となるアプリ名を環境変数に格納していきます。リソースグループ名やAKSクラスタ名がこれに準拠して作成されます。

ここでは仮にagicdemoを設定します。

appName=agicdemo

ついでに、ご利用のAzureサブスクリプション名も設定しておきます。

subscriptionName=<your-subscription-name>

利用可能なサブスクリプションの一覧は以下のコマンドで取得可能です。

az account list --query '[].name'

Azureリソースのデプロイ

必要なAzureリソースをデプロイします。まずは、先ほどのリポジトリをcloneしましょう。 submoduleを利用しているので、その初期化も同時に行います。

git clone https://github.com/ap-communications/agic-shared \
&& cd agic-shared \
&& git submodule update --init --recursive

それではAzureリソースをデプロイします。以下のリソースがデプロイされます。

  • 新規リソースグループ
  • 仮想ネットワーク
  • AKSクラスタ×2
  • Container Resistry
  • Container Insights
  • Log Analytics workspace
  • Application Gateway

AKSはノード数1でB2sインスタンス(AKSで使える最低スペック)を使用しています。まさしく庶民の味方です。 本当にミニマムですのでセレブな読者はどでかいインスタンスを使うようにBicepを書き換えてみてください。

az deployment sub create -f deploy.bicep -l japaneast -p appName=$appName

10分くらいかかると思いますが、ワンコマンドでリソースのデプロイまでが完了します。ガハハ、最高ですね。

Portalぽちぽち生活からオサラバしましょう。

Helmパッケージのインストール

はい、ここで大事なポイントです。

2021年6月現在、AGICには2つのインストール方法があります。アドオンHelmです。

sharedはHelmでしか使えません。

大事なことなのでもう一度言います。

sharedはHelmでしか使えません!

ということで、HelmとHelmパッケージをインストールします。

また、Helmはv3をインストールしてください。Debian系なら、以下です。

curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

他のインストール方法は各自で公式ページを参考にしてください。

helm.sh

パッケージをインストールします。

helm repo add application-gateway-kubernetes-ingress \
https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/ \
&& helm repo update

ちなみにAKSと1対1でしかAppGWを使わない場合は、Portalで「アドオンを使用する」というボタンをポチしてください。それで終わりです。

サービスプリンシパルとHelm用configの作成

Helmパッケージが適切にAppGWを操作できるようにサービスプリンシパルを作成し、それを組み込んだConfigファイルを作成します。

AGICにサービスプリンシパルを適用することで、Azureの操作権限を渡してあげるような形になります。とても煩雑なので、すべてのコマンドを連結しました

あなたは何も考えずにターミナルに貼り付けるだけでOKです(環境変数にもろもろ組み込んでconfigファイルに吐き出しています)。

if [ -f auth.json ]; then rm auth.json; fi \
&& az ad sp create-for-rbac --name $appName --sdk-auth > auth.json \
&& secretJSON=$(cat auth.json | base64 -w0) \
&& subscriptionId=$(az account list -o tsv --query "[?@.name=='$subscriptionName'].id") \
&& resourceGroupName=rg-${appName} \
&& applicationGatewayName=${appName}-appgw \
&& apiServerAddress=$(az aks show -g rg-$appName -n ${appName}-aks1 -o tsv --query fqdn) \
&& if [ -f helm-config-1.json ]; then rm helm-config-1.yml; fi \
&& if [ -f helm-config-2.json ]; then rm helm-config-2.yml; fi \
&& $(cat <<EOL > helm-config-1.yml
verbosityLevel: 3
appgw:
    subscriptionId: $subscriptionId
    resourceGroup: $resourceGroupName
    name: $applicationGatewayName
    shared: true
armAuth:
    type: servicePrincipal
    secretJSON: $secretJSON
rbac:
    enabled: true
aksClusterConfiguration:
    apiServerAddress: $apiServerAddress
EOL
) \
&& apiServerAddress=$(az aks show -g rg-$appName -n ${appName}-aks2 -o tsv --query fqdn) \
&& $(cat <<EOS > helm-config-2.yml
verbosityLevel: 3
appgw:
    subscriptionId: $subscriptionId
    resourceGroup: $resourceGroupName
    name: $applicationGatewayName
    shared: true
armAuth:
    type: servicePrincipal
    secretJSON: $secretJSON
rbac:
    enabled: true
aksClusterConfiguration:
    apiServerAddress: $apiServerAddress
EOS
)

※生成されたファイルは認証情報を含むので、.gitignoreに指定しています。

AKSクラスタへのインストールとサービスデプロイ

実は、上記で作成したHelmのconfigでshared: tureを設定しています。そして、それをAKSに適用にすると勝手にAzureIngressProhibitedTargetsなるものがデプロイされる仕組みとなっています。

その名の通り、Ingressの操作対象となるものを指定するリストなのですが、デフォルトではIngressによるあらゆるデプロイが無効な設定となっているんですね。

なぜ、こんなことになっているかというと、すでに別サービスで利用しているAppGWの設定をいきなり全部上書きする事故を防ぐためだと考えられます。

この状態のままでは何もできないので、新しいProhibitTargetを作成し、デフォルトのProhibitTargetを削除するということをします。以下のコマンドを打てば完了です。

az aks get-credentials -g rg-$appName -n ${appName}-aks1 \
&& az aks get-credentials -g rg-$appName -n ${appName}-aks2 \
&& helm install --kube-context ${appName}-aks1 -f helm-config-1.yml \
application-gateway-kubernetes-ingress/ingress-azure --generate-name \
&& helm install --kube-context ${appName}-aks2 -f helm-config-2.yml \
application-gateway-kubernetes-ingress/ingress-azure --generate-name \
&& kubectl apply -f prohibit-fugaginx.yml --context ${appName}-aks1 \
&& kubectl apply -f prohibit-hogeginx.yml --context ${appName}-aks2 \
&& kubectl delete AzureIngressProhibitedTarget prohibit-all-targets --context ${appName}-aks1 \
&& kubectl delete AzureIngressProhibitedTarget prohibit-all-targets --context ${appName}-aks2

参考まで、ProhibitTargetの中身はこのようになっています。書き換え禁止のホスト名を指定します。

apiVersion: "appgw.ingress.k8s.io/v1"
kind: AzureIngressProhibitedTarget
metadata:
  name: prohibit-hogeginx
spec: 
  hostname: hogeginx.com

それが終わったら、実際のサービスをデプロイします。分かりやすく(?)、hogeginx.comfugaginx.comのホスト名を設定してあげます。

&& kubectl apply -f hogeginx.yml --context ${appName}-aks1 \
&& kubectl apply -f fugaginx.yml --context ${appName}-aks2

ちなみに、hogeginxと名乗っていますが、中身はApacheです。このあとの疎通確認するときに2つともnginxだと分かりにくかったのでそうしています(fugaginxはちゃんとnginxです)。

【参考】hogeginx.ymlの中身

素のままのhttpdイメージを利用したPodを、Service/Ingressを通じてホスト名hogeginx.comで公開しています。

apiVersion: v1
kind: Pod
metadata:
  name: hogeginx
  labels:
    app: hogeginx
spec:
  containers:
  - image: httpd
    name: hogeginx-image
    ports:
    - containerPort: 80
      protocol: TCP

---

apiVersion: v1
kind: Service
metadata:
  name: hogeginx
spec:
  selector:
    app: hogeginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

---

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hogeginx
  annotations:
    kubernetes.io/ingress.class: azure/application-gateway
spec:
  rules:
  - host: hogeginx.com 
    http:
      paths:
      - path: /
        backend:
          serviceName: hogeginx
          servicePort: 80

疎通の確認

うまくいっていれば、200のHTTPステータスで応答が2つ返ってくるはずです。

※デプロイしてから1分程度待たないとAppGWの設定がまだ書き換わっておらず失敗するかもしれません。

publicIp=$(kubectl get ingress --context ${appName}-aks1 -o=jsonpath='{$..ip}') \
&& curl -I -H "Host: hogeginx.com" $publicIp \
&& curl -I -H "Host: fugaginx.com" $publicIp

はい、お疲れさまでした。無事にsharedモードで動かすことができました。

リソースの削除

最後に作成したリソースとサービスプリンシパルを削除して終わります。

az group delete -n rg-$appName --no-wait -y \
&& az ad sp delete --id $(cat auth.json | jq -r .clientId)

おわりに

はい、いかがだったでしょうか?

自動化の結果、あまりにもあっけなく疎通してしまうので、AGICの面倒さが伝わらなかったかもしれませんね。が、それが本望です。

クラウドも年々便利になってきて「ポチポチでサービス作れちゃいます」の世界観は広がってきてはいますが、少し気を利かせたことをしようとするとポチポチではちょっと厳しく相応の知識やスキルが必要な現実もあります。

今回紹介したBicepなどの便利なツールをガンガン活用して、快適なAzureライフを送っていただけると幸いです。

それではあなたのAzureライフが楽しくなるように!Stay Azure!バイバイ!