Multi Tenant OperatorでOpenShiftのマルチテナンシーを簡単に！

# はじめに
Kubernete/OpenShiftにおける「マルチテナンシー」の実現においては、いくつかの考え方が存在します。ここで言うマルチテナンシーとは、お互いに疎であるアプリケーションの集合体や、それらによって成立するシステムをなんらか物理的・論理的に隔離しつつ、クラスタ内でコンピューティングリソースを共有させることを指します。

特に、単一クラスタにおける最もわかりやすいマルチテナンシーの考え方としては`NameSpace`による分離です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5b28a445-3931-7a42-76a6-e910dc076b22.png)


ただし、多くのアプリケーションは複数の専用環境を求めます。具体的には開発環境（Dev）、テスト環境（test）、準本番環境（Stg）、そして商用環境（Prod）等です。各開発フェーズや用途に応じて複数の`NameSpace`が必要だと思います。

また、`MachinePool`を占有させることも考えられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7e1448e2-f145-1261-1a89-9c6bd651a925.png)


`MachinePool`は`Worker Node`をラベンリングしてまとめた存在です。AffinityルールやTaint/Tolerationの設定によって、特定の`Pod`しか特定のラベンリングがされた`Worker Node`にスケジュールされないようにするといったことが可能です。

:::note
KubernetesにおけるPodスケジューリング戦略まとめは以下がとてもわかりやすいです。
https://cstoku.dev/posts/2018/k8sdojo-18/
:::

最後は、クラスタ自体を分割し、特定のアプリケーション群やシステム、プロジェクト、環境毎に異なるクラスタを割り当てます。OpenShiftについては「Hosted Control Plane」の登場によって、管理クラスタ上で子クラスタ（Hosted Clusterという）達のControl Plane（`Master Node`で動いているコンポーネント達、略してCP）をホストし、更に[Advanced Cluster Management](https://www.redhat.com/ja/technologies/management/advanced-cluster-management)を活用してマルチクラスタの作成や管理が非常に容易になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c01c1157-409b-9f3a-5a6d-0d1bf0bd4e0e.png)


とは言え、やはりコンピューティングリソースの有効活用やインフラ集約率の向上を考える上で、すべてのシステムやプロジェクト毎に毎回Hosted Clusterを払い出す事は現実的ではないでしょう。単一クラスタの中で`NameSpace`を切り、適切なユーザ権限管理やリソース管理（`ResouceQuota`の設計）を実施した上で、テナント毎のリソース消費量とコストの可視化・最適化を目指したいところです。

ところが、この「適切なユーザ権限管理やリソース管理」やら「テナント毎のリソース消費量とコストの可視化・最適化」というのは言うは易く行うは難しです。じゃあ具体的にどうやって複数の`NameSpace`と複数のユーザを適切な権限で紐づけ、それらを横断して`ResouceQuota`の設計をするのか？

そこで「Multi Tenant Operator（略してMTO）」が何等か一助になってくれるのではないか？と思い試してみました。

# Multi Tenant Operator（MTO）とは
MTOは「[Stakater](https://www.stakater.com/)」という企業が提供するKubernetes Operatorです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e72e583d-b974-5620-5227-87903e5bb5bd.png)

同社のホームページを見ると、Kubernetesインフラの構築運用支援やPlatform Engineeringサービスを提供しているっぽいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8ba54a10-fd93-a399-e062-d4a48a2d3723.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6aafa9d2-32db-6d1f-8389-f3c1c05fef48.png)

スウェーデンのストックホルム、[David Bagares通り](https://sv.wikipedia.org/wiki/David_Bagares_gata)の一角に本拠地を構えているそうです。

さて、このMTOは単一クラスタにおけるマルチテナンシーを実現するためのCRD（Custom Resources Definitions）を提供し、クラスタに対して「テナント」という抽象化レイヤを提供してくれます。このテナントの中でOpenShiftユーザに対する権限の定義を行い、用意するべき環境を定義すると、いい感じに`NameSpace`を作成しつつユーザ権限とのマッピング（RBACのバインディング）をしてくれます。

## Red Hatの認定Operatorです
[Red Hatのブログ](https://developers.redhat.com/articles/2023/12/06/simplify-multitenancy-stakaters-multi-tenant-operator#)にもある通り、こちらのOperatorはRed Hatに正式に認定された [Certified Operator](https://catalog.redhat.com/software/container-stacks/detail/618fa05e3adfdfc43f73b126)です。

## 無料でも使えますが...
2テナントまでしか作ることができません（泣）。3つめのテナントを作成しようとするとこんなエラーがでます。

```
Danger alert:エラーが発生しました
admission webhook "vtenant.kb.io" denied the request: free tier limit reached, max allowed tenants: 2. to upgrade, please visit https://www.stakater.com/mto
```

本格的に利用したい場合は課金してください。料金プランは[公式サイト](https://www.stakater.com/mto)を御覧ください。1クラスタあたり795$/月だそうです。Enterpriseサポートはもちろん付いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8fbf5dc4-e84a-5f82-ec14-8ecb3d102793.png)


# MTOでテナントを作ろう
まずはものは試しということで、やってみます。

## OpenShiftクラスタの準備
その前にOpenShiftクラスタを用意してください。今回は東京リージョンに2Worker NodeのROSA HCP（Red Hat OpenShift Service on AWS with Hosted Control Plane）を作成しました。また、Cluster-admin権限を有するアカウントを使って以下のユーザを作成しました。RBACはまだ何も設定していません。
- `user0@example.com`
- `user1@example.com`
- `user2@example.com`
- `user3@example.com`
- `user4@example.com`

また、OpenShiftのコンソール画面「ユーザー管理」→「Group」から`exmaple-group`というGroupeを作成し、`user3@example.com`と`user4@example.com`をぶち込んでおきます。以下のYAMLをApplyするのでもOKです。
```yaml
apiVersion: user.openshift.io/v1
kind: Group
metadata:
  name: example-group
users:
  - user3@example.com
  - user4@example.com
```

さらに、Cluster-roleの「self-provisioner」削除してしまいます。この権限は、OAuth認証したユーザがProjectを自由に作成し、自身が作成したProjectに対するAdmin権限を持つ事ができるものです。

Cluser-admin権限を持つアカウントの「管理者向け表示」にて「ユーザー管理」→「Roles」と進んで、当該Cluster-roleを削除しておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/cf1abc44-06a7-c17a-c85e-07edb342bb00.png)


:::note
そもそもOpenShift自体を一通り触って理解を深めたいよ、しかも無料で！
そんな都合の良い話が、、、あるんです。無料で作成できるRed Hatアカウントがあれば、ROSA HCPのハンズオンが8時間×3回まで試す事ができます。しかも日本語で用意された「歩き方ガイド」も用意されていますので、お気軽にお試しください。
https://rheb.hatenablog.com/entry/202312-rosa-hcp-trial
:::

## MTOをインストールする
OpenShiftのOperatorHubで「multi tenant operator」と検索すると出てきます。全てデフォルト設定のままインストールしてください。MTOのコンポーネント群は自動的に作られる`NameSpace`（multi-tenant-operator）にDeployされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b36cb048-0dfb-2ae1-467d-5ad8ba6e7d05.png)

インストールが完了すると複数のカスタムリソース（CR）が作成できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/971e2266-3b96-d2b4-1f58-346627d59130.png)

## QuotaとTenantを作ってみる
先に今から作る環境の雰囲気を以下の絵で説明します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/2e06eced-8aeb-1edc-db30-b793b719e758.png)

