プライベートでマルチテナントな AKS(Azure Kubernetes Service) を構築・運用する上で困ったこと

この記事ではプライベートな AKS を構築し、マルチテナント用のクラスタとして運用する時に困ったこととそれに対する対応策をつらつらと書いてます。

ただ AKS で困ることについて基本的には[公式ドキュメント](https://docs.microsoft.com/ja-jp/azure/aks/)にベストプラクティスがありますので、本記事ではあくまで公式ドキュメントを読んでも解決しなかったプライベートネットワークやマルチテナント運用特有の困ったことを書いてます。
(ドキュメントに関しては自分の読み込みが足らずに解決策が書いてあったらゴメンナサイ。)

また、困ったこと一覧の中でタイトルの横に書いてある(解決済み)と(暫定対応)についてですが、困ったけども既に問題なく解決済みのものは解決済み、一応対策はしているが暫定的な対応でありより良い方法に変えたいと思っているのものは暫定対応としています。

## 前提

- AKS をプライベート クラスターとして構築する
- AKS は複数のプロジェクトが利用し、プロジェクト毎に Namespace に紐づく操作権限を払い出す
- 社内ネットワークにおけるプライマリー DNS はオンプレミス上にある。
- 社内ネットワークから外部への接続はプロキシ経由

## プライベート AKS における kube-apiserver への名前解決ができない(解決済み)

まず[プライベート Azure Kubernetes Service クラスターを作成する](https://docs.microsoft.com/ja-jp/azure/aks/private-clusters)に従ってプライベートクラスタを作るわけですが、こうするとAKS の kube-apiserver へのプライベートエンドポイントが作られます。そしてその IP に対する名前が PrivateZone に登録されます。
ここで、前提にある通り会社の DNS はオンプレミスにあるので、AKS の各 WorkerNode の DNS でオンプレミスのものを設定していると PrivateZone にある kube-apiserver のFQDNが解決できずにアクセスできずに構築エラーとなります。
[DNS フォワーダーを使用しているオンプレミスのワークロード](https://docs.microsoft.com/ja-jp/azure/private-link/private-endpoint-dns#on-premises-workloads-using-a-dns-forwarder)における DNS フォワーダーがない状況です。

解決先としては当然上記ドキュメントにあるように Azure 上にDNS フォワーダーを構築すればよいのですが、目的は AKS を使うことでありこの 1 レコードのためだけに Iaas で DNS フォワーダーを立てて冗長性も考慮してといった形で管理するといったことはしたくありませんでした。
何なら FQDN を使用せずに何とか IP で直接 kube-apiserver にアクセスできないかとかいろいろ試しました。

### 解決策

kube-apiserver の FQDN をパブリック FQDN として登録した。
というのも構築を始めた当初はこのような設定はできなかったのですが、いつの間にか設定ができるようになっていました。感激。
これは[パブリック FQDN があるプライベート AKS クラスターを作成する](https://docs.microsoft.com/ja-jp/azure/aks/private-clusters#create-a-private-aks-cluster-with-a-public-fqdn)としてドキュメントに解決策があるもので記事の最初に書いたことと速攻矛盾してますが、当初は記事になくだいぶ悩んだ部分なので書かせてもらいました。

## プライベートネットワーク内のサーバ・端末から kubectl が使えない(解決済み)

プライベートな AKS を構築した後に接続する方法は[プライベート クラスターに接続するための選択肢](https://docs.microsoft.com/ja-jp/azure/aks/private-clusters#options-for-connecting-to-the-private-cluster)としてまとまっています。
ただし、最初は`az aks command invoke`コマンドならアクセスが可能だが kubectl コマンドを実施しようとしてもつながらないという状況になって少し困りました。

### 解決策

kube-apiserver の FQDN(.privatelink.japaneast.azmk8s.io or .hcp.japaneast.azmk8s.io)を no_proxy に登録する。
kube-apiserver はプライベート エンドポイントなので、プロキシから外部にでてからアクセスしても通信できないのは当然ですね。 
単純な話ですが、最初は上記の問題と合わせて名前解決や認証、ネットワークどこに問題があるかわかっていなかったので苦労しました。

## アプリケーション開発者の権限で意図しない Azure リソースが作られる(解決済み)

Namespace に紐づくリソースの操作権限しか持っていないアプリケーション開発者側の権限で意図しない Azure 側のリソースが作られてしまうことがあります。
例えば Service リソースを`type: LoadBalancer`で作られると、PublicIP を持つ LoadBalancer が作られてしまいます。
そもそもアプリの公開は AGIC 経由に限定したいので、Service リソースにおいて`type: LoadBalancer`そのものを使わせたくないです。

### 解決策

上記の Service の問題に限らず、セキュリティ的にアプリケーション開発者にしてほしくないことは[ポリシー](https://docs.microsoft.com/ja-jp/azure/governance/policy/concepts/policy-for-kubernetes)によって制御します。
[Azure Kubernetes Service 用の Azure Policy 組み込み定義](https://docs.microsoft.com/ja-jp/azure/aks/policy-reference)で最低限のものは用意されていますが、上記のように自分たちの運用次第でドキュメントにないことも適切に設定していく必要があります。
ただし、柔軟にポリシーを定義しようとしたときに現状 Azure Policy の AKS 連携におけるカスタム ポリシー定義はパブリックプレビューなので本番運用で使う場合は注意が必要です。
ポリシーは最終的に Open Policy Agent で管理されるので、自身で直接 Gatekeeper を導入するというのもありです。
ただしその場合はAzureのサポートがなくなり、運用の面でいうとできるだけマネージドなサービスを使った方が楽なので、そこはバランスを考える必要があります。

## AGIC で作成する ingress でホスト名の重複を制御できず、後から作られた ingress で横取りされてしまう(暫定対応)

AKS を利用した際に利用できるイングレス コントローラーとして[Application Gateway イングレス コントローラー(AGIC)](https://docs.microsoft.com/ja-jp/azure/application-gateway/ingress-controller-overview)があります。
これを利用すると Azure Application Gateway をイングレスコントローラーとして利用することができ、AKS 上で Ingress リソースが作成されると Application Gateway 側に自動で各種ルールを作ってくれます。
この時 ingress にてホスト名を指定しますが、重複するホスト名で ingress を作が作られると元からあった Application Gateway 側の設定が消えて後から作った ingress の設定で上書きされます。
このような挙動の場合は本番稼働中のアプリケーションと重複するホスト名で ingress が作られてしまうと、その本番稼働中のアプリにアクセスできなくなり障害になってしまいます。
同じプロジェクト内ならば使っている URL はかぶらないようにできるかもしれませんが、マルチテナントで運用している場合は他の Namespace の事はわかっていない想定なので意図せずに横取りしてしまうこともあり得る。
仮に全体として URL をすべて管理できていたとしても、他の Namespace のアプリケーションを簡単に停止できてしまうような権限の状態というのはよろしくないです。

### 解決策

Namespace 毎に作れるホスト名を事前に決めておき、それ以外のホスト名で作れないようポリシーで制御します。
これで少なくとも別プロジェクト(Namespace)の影響で自分たちの本番稼働中のアプリが停止してしまうということはなくなります。
ただ同じ Namespace であっても少しの設定ミスで他のアプリが停止してしまう状況というのは結構不安です。
Application Gateway を普通に使った際に直接重複するホスト名でリスナーを作ろうとすると重複するものは作れませんとエラーを吐くような挙動になっているので、AGIC 側でも既存の ingress を削除しないと重複するホスト名は作れないといったような挙動に設定できるようになってほしいなと思っています。

## AzureContainerRegistry で Namespace の単位で権限制御できない(暫定対応)

Kubernetes を利用する上で当然一緒に ContainerRegistry を準備することも必要になってきます。
Azure では [Azure Container Registry](https://docs.microsoft.com/ja-jp/azure/container-registry/)があり、これはプライベートとしてに構築できるし AKS と直接連携することもできます。
ただし、マルチテナントで運用する場合はコンテナレジストリ内のイメージもプロジェクト(Namespace)ごとに権限を設定したいがこれができない状況です。
最悪読み取りまではだれでも良いとしても、書き込み権限はそのプロジェクトのイメージに限定したいですがそれも難しいです。(本番稼働中のイメージが勝手に書き換わるということを防ぐため)

この問題に対しての解決策として一応 ACR のプレビュー機能で[リポジトリ スコープのアクセス許可を持つトークンを作成する](https://docs.microsoft.com/ja-jp/azure/container-registry/container-registry-repository-scoped-permissions)というものがありますが、リポジトリと Namespace の権限は若干違っていてやりたいことの実現にはかなわない状況です。
例えば 1 つの Namespace において、「sample.azurecr.io/project1/app1」「sample.azurecr.io/project1/app2」といった2つのイメージが使われているとします。
この時対象の Namespace のユーザーには「sample.azurecr.io/project1/\*」に一致するリポジトリへの push,pull 権限を付与といったようなことが行いたいですが、現状でこのような正規表現は利用できずあくまでリポジトリ名をフルパスで指定してトークンを作る必要があります。
プロジェクト次第で Namespace 内に大量のアプリ(イメージ)がある可能性もあるので、1 つ 1 つトークンを払い出して管理するのはクラスタ管理者、アプリケーション開発者共に結構大変です。

### 解決策

アプリケーション開発者にはコンテナの pull と push だけができる権限で払い出し、クラスタ管理者がさらに上位の ACR に対する管理者権限で、`az acr repository update --name $ACR_NAME --image $IMAGE --write-enabled false --delete-enabled false`を実行することで本番稼働中のイメージを固定します。
こうすることで本番稼働中のイメージがアプリケーション開発者の権限で意図せず書き換わるといったことはなくなります。
ただし、この運用はだいぶ負担があるのでできればやめたいと思っています。プレビュー機能の成熟に期待です。



## まとめ

プライベートなクラスタを作るという場合は、構築時に考慮しなければらならない点が増えて大変ですがなんとかなってます。
ただマルチテナントの方は、そもそも Kubernetes を運用する際に課題がまだまだあるという状況なので、AKS でもそこは厳しくある程度運用でカバーという形になってしまってます。
同じような問題で悩んでいる人の助けになれば幸いです。
また、同じような問題で悩んでいたが別の方法で解決しているというものがあれば教えてもらえるとめちゃくちゃ助かります。

そして本記事で書いている内容は記事作成時はプレビューとなっているものも多いです。
記事を読んだタイミングでは開発が進んでいて状況が変わっている可能性も高いのでその点は注意してください。