AKS のApplication Gateway Ingress Controller (AGIC) を利用してみる

ここしばらくAzureでAKS周りを弄っていました。
Azure自体は不慣れだったのですが、いくつか落とし穴にハマりつつもWebUIでポチポチやってるだけで大体のことはできちゃいました。
目新しいこととしては現在プレビュー段階ですが、AKSのApplication Gateway Ingress Controllerを試しに使ってみたのでそちらを紹介したいと思います。

# Ingressとは

KuberenetesにはIngressというクラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトがあります。
Ingressは負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供します。
※[kuberenetes公式ドキュメント](https://kubernetes.io/ja/docs/concepts/services-networking/ingress/)から抜粋

KubernetesにはLoadBalancerというAPIオブジェクトもありますが、

- 1つのLBからPath等から複数のターゲットへ振り分けたい（名前ベースの仮想ホスティング）
- SSL/TLS証明書をアプリではなくロードバランサーに終端させたい

などのニーズから、KubernetesにおけるHTTP(S)のエンドポイントにはIngressを利用するケースが多いと思われます。

# パブリッククラウド各社のIngressの対応状況

パブリッククラウドにおけるKuberetesサービスでIngressを利用する場合、
各社が持つマネージドなロードバランサーを生成しKuberenetes内のServiceオブジェクトと紐づけられます。
またSSL/TLS証明書は各社のマネージドSSL証明書で生成ないしインポートしたものを紐づけることができます。

具体例としては、以下が挙げられます。

- AWS
    - EKS: ALB(Application LoadBalancer)を利用する~~ALB Ingress Controller~~ Amazon Load Balancer Controller
- GCP
    - GKE: Cloud HTTP(S) ロードバランサを利用するGKE Ingress

こうなるとAzureの対応状況が気になってきますが、
Azureのドキュメントでは[Nginx Ingress Controllerを利用する方法](https://docs.microsoft.com/ja-jp/azure/aks/ingress-basic)がメインで紹介されており、
こちらはAKS内にNginx Ingress Controllerをデプロイして利用することになり、他社のマネージドLBが連携してくれる対応とは異なっています。

じゃあAzureのマネージドLBと連携できるIngress Controllerはないのか？となりますが、それも違います。
現在はプレビュー段階ですが、AzureのApplication Gatewayと連携する[Application Gateway Ingress Controller (AGIC)](https://docs.microsoft.com/ja-jp/azure/application-gateway/ingress-controller-overview)というものが存在します。
今回はそれを利用してみました。
※プレビューの説明にある通り、商用や本番環境での利用はお勧めされていません。私が今回試したのも検証環境になります。

# AGICのデプロイ方法

AGICのデプロイ方法は大きく分けて2種類あります。プレビューなためか、WebUIでデプロイすることは現状できないようです。

- AKSアドオン
- Helmデプロイ

[AGICのドキュメント](https://docs.microsoft.com/ja-jp/azure/application-gateway/ingress-controller-overview)にあるように、デプロイ方法によって複数クラスタへの対応状況が異なるようなのでご注意ください。
特に考えはないですが、今回は**AKSアドオン**を利用しました。

また手順的には以下の2パターンが紹介されています。

- グリーンフィールド（新規構築）
- ブラウンフィールド（既存AKS/AppGWの紐付け）

今回は[AKSアドオンによるグリーンフィールドへのデプロイ](https://docs.microsoft.com/ja-jp/azure/application-gateway/tutorial-ingress-controller-add-on-new)を紹介します。

# AKSアドオンによるAGICのグリーンフィールドへのデプロイ

前置きが長くなりましたが、ここからが本題になります。

まず準備物や決め事を整理します。
もし試すときに最後にまとめて削除したい場合は、既存のリソースグループを使用するより新規のリソースグループを作成して、
終わったらリソースグループごと削除するのが良いです。

| リソース | 今回の設定値 |
|:-----------|:------------|
| リソースグループ               | rg-1        |
| 仮想ネットワーク               | vnet-1      |
| AKS CNIプラグイン                  | AzureCNI  |
| AKS用サブネット *1                | aks-subnet  |
| ApplicationGateway用サブネット *2 | appgw-subnet |

*1: AzureCNIを利用する場合、AKS用サブネット内のIPを各NodeやPodが持つようになるのでサブネットの範囲によって配置できるNode, Pod数が決まってしまうので、拡張性を考慮して設計する必要があります。
*2
アプリケーションゲートウェイ サブネットには、アプリケーション ゲートウェイのみを含めることができます。 その他のリソースは許可されません。
というわけで専用のサブネットを作成してやる必要があります。

また、今回利用するApplication Gateway(v2)を利用する場合は宛先サブネットでTCP ポート 65200 ～ 65535の受信を許可しておく必要があります。
https://docs.microsoft.com/ja-jp/azure/application-gateway/configuration-infrastructure#network-security-groups
Azureではサブネットに対してネットワークセキュリティグループ(NSG)をアタッチすることができるので、
AKS用サブネットにアタッチするNSGの受信セキュリティ規則に以下のルールを追加してください。名前や優先度は任意です。

|項目|値|
|:---|:---|
|ソース | Service Tag |
|ソースサービスタグ | GatewayManager |
|ソースポート範囲 |* |
|宛先 |Any |
|宛先ポート |65200-65535 |

次に、AGICのAKSアドオンはプレビュー機能なためデフォルトでは有効になっていません。
AzureCLI等を利用して有効化する必要があります。ちなみに各種有効化コマンドを実行すると数分かかりました。

```shell:AzureCLI
# AKS AGICアドオンの有効化と確認
az feature register --name AKS-IngressApplicationGatewayAddon --namespace Microsoft.ContainerService
az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/AKS-IngressApplicationGatewayAddon')].{Name:name,State:properties.state}"

# Azure-CLIのAKSプレビュー機能の有効化と確認
az extension add --name aks-preview
az extension list
az extension update --name aks-preview
az extension list

# AKSのサービスプロバイダの更新
az provider register --namespace Microsoft.ContainerService
```

次に事前に作成していた各サブネットのリソースIDを取得します。AKSのデプロイコマンドのオプションに指定する必要があります。
WebUIからは確認できないようだったのでこちらもAzureCLIで取得しています。

```shell:AzureCLI
# aks-subnetのリソースIDを取得
az network vnet show --resource-group rg-1 --name vnet-1 --query "subnets[?name=='aks-subnet'].id" -o tsv
/subscriptions/12345678-90ab-cdef-ghij-klmnopqrstuv/resourceGroups/rg-1/providers/Microsoft.Network/virtualNetworks/vnet-1/subnets/aks-subnet

# appgw-subnetのリソースIDを取得
az network vnet show --resource-group rg-1 --name vnet-1 --query "subnets[?name=='appgw-subnet'].id" -o tsv
/subscriptions/12345678-90ab-cdef-ghij-klmnopqrstuv/resourceGroups/rg-1/providers/Microsoft.Network/virtualNetworks/vnet-1/subnets/appgw-subnet
```

最後にAKSのデプロイコマンドを実行します。

```shell:AzureCLI
# AKSのデプロイ
az aks create -g rg-1 \
    --network-plugin azure \        # CNIの指定
    --enable-managed-identity \     # ManagedIDの有効化
    -a ingress-appgw \              # AGICの有効化
    -n cluster-1 \                  # AKSクラスタ名の指定
    --attach-acr hogehoge \         # 連携するACR(AzureContainerRegistry)の指定
    --node-vm-size Standard_A2_v2 \ # インスタンスのサイズ
    --vnet-subnet-id  "/subscriptions/12345678-90ab-cdef-ghij-klmnopqrstuv/resourceGroups/rg-1/providers/Microsoft.Network/virtualNetworks/vnet-1/subnets/aks-subnet" \ # aks-subnetのIDの指定
    --appgw-name appgw-1 \ # 作成されるApplicationGatewayの名前
    --appgw-subnet-id "/subscriptions/12345678-90ab-cdef-ghij-klmnopqrstuv/resourceGroups/rg-1/providers/Microsoft.Network/virtualNetworks/vnet-1/subnets/appgw-subnet" # appgw-subnetのIDの指定
The behavior of this command has been altered by the following extension: aks-preview
AAD role propagation done[############################################]  100.0000%{
出力は割愛
}
```

これでしばらくするとノード等が作成され、AKSとして利用できるようになります。
ちなみに、AKSをデプロイすると新たにリソースグループ`MC_<resource-group-name>_<cluster-name>_<location>`(今回であれば `MC_rg-1_cluster-1_japaneast`)が作成され、
ノードやロードバランサー、Application Gateway、PVで作成するディスク等はそちらのリソースグループ配下に作成されます。
そのせいかrg-1の共同作成者のみでは権限不足で作成に失敗しました。結局必要最小限のRole/IAMが分からずかなり強い権限のユーザで作成してしまいました。
（ここは反省点なので、GAしてドキュメント等に明示されることを期待しています）

# AGIC を使用してサンプルアプリをデプロイする

ここからはAzureのドキュメント通りですが、サンプルアプリをデプロイするためにまずはクラスタの資格情報(kubeconfig)を取得します。

```shell
az aks get-credentials -n cluster-1 -g rg-1
```

kubectl get node等が通ることが確認出来たら、さっそくアプリとIngressの設定をデプロイしてみましょう。

```shell
kubectl apply -f https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetapp.yaml
```

上記のマニフェストからIngressの部分を見てみると、annotationに`kubernetes.io/ingress.class: azure/application-gateway`と指定しているのが分かりますね。

```yaml:Ingress

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

Ingressに割り当てられたIPを確認し、ブラウザやcurl等でアクセスできればOKです。

```shell:azureCLI
kubectl get ingress
```

ちなみにApplication Gatewayへのアクセス制御をする場合、Application GatewayにはNSGをアタッチできなかったので、Application Gateway用のサブネットにNSGをアタッチしました。
これが正しい方法なのかは…不明です。



[関連付けられた Application Gateway で AGIC アドオンを無効にする](https://docs.microsoft.com/ja-jp/azure/application-gateway/ingress-controller-disable-addon)ことも可能です。
なおAGICアドオンを無効にした場合、上記のコマンドのようにAKSのAGICアドオンとしてデプロイされたApplication Gatewayはデフォルトでは削除されます。
残したい場合はApplication Gatewayに付いている`created-by: ingress-appgw`というタグを削除しましょう。

```shell:azureCLI
az aks disable-addons -n cluster-1 -g rg-1 -a ingress-appgw 
```

もろもろ試し終わったらお掃除としてリソースグループを削除しましょう。配下のリソースが全て削除されます。
今回作成したリソース以外のものがある場合はそちらまで消えてしまうので実行しないでください。

```shell:azureCLI
az group delete --name rg-1
```

以上になります。

ちなみに[AKSアドオンによるブラウンフィールドへのデプロイ](https://docs.microsoft.com/ja-jp/azure/application-gateway/tutorial-ingress-controller-add-on-existing)も試してみましたが、2つの仮想ネットワークのピアリングがうまく行かず、
かなりのところまで作っていたAKSクラスタを泣く泣く削除してグリーンフィールドとして1から作り直しました。。

また、しばらくはこのAGICを使っていたのですが、アプリの挙動確認をしたところApplication GatewayがServer-Sent Eventsに対応していなかったらしく正常に動作せず、
ダメ元でNginx Ingress Controllerに切り替えたところそちらでは動いたのでAGICはお役御免となりました…
俺の苦労は何だったのか…？という無念をここで供養したところで終わりとさせていただきます。