なんだか見慣れないOpenShift環境のポンチ絵です。通常はOpenShiftクラスタの上にいくつかの`NameSpace`が切られているだけですが、今回は`Quota`と`Tenant`と呼ばれる抽象化レイヤが存在しています。

さらに、これから作成する`Tenant`においては以下の要件を実現させます。
- exampleという名前の`Tenant`を作成し、example-quotaという名前の`Quota`を参照・適用する
- 以下の`NameSpace`をexampleテナント配下に置いて管理する
    - example-dev
    - example-test
- exampleテナントに参加するユーザに対してそれぞれ以下の権限を付与する
    - **Viewer**: exampleテナント内のリソースの閲覧権限のみを付与される
        - `user0@exmple.com`
    - **Editor**: exampleテナント内に閉じてリソースの編集権限を付与される
        - `user1@exmple.com`
    - **Owner**: exampleテナントの完全な権限を付与される
        - `user2@exmple.com`
        - Group: `exmaple-group`
-  exampleテナントに参加する各ユーザは個別のSnadbox環境（`NameSpace`）を与えられ、Sandbox環境は各ユーザからは見えないようになる
    -  ただし**Viewer**権限しか無い`user0@example.com`にはSandboxは不要

`Quota`は、`Tenant`から必須で参照されるべきリソースであり、文字通り「`Tenant`が利用できるコンピューティングリソースの上限」を設定する事ができます。コンピューティングリソースと一言で言っても、かなり細かく設定できます。例を以下に示します。

```yaml
apiVersion: tenantoperator.stakater.com/v1beta1
kind: Quota
metadata:
  name: example-quota
spec:
  limitrange: #1Pod毎のリソース上限・下限を設定できます
    limits:
      - max:
          cpu: '1'
          memory: 1Gi
        min:
          cpu: 100m
          memory: 100Mi
        type: Pod
  resourcequota: #テナント全体で利用できるリソース上限を設定できます
    hard:
      configmaps: '50'
      requests.cpu: '2'
      requests.memory: 10Gi
      requests.storage: 20Gi
      gp2-csi/requests.storage: '10Gi'
      gp3-csi/requests.storage: '10Gi'
      #<storageClassName>/requests.storage: '上限容量'と書くと、StorageClass毎に上限を個別設定できます
      secrets: '50'
      services: '50'
      services.loadbalancers: '10'
```
`Tenant`として要求できるCPUやメモリ上限はもちろん、ストレージ容量についても`StorageClass`単位での個別設定が可能で、かなりきめ細かく設定できます。

:::note
なお、`ConfigMap`数や`Secret`数、`Service`数まで設定できます。しかしこれらの設定がイマイチどう意味があるのか自分にはわかっていないので、わかる方は教えてください。`NameSpace`にApplyできる`ConfigMap`数を制限してどういう意味があるのか...🤔
:::

また、`Pod`毎のリソース上限・下限も設定できます。上記のYAMLをApplyして`Quota`リソースをDeployします。

次に`Tenant`リソースを作成します。

```yaml
apiVersion: tenantoperator.stakater.com/v1beta3
kind: Tenant
metadata: #テナント名です。
  name: example
spec:
  accessControl:
    viewers: #テナント内のリソース閲覧権限のみを与えるアカウントを指定できます
      users:
        - user0@example.com
    editors: #テナント内のリソース操作権限を与えるアカウントを指定できます
      users:
        - user1@example.com
        #編集権限を付与したいアカウント名を入れてください
    owners: #テナント自体及び内部のリソースに対する操作権限を与えるアカウントを指定できます
      users:
        - user2@example.com
      groups: #以下Groupにはuser3, user4が所属しています
        - example-group
  namespaces:
    onDeletePurgeNamespaces: true
    sandboxes: #各ユーザ毎のSandbox環境（NameSpace）を作成し、それぞれのSnadboxが他ユーザからは見えない様にします
      enabled: true
      private: true
    withTenantPrefix: #テナント名-プレフィックスとなるNameSpaceを自動で作成します
      - dev
      - test
  quota: example-quota
  #テナントに適用するQuotaを指定します
```
このYAMLファイルもApplyして`Tenant`を作成します。`Tenant`が作成されると、以下の`NameSpace`が作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fca4f98b-f6a9-f95e-b091-7c66b1d36a2d.png)

各`NameSpace`の`metadata.labels`には`stakater.com/`が付与されているはずです。

## 各ユーザからの`Tenant`に対する操作の挙動を見てみる
まず初めに**Editor**権限を持つ`user1@example.com`でOpenShiftに再ログインしてください。「管理者向け表示」では、以下の通り`NameSpace`が確認できます。
-  `example-dev`
-  `example-test`
-  `example-user1-example-sandbox`

**Editor**権限を持っているTenant配下の`NameSpace`が見えています。また自身のSandbox環境も見えていますが、他ユーザのSandbox環境は見えてません。各`NameSpace`名の右端の縦三点リーダ︙をクリックしてみてください。`NameSpace`に対するadmin権限が無いため「削除」がグレーアウトしており選択できません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/19d48cdd-64b3-263c-8eb1-53f1e537c80b.png)

