Azure Kubernetes ServiceとKubeFlowのユーザ管理をAzure Active Directoryで統合

日立アメリカR&D IoT Edge Labの大崎です。今回Azure Kubernetes Service (以降AKS)とAzure Active Directory(以降AAD)を連携させ社内ユーザ向けDXを実現する方法について説明します。

## 目的

社内ユーザに提供するサービスでDXを実現するには、ユーザのニーズに合わせ迅速にユーザ追加やサービス追加を実施することが重要です。今回の想定は、AKSで構築したKubernetesクラスタ上に、クラウドネイティブなアプリケーションを稼働させて社内ユーザに利用してもらうことを考えます。
ユーザはアプリケーションだけにアクセスする場合もあれば、クラスタの低レベルなAPIを直接使用する場合もあります。このような状況では、環境を運用するアドミンは、クラスタやアプリケーションにユーザアクセスを許可するためにそれぞれにユーザ登録する必要があり、作業が煩雑です。
**AKSの特徴として、クラスタ構築コマンドのオプションを指定するだけで外部IdPとしてAADと連携することが可能です**([出典](https://docs.microsoft.com/en-us/azure/aks/managed-aad))。外部IdPによりユーザ管理を統合することで、AKSクラスタに加えてアプリケーションも含めた一括したユーザ設定ができます。クラスタやアプリケーションごとのユーザ設定作業が不要になるため、迅速化が期待できます。

本記事では、上記のAKSを使ったシステムの構築方法と動作確認結果を紹介します。AKSクラスタ上には、アプリケーションの一つとしてKubeFlowを構築します。

## KubeFlowとは

KubeFlowは、機械学習エンジニア向けの機械学習モデルのセルフサービス開発環境を提供するKubernetes上のマイクロサービスです。内部では、機械学習の開発環境であるJupyterのコンテナや、学習済みモデルの推論処理をリクエスト時に動的に稼働させるKnative、コンテナ間及び外部との間の通信をセキュアにするIstio、ユーザを各機能に誘導するためのダッシュボードなどが稼働します。

https://www.kubeflow.org/

KubeFlowについては弊社メンバーによる他の検証記事もございます。

- [KubernetesクラスタにKubeflowバージョン1.4をデプロイする](https://qiita.com/Akira_TANAKA/items/7bc4d9fcf12d9e4ee1f7)
- [KubeFlow上で機械学習の環境を用意し、MLFlowでモデルを保存する](https://qiita.com/Hiroyuki_OSAKI/items/9ab5cbbcb9365eed7fc5)


## 環境構成

Kubernetes上のアプリケーションを社内ユーザ(機械学習エンジニア)に提供するために、アドミンはアプリケーション(KubeFlow)とその土台となるKubernetesクラスタを構築する必要があります。アプリケーションにもKubernetesにもそれぞれ認証認可の仕組みがあり、社内ユーザに利用してもらう際にはユーザ登録作業が必要です。アプリケーション数やクラスタ数が膨大になると、個々にユーザ登録するのは煩雑です(下図)。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/8b925018-9137-83da-3c6f-cb36bb8abde0.png" width=400px/>

今回構築する環境では、AKS上のKubernetesクラスタとKubeFlowの両方が連携可能な認証システムをAAD上に作成します(下図の左の青い箱)。これによって、ユーザ登録作業はこのAADに一回だけ実施すれば良くなります。そのユーザ情報は、KubernetesやKubeFlowに反映され、双方に同一ユーザでログインが可能になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/8b8cb6b6-43fc-efff-3d95-bb0fa9e00b18.png" width=400px/>


## 使用端末および前提

準備が必要な機材は端末とAzureアカウントのみです。私は今回以下のMacbook端末を使用しました。WindowsでもLinuxでも実行可能です。

- 端末

| スタック | 摘要 |
|-|-|
| Hardware | Macbook Pro (15-inch, 2017 = Intel版) |
| OS | macOS Big Sur 11.6 |

- 必要なもの
  - Azureのアカウントおよび契約

## 構築手順

AKSクラスタとKubeFlow、およびAADの構築は以下の手順で実行します。

1. 初期状態は、Azureアカウントのサブスクリプションがあり、AKS用テナント(A)も存在する状態
2. 外部IdPとして利用する別のAADテナント(B IdP用ADテナント)を作成
3. IdP用ADテナントにユーザ/アドミングループを作成
4. Azure ADと連携したAKSクラスタを作成
5. Kubernetesクラスタ上にKubeFlowを構築

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/b7d802b3-518c-fb99-7c41-64fd586637c0.png" width=600px/>

後述のAKSとAADの連携は異なるテナント間でも構築できます。今回はAADのテナントを以下2つ使用します。

- A. AKSでKubernetesクラスタを構築するためのテナント (AKS用テナントと呼ぶ)
- B. 外部IdPとして利用するAADを設定するテナント (以降IdP用テナントと呼ぶ)

### 端末の準備(`az` CLIのインストール)

以下ドキュメントを参考に、端末に`azure-cli`をインストールします。
https://docs.microsoft.com/en-us/cli/azure/install-azure-cli

```sh
brew update
brew install azure-cli
```

以下のコマンドでAzureにログインします。ブラウザが起動して、自分のAzureアカウントで認証を済ませると、`az`コマンドの設定が完了します。

```sh
az login
```

### 外部IdPとして利用する別のAzure ADテナントを作成

IdP用ADテナントを以下のドキュメントに従い作成します。

https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-access-create-new-tenant

まず、Azureポータルから`myAzureKubeFlow`という名称のテナントを作成します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/035d5cc2-2341-0e3e-569b-9538f6c5e0a8.png" width=400p>

次に、作成したテナントに`az`コマンドでログインするためには以下のようにログインします。ブラウザが起動しますので、自分のアカウントで認証を済ませます。この作業によって、ユーザとしては同じなのですが、別のAADテナントを作ったことになります。

```sh
az login -t myAzureKubeFlow.onmicrosoft.com --allow-no-subscriptions

[
  {
    "tenantId": "xxxxx",
  }
]

```

上記のログイン時に表示される`tenantId`をメモしておきます。コマンドライン上で以下のようにメモリに残しておくと便利です。

```sh
myAadId=xxxxx
```

この新しいテナント`myAzureKubeFlow`には今何も構築されておらず、まっさらの状態です。以降、テナント`myAzureKubeFlow`にはActive Directoryのみを構築し、Kubernetesは構築しません。

### ADへのユーザ/アドミングループ追加

この節では、以降AKSクラスタとKubeFlowでログインを確認するために、２名のユーザをADに追加します。

各ユーザの情報は以下です。

| ユーザ | ユーザ名 | パスワード | Email |
|-|-|-|-|
| ユーザ1 <br/>(アドミングループに所属)| `MyFirstUser`<br/>`@myAzureKubeFlow.onmicrosoft.com` | (省略) | ユーザ名に同じ  |
| ユーザ2 | `MySecondUser`<br/>`@myAzureKubeFlow.onmicrosoft.com` | (省略) |ユーザ名に同じ |


**ユーザ作成**

以下２つのコマンドで２つのユーザを作成します。

```sh
az ad user create --display-name myUser1 \
                  --password <ユーザ1のパスワード> \
                  --user-principal-name "MyFirstUser@myAzureKubeFlow.onmicrosoft.com"
az ad user create --display-name myUser2 \
                  --password <ユーザ2のパスワード> \
                  --user-principal-name "MySecondUser@myAzureKubeFlow.onmicrosoft.com"
```

**`email`の追記**

Azureポータルで`myUser1`と`myUser2`の各ユーザの情報にアクセスすると、`Email`や`Alternate email`が抜けています。これでは後ほどKubeFlowのログインで失敗するため、そこに`User Principal Name`と同じ内容を書き写し、保存します。

| | |
|-|-|
| `email`の書き込み(ユーザ1、ユーザ2それぞれで必要) |保存後、以下のように確認できれば問題ありません。 |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/46d10b35-90cb-83ee-7dc8-4a8d6de5486b.png" width=400p> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/19722b29-979d-2c7b-84a4-3361b6fdb576.png" width=400p> |
| ユーザが２名追加されていることが確認できます。| |
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/fc49e4f2-59cf-f108-6e90-d3a88fda5002.png" width=400p> | |

**アドミングループ作成**

以下コマンドでAKSのアドミンのグループを作成します。

```sh
myadmingroup=myAzureKubeFlowAdminGroup
az ad group create --display-name ${myadmingroup} --mail-nickname ${myadmingroup}
```

このときの`objectId`を控えておきます。

```json
{
  "objectId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
}
```

```sh
myadminid=yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
```

**アドミングループにユーザを所属させる**

アドミングループにユーザ1を追加します

```sh
az ad user list | jq '.[] | "\(.userPrincipalName) \(.objectId)"'

```

ユーザ1に紐付いている`objectId`を以下のように指定して、ユーザ1をアドミンに設定します。

```sh
az ad group member add --group ${myadminid} \
                       --member-id <objectID>
```

これによって、自分のアカウントがアドミングループに追加されました。Azureポータル上も確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/d3460f4e-16bf-bf63-54f9-f03152bc56ad.png" width=400p>

ここまでがIdP用ADテナント作成の手順です。

## Azure ADと連携したAKSクラスタを作成

AKSでKubernetesクラスタを作成します。今回AADとの連携のために、以下のドキュメントで紹介されているAKS-managed Azure Active Directory integrationという方法を使います。`az aks create`コマンドを使ってADとの接続ができます。

https://docs.microsoft.com/en-us/azure/aks/managed-aad

まず、`az`コマンドでAKS用テナント(自分のアカウントにもともと紐付いていたテナント)に戻ります。

```sh
az login -t <AKS用テナント>.onmicrosoft.com --allow-no-subscriptions
```

AKSクラスタのリソース群を入れるリソースグループを新規作成します。

```sh
myenv=myAzureKubeFlow
myrg=${myenv}RG
az group create -l westus -n ${myenv}RG
```

リソースグループ内にAKSクラスタを作成します。このときのポイントは、**`--enable-aad`オプションを指定**し、`aad-tenant-id`オプションと`--aad-admin-group-object-ids`オプションにはそれぞれ **前述のIdP用テナントIDとアドミングループのIDを設定する**ところです。

```sh
mycluster=${myenv}Cluster
az aks create -g ${myrg} -n ${mycluster} \
--enable-aad --aad-admin-group-object-ids ${myadminid} --aad-tenant-id ${myAadId} --load-balancer-managed-outbound-ip-count 1
```

これだけでAADが連携されたクラスタが作成できました。正しく以下の"adminGroupObjectIDs", "tenantId"が設定されていることを出力から確認できます。

```json
{
  "aadProfile": {
    "adminGroupObjectIDs": [
      "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    ],
    "clientAppId": null,
    "enableAzureRbac": false,
    "managed": true,
    "serverAppId": null,
    "serverAppSecret": null,
    "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  },
```

ポータル上では以下のようにAKSクラスタの状態を確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/93d9693f-cf67-43e2-c166-cb734c8b8a98.png" width=400p>

### AKSクラスタのテスト(Kubernetesクラスタにアクセスする)

`kubectl`コマンドでAKSクラスタにアクセスします。まず、以下のコマンドで`kubectl`用プロファイルを読み込みます。

```sh
az aks get-credentials --resource-group ${myrg} --name ${mycluster}
```

以下のように`kubectl`を実行すると、まずはAzureアカウントの認証を実行し、その結果(認証済みのトークン)をプロファイルに追記することで作成済みAKSクラスタにアクセスに可能になります。

```sh
$ kubectl get nodes
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/993730d7-ad75-800d-6a83-b5d7535c488c.png" width=400p>

上記認証後、以下の出力が見られ、AKSクラスタにアクセスできていることが分かります。

```sh
NAME                                STATUS   ROLES   AGE   VERSION
aks-nodepool1-xxxxxxxx-vmss000000   Ready    agent   18m   v1.20.9
aks-nodepool1-xxxxxxxx-vmss000001   Ready    agent   18m   v1.20.9
aks-nodepool1-xxxxxxxx-vmss000002   Ready    agent   18m   v1.20.9
```

### AKSに静的なIPアドレスを付与する

外部からアクセスするための静的なIPアドレスをAKSのKubernetesクラスタに付与します。
まず、以下のドキュメントに従いパブリックIPアドレスを取得し、クラスタに割り付けます。

https://docs.microsoft.com/en-us/azure/aks/static-ip

まずパブリックIPアドレスを新規作成します。

```sh
az network public-ip create --resource-group ${myrg} --name myAKSPublicIP --sku Standard --allocation-method static --query publicIp.ipAddress -o tsv

XXX.XXX.XXX.XXX
```

コマンド実行後に取得できるIPアドレスを保持しておきます。

```sh
STATIC_IP=XXX.XXX.XXX.XXX
```

ClusterがパブリックIPを割り当てられるRoleを割り当てます。

```sh
aksPrincipalId=$(az aks list | jq '.[0].identity.principalId')
az role assignment create \
    --assignee ${aksPrincipalId} \
    --role "Network Contributor" \
    --scope /subscriptions/${subscription}/resourceGroups/${myrg}
```

テスト用に以下のリソースを`test.yaml`として保存し、以下コマンドでAKSクラスタにデプロイしてみます。

```test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: aks-helloworld-one  
spec:
  replicas: 1
  selector:
    matchLabels:
      app: aks-helloworld-one
  template:
    metadata:
      labels:
        app: aks-helloworld-one
    spec:
      containers:
      - name: aks-helloworld-one
        image: mcr.microsoft.com/azuredocs/aks-helloworld:v1
        ports:
        - containerPort: 80
        env:
        - name: TITLE
          value: "Welcome to Azure Kubernetes Service (AKS)"
---
apiVersion: v1
kind: Service
metadata:
  name: aks-helloworld-one
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-resource-group: myAzureKubeFlowRG
spec:
  type: LoadBalancer
  loadBalancerIP: <パブリックIPアドレス>
  ports:
  - port: 80
  selector:
    app: aks-helloworld-one
```

```sh
kubectl apply -f test.yaml
```

パブリックIPアドレスにアクセスすると以下のように表示されるので、静的なIPアドレスをKubernetes内のWebサービスに付与できたことが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/b61806c7-bd65-07e8-52aa-dc9c71ca20eb.png" width=400p>

確認が取れたので、テスト用Webサービスを削除しておきます。

```sh
kubectl delete -f test.yaml
```

ここまででAKSクラスタ構築が完了しました。すでにAADとも連携が完了しています。

## KubeFlowの構築

### KubeFlow連携のためのIdP用テナントの準備

AADのIdP用テナント`myAzureKubeFlow`に、KubeFlowが認証を依頼するための"App registrations"設定を作成します。

https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#add-a-client-secret

| | |
|-|-|
| `myAzureKubeFlow`テナントにWebポータルでアクセスし、`App registrations`を参照します。 |"New registration"をクリック。"kubeflow"というアプリを登録。 |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/86d5b1c1-13b2-4883-4275-53189f309b3d.png" width=400p>　| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/851a51fa-0f76-826e-c231-4005a061df9b.png" width=400p>　|
| "Certificates & secrets"からClient Secretsを作成。| 作成されたシークレット|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/b189db1e-e6a4-c897-512c-92482d9fbd4b.png" width=400p>　|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/a6a35e7a-c5e1-5a5c-8c49-819adadaf609.png" width=400p>　|

最後の`Value`と、Overviewに記載の`Application (client) ID = Client ID`を控えておきます。

**KubeFlow側のURIの追加**

| | |
|-|-|
| Authentication内のPlatform configurationsから `+ Add a platform`を選択し、|`Web`を選択。|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/0d4e1fc5-bfbe-19fc-3ca7-293111688a25.png" width=400p> |<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/fd424b36-21f5-e64a-9864-e36ea269e55f.png" width=400p>
| Redirect URIsに`https://<パブリックIPアドレス>/login/oidc`を設定します。|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/99c0ce2a-bdee-18b2-48dd-d22b4dba4754.png" width=400p> |



ここまででKubeFlow用準備が完了です。

### KubeFlowのインストール

KubeFlowのインストールにはkfctlコマンドを利用する方法とマニフェストを直接`kubectl`で流し込む方法があります。
今回は後者を利用しKubeFlowをAKS上にインストールします。

ドキュメントは以下を参考にし、まずは以下コマンドでKubeFlowのマニフェストのレポジトリをダウンロードします。
https://github.com/kubeflow/manifests#installation

```sh
git clone https://github.com/kubeflow/manifests.git
```

マニフェストの中身をIdP連携/HTTPS利用のために一部変更します。

まず、Azure ADにOIDC接続できるようにするために、OpenID Connectに関する以下のファイルを修正します。

`manifests/common/oidc-authservice/base/params.env`

```diff
- OIDC_PROVIDER=http://dex.auth.svc.cluster.local:5556/dex
+ OIDC_PROVIDER=https://login.microsoftonline.com/<ADのテナントID = myAadId>/v2.0
+ AUTHSERVICE_URL_PREFIX=
- OIDC_AUTH_URL=/dex/auth
+ OIDC_AUTH_URL=
- OIDC_SCOPES=profile email groups
+ OIDC_SCOPES=openid profile email
- REDIRECT_URL=/login/oidc
+ REDIRECT_URL=https:/<パブリックIPアドレス>/login/oidc
- SKIP_AUTH_URI=/dex
+ SKIP_AUTH_URI=
USERID_HEADER=kubeflow-userid
USERID_PREFIX=
USERID_CLAIM=email
PORT="8080"
STORE_PATH=/var/lib/authservice/data.db
```

以下ファイルに、AADで作成したシークレットを記載します。

`manifests/common/oidc-authservice/base/secret_params.env`

```diff
- CLIENT_ID=kubeflow-oidc-authservice
+ CLIENT_ID=<AADにおけるApp registrationsのClient ID>
- CLIENT_SECRET=pUBnBOY80SnXgjibTYM9ZWNzY2xreNGQok
+ CLIENT_SECRET=<Azure ADで作成したValue>
```

次にHTTPSを許可するため、以下の項目を末尾に足します。

`manifests/common/istio-1-9/kubeflow-istio-resources/base/kf-istio-resources.yaml`

```yaml
  - hosts:
    - '*'
    port:
        name: https
        number: 443
        protocol: HTTPS
    tls:
        mode: SIMPLE
        privateKey: /etc/istio/ingressgateway-certs/tls.key
        serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
```

初回ダッシュボードアクセス時にユーザ自身にNamespaceを自動作成させるために、以下のように編集します。

`manifests/apps/centraldashboard/upstream/base/parames.env`

```diff
- CD_REGISTRATION_FLOW=false
+ CD_REGISTRATION_FLOW=true
```

ここまでの編集が完了したら、`kustomize`コマンドを使用しKubeFlow用のマニフェスト`kubeflow.yaml`を作成します。この際に、[ドキュメント](https://github.com/kubeflow/manifests#installation)に記載の通り、最新の`kustomize 4`以降には対応していないようですので、[GithubのKustomizeレポジトリ](https://github.com/kubernetes-sigs/kustomize/releases/tag/kustomize%2Fv3.10.0)から3.x系の最新の3.10.0をダウンロードして使用します。

```sh
curl -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv3.10.0/kustomize_v3.10.0_darwin_amd64.tar.gz
tar zxvf kustomize_v3.10.0_darwin_amd64.tar.gz
./kustomize build manifests/example > kubeflow.yaml 
```

生成したマニフェスト`kubeflow.yaml`を`kubectl`コマンドを利用してAKSクラスタにインストールします。[ドキュメント](https://github.com/kubeflow/manifests#installation)に従い、`kubectl`が全リソース分完了するまで`while`ループを回します。

```sh
while ! kubectl apply -f kubeflow.yaml; do echo "Retrying to apply resources"; sleep 10; done
```

全リソースが適用されるとコマンドは終了します。

**インストール後の設定(HTTPSアクセス周り)**

KubeFlow自体はインストールできましたが、その上にパブリックIPアドレスをKubeFlow用のゲートウェイが使用するために以下のコマンドを実行します。

```sh
kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec":{"loadBalancerIP":"40.78.45.167", "type":"LoadBalancer"}, "metadata": { "annotations": { "service.beta.kubernetes.io/azure-load-balancer-resource-group": "myAzureKubeFlowRG"}}}'
```

パブリックIPの設定反映(ロードバランサからのIP払い出し)が完了するまで、以下コマンドで待機します。

```sh
kubectl get svc -n istio-system -w
NAME                    TYPE           CLUSTER-IP     EXTERNAL-IP           PORT(S)
istio-ingressgateway    LoadBalancer   10.0.130.154   <パブリックIPアドレス>   15021:30153/TCP,80:30836/TCP,443:32438/TCP,31400:32111/TCP,15443:31910/TCP   7m48s
```

HTTPSアクセスを許可するため、以下の内容の`<パブリックIPアドレス>`を書き換えファイル`certificate.yaml`を作成する。

```yaml
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: istio-ingressgateway-certs
  namespace: istio-system
spec:
  commonName: istio-ingressgateway.istio-system.svc
  ipAddresses:
  - <パブリックIPアドレス>
  isCA: true
  issuerRef:
      kind: ClusterIssuer
      name: kubeflow-self-signing-issuer
  secretName: istio-ingressgateway-certs
```

以下コマンドでそれを適用し、パブリックIPアドレス用のHTTPS用の証明書が発行されます。これにより、HTTPS経由でKubeFlowにアクセス可能となります。

```sh
kubectl apply -f certificate.yaml
```

以上で、KubeFlowのインストールが完了しました。

## 動作確認

AAD作成時に新規作成したユーザ1とユーザ2で A.KubeFlowとB.Kubernetesにそれぞれアクセスしてみます。

### A. KubeFlowへのアクセス

まず、登録されたユーザでKubeFlowへのアクセスを試します。

ユースケースとしては、一般的な機械学習開発者がKubeFlowのWebインターフェース上で開発作業する中で多くのケースが該当します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/9c23122d-4ee0-89a3-1134-9b28d2addcdc.png" width=400p>

ユーザ1のアカウントでKubeFlowにアクセスするため、HTTPSでパブリックIPアドレスにアクセスします。

`https://<IPアドレス>`

確認の結果、以下の通り、ユーザの初回ログイン時には初期画面が開き、**Namespace名を指定できました**。その後、ノートブックを作成し**機械学習開発の作業を開始できました**。AKSクラスタやKubeFlowに特段の設定は不要であり、AADのユーザ登録さえしておけば、この環境にログインできました。

| | |
|-|-|
| 初回ログイン(1) | 初回ログイン(2) | 
|  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/1850a663-51bc-89da-92e3-cf54111d10db.png" width=400p> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/a1b152a7-e8ec-dc48-1091-ceba176f5110.png" width=400p> |
| 初回ログイン(3 Namespace名指定) | ダッシュボード |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/a722cd0c-914a-b8db-7bb0-94a3e3a48e5d.png" width=400p> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/1e5d5f21-543d-c2de-f416-fad17dd58cc0.png" width=400p> |
| ノートブック作成後 | ノートブックへのアクセス |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/32cfe649-ae87-772b-44b3-23159a5b75f8.png" width=400p>| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/bdd51566-f413-dc91-5e25-68b52deb6bc9.png" width=400p>|

上記の作業において、Namespace`myfirstuser`がKubernetes上に作成されます。ユーザ2についても同様にログインして確認をしておきます。ログイン後に作成されたNamespaceは、下図のようにダッシュボードの左上のプルダウンから確認できます。この画面からわかるように、**ユーザ1もユーザ2も自分のNamespaceしかアクセスできませんでした**。

| | |
|-|-|
|ユーザ1で選択できるNamespace|ユーザ2で選択できるNamespace |
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/0d9ff70e-011c-1858-67bb-eef11d146d1b.png" width=400p>| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/4d2d801a-e437-a760-367a-f3db201e692f.png" width=400p> | 



### B. Kubernetesを使用する

次に、登録されたユーザでKubernetesへ直接アクセスできるかを確認しました。このアクセスパターンのユースケースとしては、たとえば機械学習開発者がInferenceのためのWebサービスを自分で構築するために`kubectl`を操作するといったケースがあります。

確認の結果、`kubectl`にて各ユーザでログイン後、**各ユーザが作成したNamespace内のリソースを正しく表示できました**。



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/25cf079f-5fd5-8d95-5978-95d30e75468c.png" width=400p>

AADのユーザ情報で認証し、`kubectl`を使ってKubernetesにアクセスします。

まず`az`コマンドでユーザ1としてIdP用ADテナントにログインします。

```sh
az login -t myAzureKubeFlow.onmicrosoft.com --allow-no-subscriptions
```

ブラウザが開いた後、ユーザ1のユーザ名、パスワードを入力します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/afd17a23-6d32-5b2b-8a3f-a095581aafa8.png" width=400p>

`kubectl`の設定をします。ここでは異なるテナント内のAKSにアクセスするため、`--subscription`を指定するのがポイントです。

```sh
az aks get-credentials --resource-group ${myrg} --name ${mycluster} --subscription ${subscription}
```

A.のログイン後であればNamespaceが作成されています。以下のコマンドで、自分のNamespace `myfirstuser`のリソースを表示してみます。

```sh
kubectl get all -n myfirstuser

NAME                                                   READY   STATUS    RESTARTS   AGE
pod/ml-pipeline-ui-artifact-5dd95d555b-265bn           2/2     Running   0          11m
pod/ml-pipeline-visualizationserver-6b44c6759f-s7cfr   2/2     Running   0          11m
(以下略)
```

ユーザ1はユーザ2のNamespaceにアクセスすることができます。これは、ユーザ1をアドミングループに追加したためです。したがって、以下のコマンドも成功します。

```sh
kubectl get all -n myseconduser
NAME                                                   READY   STATUS    RESTARTS   AGE             
pod/ml-pipeline-ui-artifact-5dd95d555b-265bn           2/2     Running   0          7m48s           
pod/ml-pipeline-visualizationserver-6b44c6759f-s7cfr   2/2     Running   0          7m48s  
(以下略)
```

次に、ユーザ2でのアクセスを確認します。上記と同じ流れでログインをしますが、ユーザ名・パスワードはユーザ2のものを使用します。

自分のNamespaceにアクセスし、内部のリソースを確認することができます。

```sh
kubectl get all -n myseconduser
NAME                                                   READY   STATUS    RESTARTS   AGE
pod/ml-pipeline-ui-artifact-5dd95d555b-265bn           2/2     Running   0          20m
pod/ml-pipeline-visualizationserver-6b44c6759f-s7cfr   2/2     Running   0          20m
```

ユーザ2からユーザ1のNamespaceにアクセスし、内部のリソースを確認することはできません。

```sh
kubectl get all -n myfirstuser
Error from server (Forbidden): pods is forbidden: User "MySecondUser@myAzureKubeFlow.onmicrosoft.com
" cannot list resource "pods" in API group "" in the namespace "myfirstuser" 
```

### C. アクセスコントロールの確認

ユーザ1とユーザ2という異なるユーザでログインし、AKSとKubeFlowそれぞれでどの範囲にアクセス可能か確認しました。

- #1 KubeFlowへのWeb UIを利用したアクセス (OpenID Connect経由でのシングルサインオン)
  - このタイミングでAKS内にNamespaceが作成される
- #2 `kubectl`を用いたAKSへのアクセス
  - #1の操作後であれば、Namespaceとそこへのアクセス権付与(RoleBinding)は完了しているため、自分のみがアクセス可能なNamespaceを利用可能。


KubernetesクラスタとKubeFlowでのアクセスコントロールの確認結果を下表にまとめます。

| ユーザ | アクセス手段 | アクセス先リソース | 成否 |
|-|-|-|-|
| ユーザ1(アドミングループ) | KubeFlow | ユーザ1のNamespace | 可能 |
| ユーザ1(アドミングループ) | `kubectl` | ユーザ1のNamespace | 可能 |
| ユーザ1(アドミングループ) | KubeFlow | ユーザ2のNamespace | **不可能** (他人であるため *1) |
| ユーザ1(アドミングループ) | `kubectl` | ユーザ2のNamespace | **可能** (アドミンが有効でアクセス権があるため) |
| ユーザ2 | KubeFlow | ユーザ2のNamespace | 可能 |
| ユーザ2 | `kubectl` | ユーザ2のNamespace | 可能 |
| ユーザ2 | KubeFlow | ユーザ1のNamespace | 不可能 (他人であるため *1) |
| ユーザ2 | `kubectl` | ユーザ1のNamespace | 不可能 (Namespaceアクセス権がないため *1) |

基本的にはアクセス手段に関わらず、ユーザが自分のNamespaceにのみアクセスできることが保証されています。例外として、`kubectl`はアドミンを考慮したアクセス権が付与されます。KubeFlowはNamespaceを基本的にユーザごとに割り当てますが、Contributorというアクセス権付与の仕組みを使えばNamespaceを複数のユーザで共有する設定もできます(*1)。

*1 https://www.kubeflow.org/docs/components/multi-tenancy/getting-started/#managing-contributors-through-the-kubeflow-ui

## 後片付け

### Kubernetesクラスタを削除する

作成したクラスタは以下のコマンドで削除可能です。

```sh
az aks delete -g ${myrg} -n ${mycluster}

Are you sure you want to perform this operation? (y/n): y
```

### IdP用テナントを削除する

AADの"Manage tenants"から作成したテナントを削除することができます。以下のようなチェックリストが出てきますので、App registrationsなどを終了させ、最後にテナントを削除します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/6a552b4f-864d-0dcb-94e6-74908449ef43.png" width=400p>

## 余談

### さらに自動化する (Azure Logic Appsによるユーザ登録)

AADはユーザ登録などのAPIを持っており、IPaaSサービス(integration platform as a service)である[Azure Logic Apps](https://azure.microsoft.com/en-us/services/logic-apps/)からでも呼び出しを自動実行することが可能です。Azure Logic Appsの追加使用により、以下のようなユーザ登録自動化も可能です。

- [Form](https://forms.office.com/)でユーザ登録依頼があったら、
- [Teams](https://www.microsoft.com/en-us/microsoft-teams/group-chat-software)上に通知＆承認待ちを表示し、
- ユーザ依頼と承認結果に基づいてAADのテナントにユーザを登録し、
- そのユーザをAAD内の適切なグループに配属し、
- 登録完了をTeamsに返答する

ユーザ登録後は、ユーザ自身が初回KubeFlowアクセス時に自動的にNamespaceが作成されるように設定(`CD_REGISTRATION_FLOW=true`)済みですので、管理者は特段の作業は不要です。

## まとめ

AKSのKubernetesクラスタとAADとの連携ができました。[AKS-managed Azure Active Directory integration](https://docs.microsoft.com/en-us/azure/aks/managed-aad)の方法を使用することで、**AKSのクラスタ作成のオプションにAADのパラメータを指定するだけで、スムーズに必要な連携を構築**できました。

さらに、AADでユーザ管理しておくことで、**KubernetesクラスタだけでなくアプリケーションであるKubeFlowのユーザ登録も一元化**できました。新規登録されたユーザは、KubeFlowにログインすると同時に、その後の`kubectl`により同一ユーザでKubernetesクラスタにアクセスも可能でした。

余談として、Azure ADへのユーザ登録自体もAzure App Logicを使えば自動化できることも紹介しました。私は日立アメリカR&D向けの機械学習開発環境の運用において、この仕組を利用してKubernetesへのユーザ登録の自動ワークフローも運用しています。

以上のように、Kubernetesとアプリケーションのユーザ管理を統合しておくことで、ユーザ登録のプロセスが迅速化でき、社内ユーザ向けDXの実現に一歩近づいたと考えます。このような改善の積み重ねていくことで、社内ユーザ向けDXを実現できると考えています。

### 謝辞

マイクロソフト社の阪本真悟様には、日頃よりAzureの様々なサービスについてご教示いただきました。本件に関してもAADの基本的な使い方から相談に乗っていただき、Azure Logic Appsのことも教えていただきました。御礼申し上げます。