次に自身のSandbox内で適当な`ConfigMap`を作成してみます。

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: user1-configmap
  namespace: example-user1-example-sandbox
data:
  USER1_KEY: hogehoge
```
何の問題もなく作成できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/19e869ee-6d30-6dfa-2d9c-00698b15f719.png)

次に`example-dev`内に適当なアプリケーションをDepolyしてみます。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd-deployment
  namespace: example-dev
  labels:
    app: httpd
spec:
  replicas: 3
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - name: httpd
        image: registry.redhat.io/rhel8/httpd-24
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 300m
            memory: 1Gi
          requests:
            cpu: 200m
            memory: 500Mi        
---
apiVersion: v1
kind: Service
metadata:
  name: httpd-service
  namespace: example-dev
spec:
  ports:
  - port: 8080 
  selector: #紐づけるアプリケーションのLabelsを指定します
    app: httpd
  clusterIP: None
---
kind: Route
apiVersion: route.openshift.io/v1
metadata:
  name: httpd-route
  namespace: example-dev
spec:
  to:
    kind: Service
    name: httpd-service
  port:
    targetPort: 8080
```
こちらはApache Webサーバコンテナを3つの`Pod`で起動するDeploymentです。1`Pod`あたりCPU: 100ミリコア、メモリを500メガバイト要求し、それぞれの上限を200ミリコア・1ギガバイトとしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c1857ba0-2cf1-0a7c-f895-5d2f6a6c5393.png)

リソースを作成することができました。Apache Webサーバも問題なく起動している模様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a63286a1-d724-519b-f096-481d56bae823.png)


「開発者向け表示」のトポロジー画面で`Pod`数をどんどん増やしてみます。すると当該環境においては10`Pod`までしか増やすことができなくなりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8136707a-37e9-5a07-c4d8-d655562e8958.png)

コンソール画面にも「`Quota`を超えました」といった趣旨のエラーメッセージがでています。

今、exapleテナントにおけるコンピューティングリソース上限は
- CPU上限: 2コア 
- メモリ上限: 10ギガバイト
でした。そしてApache Webサーバコンテナの1`Pod`辺りのコンピューティングリソース利用については
- CPU: 
    - 要求: 200ミリコア
    - 上限: 300ミリコア
- メモリ:
    - 要求: 500メガバイト
    - 上限: 1ギガバイト
でした。今10`Pod`までレプリケーションしていますのでメモリについてはまだ余裕があるものの、CPUについては200ミリコア×10`Pod`≒2コアに達してしまい、これ以上リソース要求できない状態です。

では、`NameSpace: example-test`に対して、同じApache WebサーバのYAMLファイルをApplyしてみます。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd-deployment
  namespace: example-test
  labels:
    app: httpd
spec:
  replicas: 3
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - name: httpd
        image: registry.redhat.io/rhel8/httpd-24
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 300m
            memory: 1Gi
          requests:
            cpu: 200m
            memory: 500Mi        
---
apiVersion: v1
kind: Service
metadata:
  name: httpd-service
  namespace: example-test
spec:
  ports:
  - port: 8080 
  selector: #紐づけるアプリケーションのLabelsを指定します
    app: httpd
  clusterIP: None
---
kind: Route
apiVersion: route.openshift.io/v1
metadata:
  name: httpd-route
  namespace: example-test
spec:
  to:
    kind: Service
    name: httpd-service
  port:
    targetPort: 8080
```

いつまで経っても`NameSpace: example-test`の方では`Pod`が起動してきません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4996e7a2-b436-9e37-d888-99ed00e8e524.png)

`NameSpace: example-dev`にDeployしたPodの数を7まで減らします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3992a4aa-8f5f-59dd-10c9-17cb66ab2b12.png)

お、`NameSpace: example-test`の方の`Pod`が3つ起動しました。では`NameSpace: example-test`の方の`Pod`を3から4に増やすことを試みます。

増えません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/56de489a-e68c-9a0e-1209-1f1ec34ea1a6.png)

では、`NameSpace: example-dev`にDeployしたPodの数を7から6に減らします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9da3cd4f-6983-3cc5-5d23-15d93e4de55d.png)

両`NameSpace`の`Pod`数合計は常に10が上限です。つまり同じexampleテナント配下の2つの`NameSpace`間で`Quota`がシェアされていることがわかりました。

なお、現在のexampleテナント全体で利用されているリソース状況については、OpenShiftのコンソール画面でも確認することができます。「管理者向け表示」にて「管理」→「ResourceQuota」と進むと、「AppliedClusterResourceQuotas」というCRを発見できます。このインスタンス名「example」こそが、実はMTOで設定した`Tenant`の`Quota`そのものなんですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/3dd6b626-88c5-3f6b-c1eb-8a4c0a7a2248.png)

では、`NameSpace: example-dev`のApache Webサーバコンテナを3つまで減らし、`NameSpace: example-test`の方はDeploymentを削除し元に戻しておきます。

次に、`NameSpace`を作成しようと試みます。以下のYAMLをApplyしてみます。
```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sample
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/2c020687-8990-93e9-cc74-a055d0c94753.png)

駄目でした。**Editor**権限のユーザでは、勝手に`NameSpace`を追加することはできません。あくまで用意された`Tenant`及びその配下の`NameSpace`の範疇でリソースの編集権限を与えられているに過ぎず、`Tenant`そのものを拡張・縮小したりすることはできないようです。


次に`user0@example.com`で再ログインしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/29cc8e1b-227e-bb75-264c-e92a63587dc9.png)

**Viewer**権限しか無い`user0@example.com`にはSandbox環境は与えられません。
（´・ω・） ｶﾜｲｿｽ

先ほど`example-dev`にDeployしたApache Webサーバを確認する事は可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/090e1d2e-9839-429a-deb4-3c8bb898beff.png)

ただし、編集したり削除したりすることができません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/761dd9c5-4420-bdad-4446-2b73a6271e13.png)

編集メニューはすべてグレーアウトし、`Pod`数変更などもできません。`ConfigMap`を作成しようとしても、GUIに「作成」ボタンすら見えません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/91944c27-46a0-a425-3e4e-3ef939fdd721.png)

YAMLファイルをApplyしようとしても駄目です

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: user1-configmap
  namespace: example-user1-example-sandbox
data:
  USER1_KEY: hogehoge
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5b7d8cf8-b0e5-3d64-3456-b6607a4daefc.png)

Tenant配下の`NameSpace`に対する操作権限は本当に見るだけ（**Viewer**）の様です。

最後に、**Owner**権限を持つユーザ`user2@example.com`で再ログインします。
`NameSpace`を作成しようと試みますが、

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sample
```

やはり駄目です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/62eeb4ab-6a89-b250-ebe1-71746d42e862.png)

しかし、作成しようとする`NameSpace`に以下の様なラベルを付与してください。

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: sample
  labels:
    stakater.com/tenant: example
```

あら不思議！`sample`という新しい`NameSpace`を作成することができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ad216d16-e4d6-c4e9-ff24-3fcc53fcabd0.png)

再度`user1@example.com`でログインし直して同じ様にやってみても駄目です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1014043f-297d-c48a-60ae-f402a96104ae.png)

つまり、**Owner**権限を持つユーザであれば、`NameSpace`リソースの`metadata.labels.stakater.com/tenant: <テナント名>`を付与することで、テナント配下に`NameSpace`を追加することができます。しかしそれ以外の権限では追加できません。これが**Owner**と**Editor**の権限の差でした。

# ArgoCDのマルチテナンシーを確認
では、ArgoCD（OpenShift GitOps）とMTOの連携機能を見てみます。

## `IntegrationConfig`を変更

その前にMTOのカスタムリソース`IntegrationConfig`を弄る必要があります。Cluster-admin権限を持つアカウントの「管理者向け表示」で「Operator」→「インストール済みのOperator」と進み、MTOを選択、`IntegrationConfig`タブでインスタンス名：tenant-operator-configを確認、クリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e93299e5-504b-a4a8-5dc5-64c1fb0bcbdd.png)

YAMLの`spec.integrations`より下を以下の通り書き換えます。

```yaml
spec:
(中略)
  integrations:
    argocd:
        namespace: openshift-gitops
        #OpenShift GitopsのArgoCDインスタンスが存在するNameSpace(インストール後のデフォルト値)を入れてあります
  metadata:
    groups: {}
    namespaces:
      labels:
        argocd.argoproj.io/managed-by: openshift-gitops
        #Tenantに所属するNameSpaceに対し、OpenShift Gitops配下とするためのLabelを付与します
    sandboxes: {}
```

OpenShift GitOps Operatorインストール後に自動作成されるArgoCDインスタンスの所在する`NameSpace`を指定し、さらに各`Tenant`配下の`NameSpace`に自動的にラベル`argocd.argoproj.io/managed-by: openshift-gitops`を付与する設定をします。これにより各`Tenant`配下の`NameSpace`はArgoCDから管理できようになります。ラベリング対象は`NameSpace`以外にもユーザGroupやSandobox環境に対しても実施できるようです。

「保存」をクリックして設定を反映します。

exampleテナント配下の`NameSpace`に自動的にラベルが付与されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/83fa52b0-e6f8-06a6-5ed8-becd8e106fb1.png)

## `Extensions`の作成

更に`Extensions`というカスタムリソースを作成します。こちらは`Tenant`に紐づくArgoCDのプロジェクト（`AppProject`）を自動的に作成してくれる便利なCRです。

```yaml
apiVersion: tenantoperator.stakater.com/v1alpha1
kind: Extensions
metadata:
  name: example-extensions
spec:
  tenantName: example
  argoCD:
    onDeletePurgeAppProject: true
    appProject:
      sourceRepos: #Tenantで利用する任意のGitリポジトリのURLを登録できます
        - "https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git"
        #とりあえず今回は今回のDemo用リポジトリを入れました
      clusterResourceWhitelist: #GitOpsによるApplyを許可するリソース種別を明示的に指定できます
        - group: ""
          kind: ""
      namespaceResourceBlacklist: #GitOpsによるApplyを禁止するリソース種別を明示的に指定できます
        - group: ""
          kind: "Secret"
          #今回はSecretを明示的に禁止にしてみました。
```
こちらのManifest.yamlをCluster-admin権限を持つアカウントからApplyしておきます。ここまでの流れで本クラスタに適用されたCRは以下の通りかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/203eaca0-7dfb-7d54-91c9-568014f1d82e.png)

## OpenShift GitOps Operatorをインストール

さて、このままCluster-admin権限を持つアカウントにて、OpenShift GitOps Operatorもインストールしておきます。OperatorHubにて検索して、デフォルト設定でインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/590e5df0-029e-5f66-4c61-9d35d4d83cbd.png)

お〜！exampleという`AppProject`が自動的に作成されていますね〜

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/dfe1fd33-acbe-7877-5377-fcd6f7766935.png)

テンション上がってきました。では、**Editor**権限をもつ`user1@example.com`にてOpenShift GitOpsにログインしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/13509026-f824-b139-66be-395a104ef947.png)

タイル右上のリンクからOpenShift GitOpsのコンソール画面に遷移し、「LOG IN VIA OPENSHIFT」の方から、`user1@example.com`のID/PWにてシングルサインオンします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/403c5113-a017-9779-2ac3-d72134d96417.png)

「Setteings」→「Project」と進むと、`user1@example.com`が`AppProject: example`に参加していることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4617bf89-5041-ca4f-60dd-34de64c53c55.png)

では何かアプリケーションをDeployしてみましょう。Postgresqlコンテナを試しにDeployします。ArgoCDアプリケーションのYAMLは以下の通りです。

:::note
本記事で利用するManifestはすべて以下のGitlabリポジトリにおいているのでご参考にしてください。
https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git
:::

```application-fail1.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: postgresql-sample-fail1
spec:
  destination:
    name: ''
    namespace: example-dev
    server: 'https://kubernetes.default.svc'
  source:
    path: Postgresql/Manifests-fail1
    repoURL: 'https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git'
    targetRevision: main
  sources: []
  project: example
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions: []
```
このArgoCDアプリケーションがApplyするManifestファイルなのですが、`~/Postgresql/Manifests-fail1`以下には2つのManifestファイルを入れておきました。

```manifest.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgresql-pvc
spec:
  accessModes:
    - ReadWriteOnce #アクセスモードはStorageClassによって何が選べるか変わります。AWS EBSは仕様上RWOしか選べません
  storageClassName: gp3-csi
  resources:
    requests:
      storage: 11Gi #要求するStorage容量です。適切な容量を選択しましょう
  volumeMode: Filesystem
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql
spec:
  selector:
    matchLabels:
      app: postgresql
  strategy:
    type: Recreate
  template:
    metadata:
      labels: #Serviceからディスカバリーしてもらう為のKey:Valueをセットします
        app: postgresql
    spec:
      containers:
      - image: registry.redhat.io/rhel8/postgresql-15 #RedHatが公開している安心安全なpostgresqlコンテナイメージです。
        name: postgresql
        envFrom: #別ファイルから環境変数を読み込む様に指定します
        - secretRef: #Secretを参照するように指定します
            name: postgresql-secret #対象のSecretファイル名
        ports:
        - containerPort: 5432 #postgresqlのポート番号です
          name: postgresql
        volumeMounts:
        - name: postgresql-pv #作成されたPVの名称です
          mountPath: /var/lib/pgsql/data #永続ボリュームをマウントするべきパスです。これはLinux上で動くpostgresqlの仕様です。
      volumes:
      - name: postgresql-pv #PVCから作成するPVの名称を指定します
        persistentVolumeClaim:
          claimName: postgresql-pvc #利用するPVCの名称を指定します
```

```secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgresql-secret
type: Opaque 
stringData: #ユーザ名とパスワード、初期作成するDB名を指定できます
  POSTGRESQL_USER: user
  POSTGRESQL_PASSWORD: password
  POSTGRESQL_DATABASE: mydatabase
# 通常のApplication開発において、Secretを公開Repositoryに置くのはNGです。あくまでデモ用途です。
```
`Deployament`で設定する環境変数（Postgresqlのユーザ情報や初期作成するDB名）は`Secret`を参照するようにしました。ちなみに、YAMLにもコメントで記載している通り、通常のApplication開発において、Secretを公開Repositoryに置くのはNGです。`stringData`を使わず`Data`を使っても、それはBASE64デコードすれば誰でも平文に戻せるので、基本的に`Secret`をその他のManifestといっしょにGitリポジトリで管理するのはおすすめしません。

:::note
Hashicorp Vaultを使おう！
:::

では、ArgoCDのコンソール画面にて「CREATE APPLICATION」をクリックし、`application.yaml`をApplyして「CREATE」をクリックします。

アプリケーションがDeployできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5a09d788-b8b3-81d7-8eba-2f592bba1fa8.png)

まず、PVCを見てみると`storageClass: gp3-csi`に対して11ギガバイトの容量を要求しています。しかしexampleテナントにおいては10ギガバイトまでしか利用できませんので、`Quota`超過です。おっとうっかり。一度アプリケーションをDeleteしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a26d7e6d-f2c1-bf34-8ef1-7dc479b8910b.png)

では、Manifestを修正して5ギガバイトまでに直しましょう。修正後のManifestが格納されたリポジトリパスを参照するArgoCDアプリケーションは以下です。

```application-fail1.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: postgresql-sample-fail2
spec:
  destination:
    name: ''
    namespace: example-dev
    server: 'https://kubernetes.default.svc'
  source:
    path: Postgresql/Manifests-fail2
    repoURL: 'https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git'
    targetRevision: main
  sources: []
  project: example
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions: []
```

同様にArgoCDからApplyを試みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/50176e9c-b699-c751-81fc-f1d69b440802.png)

また駄目でした。そういえば、`Extensions`で`Secret`の同期を禁止していました。うっかりうっかり。テヘペロ。`Secret`がApplyできないと環境変数を参照できないため`Deployment`もApplyできません。先ほど同様、ArgoCDアプリケーションは削除しておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/946310ff-c4af-5b8b-3499-cfcfc38aa89f.png)

では、先に`NameSpace: example-dev`に`Secret`を手動でApplyしておき、Gitリポジトリからは`Secret`は除外しましょう。

```secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgresql-secret
type: Opaque 
stringData: #ユーザ名とパスワード、初期作成するDB名を指定できます
  POSTGRESQL_USER: user
  POSTGRESQL_PASSWORD: password
  POSTGRESQL_DATABASE: mydatabase
# 通常のApplication開発において、Secretを公開Repositoryに置くのはNGです。あくまでデモ用途です。
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a73b9d14-4e4e-b7a4-cbe0-5c2b05de9c7a.png)

コンソール画面のGUIからYAMLをApplyしました。仏の顔も三度まで。`Secret`を除外したManifest一式を参照するArgoCDアプリケーションは以下です。以下のArgoCDアプリケーションをApplyします。

```application-success.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: postgresql-sample-success
spec:
  destination:
    name: ''
    namespace: example-dev
    server: 'https://kubernetes.default.svc'
  source:
    path: Postgresql/Manifests-success
    repoURL: 'https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git'
    targetRevision: main
  sources: []
  project: example
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions: []
```
やったぜ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/425893a9-6f5a-a50d-1148-e25a234e59ca.png)

OpenShiftのコンソールからもPostgresqlコンテナがDeployできたことを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/f5db09bc-b4d9-e607-02aa-f31f2647c215.png)

なお**Viewer**権限しか持っていない`user0@example.com`からは、当たり前ですがArgoCDにログインできてもアプリケーションのDeployはできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a806133a-3af3-6bc6-3c7e-216a0249f95d.png)

また、他のユーザが作成したアプリケーションを参照することはできますが、削除もできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c8351c77-9cd6-eff3-07c8-2e87b8fe50b8.png)

このようにして、MTOを利用してArgoCDのマルチテナンシーまで実現できます。ArgoCDの`AppProject`に対するRBACと、MTOで設定した`Tenant`向けのRBACがいい感じに同期してくれます。

# NameSpaceにリソース配布してみる
さて、最後に`Tenant`配下の`NameSpace`に対して一斉にリソースを配布してみます。想像できる活用事例としては、

- コンテナイメージレジストリへのアクセス情報（`Secret`）を配布する
- 新しいプロジェクトが立ち上がったので、対応する`Tenant`を作成し、Platform Engineering的な文脈から、配下の`NameSpace`にサンプルアプリケーションやパイプラインを配布してあげる

などが考えられます。`Tenant`配下の`NameSpace`に一斉にリソースを配布できるのはかなり便利です。今回はDocker HubにあるプライベートレジストリからコンテナイメージをPullする為に必要な認証情報を配布したいとします。具体的には以下の`Secret`を配布したいと考えましょう。

```配布したいSecret.yaml
kind: Secret
apiVersion: v1
metadata:
  name: docker-pull-secret
data:
  .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJteS1kb2NrZXItdXNlcm5hbWUiLCJwYXNzd29yZCI6Im15LWFjY2Vzcy10b2tlbiIsImVtYWlsIjoibXktZW1haWxAZXhhbXBsZS5jb20iLCJhdXRoIjoiYlhrdFpHOWphMlZ5TFhWelpYSnVZVzFsT20xNUxXRmpZMlZ6Y3kxMGIydGxiZz09In19fQ==
type: kubernetes.io/dockerconfigjson
```
:::note
プライベートレジストリに存在するコンテナイメージをpullしてくる為には認証情報が必要です。そのためには管理者からアクセストークンをDocker Hub上で発行してもらう必要があります。

![スクリーンショット 2024-09-16 21.37.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/f0ef1fdd-763e-d291-51ee-03c071a25ba4.png)

管理者のDocker Hubアカウントで https://app.docker.com/settings/personal-access-tokens/create にアクセスするとアクセストークンを発行できます。ここで発行したアクセストークンをメモっておきます。

上記`Secret: docker-pull-secret`の`.dockerconfigjson:`以下の文字列は、以下の.jsonがBASE64エンコードされたものです。

```.json
{"auths":{"docker.io":{"username":"my-docker-username","password":"my-access-token","auth":"bXktZG9ja2VyLXVzZXJuYW1lOm15LWFjY2Vzcy10b2tlbg==","email":"my-email@example.com"}}}
```
さらに、`bXktZG9ja2VyLXVzZXJuYW1lOm15LWFjY2Vzcy10b2tlbg==`は`my-docker-username:my-access-token`をBASE64エンコードしたものです。え、なんか2回もBASE64エンコードしてめんどくさいなぁと思った方、ご安心を。こちらのSecretはOpenShiftのGUIで簡単に作成できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/f53ba9a9-7255-16b7-64af-269c9fae4c6f.png)

平文で`value`を入れれば同じ`Secret`ができます。なお、「パスワード」欄はアクセストークンを入れてください。この`Secret`を参照して`Deployment`等のリソースにてコンテナイメージをPullすることができます。例えば以下のように記載することで、プライベートレジストリに格納されたコンテナイメージを認証を経てPull、クラスタ上に`Pod`としてスケジュールすることができます。

```deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: <container-app-name>
        image: <your-docker-hub-username>/<your-image>:<tag>
      imagePullSecrets:
      - name: docker-pull-secret
```
:::

さて、こんな`Secret`を`Tenant`配下の`NameSpace`に配布したいです。

## `Template`を作成する
`Tenant`配下の`NameSpace`に配布したいKubernetesリソースを定義するカスタムリソース`Template`を作成します。例えば今回は以下のような`Template`を作成しました。

```template.yaml
apiVersion: tenantoperator.stakater.com/v1alpha1
kind: Template
metadata:
  name: docker-secret
resources:
  manifests:
    - kind: Secret
      apiVersion: v1
      metadata:
        name: docker-pull-secret
      data:
        .dockercfg: eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJteS1kb2NrZXItdXNlcm5hbWUiLCJwYXNzd29yZCI6Im15LWFjY2Vzcy10b2tlbiIsImF1dGgiOiJiWGt0Wkc5amEyVnlMWFZ6WlhKdVlXMWxPbTE1TFdGalkyVnpjeTEwYjJ0bGJnPT0iLCJlbWFpbCI6Im15LWVtYWlsQGV4YW1wbGUuY29tIn19fQ==
      type: kubernetes.io/dockercfg
```

`resources.manifests`以下に`Secret`のYAMLをそのまま貼り付けるだけです。この`template.yaml`をCluster-admin権限を持つアカウントにて、OpenShiftクラスタにApplyしておきます。

## `TemplateGroupInstance`を作成する

先ほど作成した`Template`を参照する`TemplateGroupInstances`をApplyすることで、任意の`Tenant`配下の`NameSpace`に先ほど示した`Secret`を配布することができます。

```templategroupinstance.yaml
apiVersion: tenantoperator.stakater.com/v1alpha1
kind: TemplateGroupInstance
metadata:
  name: docker-secret-group-instance
spec:
  template: docker-secret
  #作成済みのTemplate名を指定します
  selector:
    matchLabels: #ラベル（key: value)を指定すると、そのラベルが付いたNameSpaceにTemplateで定義したリソースが配布されます
      stakater.com/tenant: example
  sync: true
  #Templateの内容が変更されると自動的に反映される設定（True）
```

特定のラベルが付いた`NameSpace`にリソースを配布する事ができます。今回は`Tenant`配下のすべての`NameSpace`に同じ`Secret`を配布したいとします。すべての`NameSpace`には`IntegrationConfig`によって`stakater.com/tenant: example`が付与されていますので、これをしてします。例えばもし開発環境(dev）のみに配布したい場合は、`NameSpace: example-dev`にだけ付与されているラベル`stakater.com/kind: dev`を指定しても良いでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0f33b72d-4d1b-3af7-5260-57c14a1b98b1.png)

では`templategroupinstances.yaml`もCluster-admin権限を持つアカウントにてApplyしておきます。これまでの作業をしてきて、今クラスタ内に作成されたカスタムリソースは以下になっているでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7f799450-5ade-7c72-4bc2-c405abf479ea.png)

では`docker-pull-secret`は配布されたのでしょうか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/13bfae14-50ce-f778-3c47-6087ea5c5f93.png)

`user2@example.com`でログイン済みの状態で各`NameSpace`のシークレット一覧を見ると、すべての`NameSpace`に`docker-pull-secret`がきちんと配布されていました。

このようにして、`Template`と`TemplateGroupInstance`を使うと、簡単に指定した`NameSpace`に同じリソースを一元的に配布できます。各`Tenant`内で共通的に利用する認証情報や、サンプルアプリのパイプラインやマニフェストを配布することで、`Tenant`に参加するユーザにより良い開発者体験を提供できることでしょう。

# MTOコンソールでテナントのコストを可視化しよう
実はMTOにはコンソール画面を提供するコンポーネントがあります。先にどんな事がわかるのかを示します。

ログインすると、Dashboardに遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7e94579f-3849-e573-a51f-080370948e49.png)

ここでは、ユーザが参加している`Tenant`の情報が一元的に確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/db6f7700-e01e-676c-8b3b-6dd5b26b6640.png)


こんな感じで`Tenant`のリソース消費状況をチャートで見たりできます。`Tenant`配下の`NameSpace`ごとの利用状況も確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b37e0d96-c342-a238-36f4-de18f8d6d480.png)

`NameSpace`名をクリックすると、`NameSpace`毎かつワークロード種別毎のより詳細な利用状況が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5591adc4-5128-c5db-db43-414cfa84f759.png)


## MTOコンソールを有効化しよう
MTOのデフォルト設定ではコンソール機能が無効化されています。有効化は`IntegrationConfig`を編集することで可能です。Cluster-admin権限を持つアカウントの「管理者向け表示」にて、「Operator」→「インストール済みのOperator」と進み、MTOのカスタムリソース欄から`IntegrationConfig`を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/db41d89d-e80d-0ddd-5aac-666fe9ee02be.png)

「tenant-operator-config」のYAML編集画面にて以下のように変更してください

```integrationconfig.yaml
(中略)
spec:
(中略)
  components:
    console: true
    showback: true
(中略)

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d6ea4b97-b511-b431-0ce4-96c48bcbdfa0.png)

「保存」をクリックすると、`NameSpace: multi-tenant-operator`のトポロジー画面でMTOコンソール関連のコンポーネントが起動してきます。すべてのコンポーネントが起動してくるまでに2,3分かかるのでしばし待ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e6436eed-42b6-2694-2437-2fd7101dcadb.png)

左の塊がMTOコンソールを構成するコンポーネント達になります。MTOコンソール自体のURLは「tenant-operator-console」の`Route`で提供されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/aa3323d5-1271-256f-b6ce-32d9a3135edc.png)

クリックしてアクセスしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b85f0bbf-e5fd-9d46-5e3e-038e62a5ae62.png)

サインイン画面にはなりますが、ユーザを作成していないのでここから先には進めません。ちなみに、デフォルトユーザとして
- Username: `mto`
- Password: `mto`

でログインすることはできます。ただしデフォルトユーザは何の`Tenant`にも参加していないので、何も見れません。ユーザ登録の仕組みや認証の仕組みは別途MTOコンソールコンポーネントたちの中で起動してきた「Keycloak」を使って整える必要があります。

## Keycloakを設定変更してユーザ登録機能を開放する
MTOコンソールコンポーネント達の「tenant-operator-keycloak」の`Route`からKey Cloakログイン画面に遷移します。Adminのアクセス情報は以下です
- Username or email: admin
- Password: admin

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/abf1dc60-5ac1-8841-5b5d-60f5d9fd3b4c.png)


::: note
Keycloakは、オープンソースの認証基盤ソフトウェアです。OAuth 2.0、OpenID Connect、SAML 2.0 などの業界標準のプロトコルをサポートしており、既存のシステムや外部認証プロバイダー（Google、GitHub、LDAPなど）と簡単に統合できます。これにより、企業や開発チームが既存の認証基盤にKeycloakをシームレスに導入できる点が大きな強みです。認証機能を有する様々なソフトウェアに組み込まれています。

なお余談ですが、Red HatがEnterpriseサポートを提供している「[Red Hat Build of Keycloak](https://access.redhat.com/products/red-hat-build-of-keycloak)」はOpenShiftのサブスクリプションにエンタイトルメントされており、追加のサブスクリプション等を不要でOpenShiftの上で認証基盤が構築できます。OperatorHubから簡単にインストールして、Keycloakインスタンスを始めとしたカスタムリソースを簡単に作成・管理できます。OpenShiftを利用しているなら非常に幅広く使われている認証基盤ソフトウェアをEnterpriseサポート付きでタダで使えますので、何等かの認証機能を持たせたいシステムをOpenShift上で構築運用するなら、「Red Hat Build of Keycloak」は最有力候補のソリューションです。
:::

ここではKeycloak自体の機能の詳細は割愛しますが、ざっくり「Realm」という論理空間がKeycloakインスタンスの中には存在しており、このRealm単位でフロントエンドアプリのユーザ管理や外部プロバイダとの連携を実施します。既にMTOコンソールのためのRealm名「mto」が作られており、mto RealmにはClient（フロントエンド）として「mto-console」が登録済みです。確認しておきましょう。

左上からmto Realmに変えてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/5b04474e-cb4f-efe3-5e91-d646414d314d.png)

Clientsに「mto-console」があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/909d1710-0c0b-4d78-a72f-3757447df63b.png)

ただ、ここは特にいじりません。「Realm settings」の「Login」タブをクリックしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/04b84105-3f3a-8608-bfa5-d83645020139.png)

デフォルト設定だとMTOコンソールのログイン画面にユーザ登録機能がありませんので、ユーザ登録できるように機能を有効化してください。以下のスクリーンショットの通りにトグルスイッチをONにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/055600f5-1c36-2373-f999-1cf6d61a73e7.png)

トグルをONにするとすぐにMTOコンソールに反映されます。MTOコンソール画面を更新してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/a00c9eb9-ad20-f39c-ce75-34a52dce1c16.png)

## MTOコンソールのログイン認証をOpenShiftの認証機能にブローカーさせる

MTOコンソールのユーザ登録機能が有効化されました。ただ、できればOpenShiftコンソールのログインとSSOしたいので、IdP（Identity provider）としてOpenShift自体の認証機能を設定します。「Identity providers」をクリックし、「OpenShift v4」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/30e4561f-a2be-d0dd-49c6-2221d280f859.png)

ここに必要な情報を色々いれるとOpenShiftの認証機能にブローカーしてくれるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ff25281d-25fe-1ed6-bea0-5ff20fa3c038.png)

これらの情報はOpenShiftの認証機能側から払い出される値になるので、それを今から登録します。OpenShiftのコンソール画面にログインしようとしたり、OpenShift GitOpsで「LOG IN VIA OPENSHIFT」をクリックすると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/017df863-e205-7202-1e3f-1dc1d4fc723e.png)

この画面にいつも行くと思うのですが、これもOpenShift上で動いているアプリケーションです。こいつにクライアント側からリダイレクトしてもらう為には、`OAuthClient`というカスタムリソースを作成する必要があります。Cluster-admin権限を持つアカウントの「管理者向け表示」にて、「ホーム」→「検索」をクリックし、リソースで「OAuthClient」と検索すると、いくつかのクライアントが出てきます。ちなみに「console」はOpenShift自体のコンソール（これもひとつのクライアント）です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7ec9a448-466f-83c8-16d7-4ff59f17510c.png)

「OAuthClientの作成」をクリックして新しいインスタンスを作成できます。あるいは以下のYAMLをApplyしてもOKです。

```oauthclient.yaml
kind: OAuthClient
apiVersion: oauth.openshift.io/v1
metadata:
  name: mto-console
  #↑これがclient-idになります。
secret: mto-secret
#↑これがclinet-secretになります。何か任意の文字列を入れておけば良いですが、きちんと管理する必要はあります。
redirectURIs:
  - '<MTO Key cloak Redirect URI>'
#↑ MTOのKey cloakで指定されるRedirect URIをコピペします
grantMethod: auto
```

ユーザが設定するべき要素は3つです。まず`metadata.name`ですが、これはClient-idになります。とりあえず今回は`mto-console`と入れます。次に`secret`です。これはclient-secretに該当します。ここでは`mto-secret`と入れておきます。通常、こうしたクレデンシャル情報は機密情報になるので、適切に管理しましょう。最後に`redirectURIs`ですが、これはKeycloakから提供されるものです。正しく認証がされたら戻って来る（リダイレクトされる）URIです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/9d555eeb-afd9-439d-131a-04a5774ae311.png)

「Redirect URI」欄に出ている文字列をコピペしておいてください。必要な情報を入れて「作成」をクリックすれば、`OauthClient`が作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8cb0e3dc-a883-d0f6-c4a5-9f883c64215f.png)

次に「Base URL」を取得します。これはOpenShiftのAPIサーバのアドレスです。KeycloakからOpenShiftの認証画面にリダイレクトされるのに必要です。この情報は簡単に入手できます。OpenShiftのコンソール画面で「ログインユーザ名」をクリックし、「ログインコマンドのコピー」をクリック、認証が完了すると「Display Token」というボタンが出るので、それをクリックします。

![スクリーンショット 2024-09-17 17.43.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/60a7fc02-e868-72b3-b280-f13f945c2720.png)

こんなコマンドが出てくる画面に行きます。「Base URL」とは、`https://api`から始まり`.com`で終わる部分です。`:443`は不要です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/39914e22-d73e-c88b-c15f-b1661d7842d2.png)

自身のクラスタ名を入れて「Add」をクリックしてください。「Trust Email」は必要に応じてONにしてください。「Save」をクリックすると反映されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fd1a2f85-29d1-47ba-3503-432e85ffcd29.png)

さて、これでMTOコンソールのサインイン手段にOpenShift自体の認証機能を使うことができるようになりました。再度MTOコンソールのサインイン画面を更新します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/c60afb03-d814-96a5-9edf-0682d1b3843b.png)

サインイン手段として「OpenShift v4」が出てきましたので、こちらをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/0272b02b-5cca-994d-e587-cc9637589f9b.png)

無事OpenShiftの認証画面にリダイレクトされました。とりあえず`user2@example.com`でログインしてみます。まだユーザが存在しなかったので、登録画面に遷移しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/15ad0a9a-5607-ef21-ede4-af23ee63aca9.png)

とりあえず必須情報を求められるのでなにか適当に入れて「Submit」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8636d542-72e0-ef32-b67a-b5c1c70b5e65.png)

無事にログインできました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e7f78e07-f697-c388-b063-86f78ea90eed.png)

一度ログアウトしてから再度ログインする場合は、OpenShift自体のログイン情報だけですぐにMTOコンソールにもログインできるようになっています。わざわざMTOコンソールだけの為にログイン情報を管理するのはめんどくさいので、これは便利ですね。

## Show back機能を見てみる

MTOコンソールのShow backメニューでは、ログインしているユーザが参加している`Tenant`ごとのリソース消費実績を遡及して確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b7d40e3f-0d64-1112-12d2-ac9cdc598a37.png)

これにより`Tenant`単位でのリソース消費量を週や月、年単位で集計し、最適化の議論の材料に使ったり、費用按分のためのエビデンスとして利用する事が可能だと思われます。

こちらのShow backで可視化されるリソース消費量とコスト換算する際の係数は`IntegrationConfig`で変更できます。`IntegrationConfig`に以下の様に追記して、係数を設定できる模様です。

```integrationconfig.yaml
（中略）
spec:
  components:
    customPricingModel:
      CPU: '0.031611'
      GPU: '0.95'
      RAM: '0.004237'
      internetNetworkEgress: '0.12'
      regionNetworkEgress: '0.01'
      spotCPU: '0.006655'
      spotRAM: '0.000892'
      storage: '0.00005479452'
      zoneNetworkEgress: '0.01'
```
ちなみにこちらの係数は「GCPのus-central1リージョン」のプライスに基づいているものです。ここは利用しているOpenShiftがDeployされている環境（本環境であればAWS TokyoリージョンでNodeとして利用しているEC2のインスタンスタイプ）に合わせて設定する必要があると思います。ちょっとそこまで細かく調べるのが面倒なので、ここでは正確な値をいれるのは割愛します。こんな感じで設定できるんだなというとこだけ抑えておいていただければ幸いです。

# おわりに
以上、Red Hatの認定オペレータであるMulti Tenant Operatorの概況を見てみました。[公式のドキュメント](https://docs.stakater.com/mto/latest/index.html)には他にも様々な拡張機能の情報が載っているので、興味があれば御覧ください。例えば、定期的に`Tenant`内の特定の`NameSpace`にDeployされている`Pod`を0スケールしてくれる機能（ハイバネーション機能）なんかもあるそうですよ。
ただし、私の見た感じお世辞にもわかりやすいとは思えなかったので、ドキュメントが今後改善されるといいなぁ〜という感じでした。例えば、この記事で説明したKeycloak周りの説明はほとんどありません。またドキュメント通りにHashicorp Vaultとの連携も試してみたんですが、うまくいきませんでした。どなたかうまく行った方は教えて下さい><

おわり。