はじめに
Kubernete/OpenShiftにおける「マルチテナンシー」の実現においては、いくつかの考え方が存在します。ここで言うマルチテナンシーとは、お互いに疎であるアプリケーションの集合体や、それらによって成立するシステムをなんらか物理的・論理的に隔離しつつ、クラスタ内でコンピューティングリソースを共有させることを指します。
特に、単一クラスタにおける最もわかりやすいマルチテナンシーの考え方としてはNameSpaceによる分離です。
ただし、多くのアプリケーションは複数の専用環境を求めます。具体的には開発環境(Dev)、テスト環境(test)、準本番環境(Stg)、そして商用環境(Prod)等です。各開発フェーズや用途に応じて複数のNameSpaceが必要だと思います。
また、MachinePoolを占有させることも考えられます。
MachinePoolはWorker Nodeをラベンリングしてまとめた存在です。AffinityルールやTaint/Tolerationの設定によって、特定のPodしか特定のラベンリングがされたWorker Nodeにスケジュールされないようにするといったことが可能です。
KubernetesにおけるPodスケジューリング戦略まとめは以下がとてもわかりやすいです。
https://cstoku.dev/posts/2018/k8sdojo-18/
最後は、クラスタ自体を分割し、特定のアプリケーション群やシステム、プロジェクト、環境毎に異なるクラスタを割り当てます。OpenShiftについては「Hosted Control Plane」の登場によって、管理クラスタ上で子クラスタ(Hosted Clusterという)達のControl Plane(Master Nodeで動いているコンポーネント達、略してCP)をホストし、更にAdvanced Cluster Managementを活用してマルチクラスタの作成や管理が非常に容易になりました。
とは言え、やはりコンピューティングリソースの有効活用やインフラ集約率の向上を考える上で、すべてのシステムやプロジェクト毎に毎回Hosted Clusterを払い出す事は現実的ではないでしょう。単一クラスタの中でNameSpaceを切り、適切なユーザ権限管理やリソース管理(ResouceQuotaの設計)を実施した上で、テナント毎のリソース消費量とコストの可視化・最適化を目指したいところです。
ところが、この「適切なユーザ権限管理やリソース管理」やら「テナント毎のリソース消費量とコストの可視化・最適化」というのは言うは易く行うは難しです。じゃあ具体的にどうやって複数のNameSpaceと複数のユーザを適切な権限で紐づけ、それらを横断してResouceQuotaの設計をするのか?
そこで「Multi Tenant Operator(略してMTO)」が何等か一助になってくれるのではないか?と思い試してみました。
Multi Tenant Operator(MTO)とは
MTOは「Stakater」という企業が提供するKubernetes Operatorです。
同社のホームページを見ると、Kubernetesインフラの構築運用支援やPlatform Engineeringサービスを提供しているっぽいですね。
スウェーデンのストックホルム、David Bagares通りの一角に本拠地を構えているそうです。
さて、このMTOは単一クラスタにおけるマルチテナンシーを実現するためのCRD(Custom Resources Definitions)を提供し、クラスタに対して「テナント」という抽象化レイヤを提供してくれます。このテナントの中でOpenShiftユーザに対する権限の定義を行い、用意するべき環境を定義すると、いい感じにNameSpaceを作成しつつユーザ権限とのマッピング(RBACのバインディング)をしてくれます。
Red Hatの認定Operatorです
Red Hatのブログにもある通り、こちらのOperatorはRed Hatに正式に認定された Certified Operatorです。
無料でも使えますが...
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
本格的に利用したい場合は課金してください。料金プランは公式サイトを御覧ください。1クラスタあたり795$/月だそうです。Enterpriseサポートはもちろん付いています。
MTOでテナントを作ろう
まずはものは試しということで、やってみます。
OpenShiftクラスタの準備
その前にOpenShiftクラスタを用意してください。今回は東京リージョンに2Worker NodeのROSA HCP(Red Hat OpenShift Service on AWS with Hosted Control Plane)を作成しました。また、Cluster-admin権限を有するアカウントを使って以下のユーザを作成しました。RBACはまだ何も設定していません。
user0@example.comuser1@example.comuser2@example.comuser3@example.comuser4@example.com
また、OpenShiftのコンソール画面「ユーザー管理」→「Group」からexmaple-groupというGroupeを作成し、user3@example.comとuser4@example.comをぶち込んでおきます。以下のYAMLをApplyするのでもOKです。
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を削除しておいてください。
そもそも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されます。
インストールが完了すると複数のカスタムリソース(CR)が作成できるようになります。
QuotaとTenantを作ってみる
先に今から作る環境の雰囲気を以下の絵で説明します。
なんだか見慣れない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
-
Viewer: exampleテナント内のリソースの閲覧権限のみを付与される
- exampleテナントに参加する各ユーザは個別のSnadbox環境(
NameSpace)を与えられ、Sandbox環境は各ユーザからは見えないようになる- ただしViewer権限しか無い
user0@example.comにはSandboxは不要
- ただしViewer権限しか無い
Quotaは、Tenantから必須で参照されるべきリソースであり、文字通り「Tenantが利用できるコンピューティングリソースの上限」を設定する事ができます。コンピューティングリソースと一言で言っても、かなり細かく設定できます。例を以下に示します。
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単位での個別設定が可能で、かなりきめ細かく設定できます。
なお、ConfigMap数やSecret数、Service数まで設定できます。しかしこれらの設定がイマイチどう意味があるのか自分にはわかっていないので、わかる方は教えてください。NameSpaceにApplyできるConfigMap数を制限してどういう意味があるのか...🤔
また、Pod毎のリソース上限・下限も設定できます。上記のYAMLをApplyしてQuotaリソースをDeployします。
次にTenantリソースを作成します。
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が作成されます。
各NameSpaceのmetadata.labelsにはstakater.com/が付与されているはずです。
各ユーザからのTenantに対する操作の挙動を見てみる
まず初めにEditor権限を持つuser1@example.comでOpenShiftに再ログインしてください。「管理者向け表示」では、以下の通りNameSpaceが確認できます。
example-devexample-testexample-user1-example-sandbox
Editor権限を持っているTenant配下のNameSpaceが見えています。また自身のSandbox環境も見えていますが、他ユーザのSandbox環境は見えてません。各NameSpace名の右端の縦三点リーダ︙をクリックしてみてください。NameSpaceに対するadmin権限が無いため「削除」がグレーアウトしており選択できません。
次に自身のSandbox内で適当なConfigMapを作成してみます。
apiVersion: v1
kind: ConfigMap
metadata:
name: user1-configmap
namespace: example-user1-example-sandbox
data:
USER1_KEY: hogehoge
何の問題もなく作成できました。
次にexample-dev内に適当なアプリケーションをDepolyしてみます。
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です。1PodあたりCPU: 100ミリコア、メモリを500メガバイト要求し、それぞれの上限を200ミリコア・1ギガバイトとしました。
リソースを作成することができました。Apache Webサーバも問題なく起動している模様です。
「開発者向け表示」のトポロジー画面でPod数をどんどん増やしてみます。すると当該環境においては10Podまでしか増やすことができなくなりました。
コンソール画面にも「Quotaを超えました」といった趣旨のエラーメッセージがでています。
今、exapleテナントにおけるコンピューティングリソース上限は
- CPU上限: 2コア
- メモリ上限: 10ギガバイト
でした。そしてApache Webサーバコンテナの1Pod辺りのコンピューティングリソース利用については - CPU:
- 要求: 200ミリコア
- 上限: 300ミリコア
- メモリ:
- 要求: 500メガバイト
- 上限: 1ギガバイト
でした。今10Podまでレプリケーションしていますのでメモリについてはまだ余裕があるものの、CPUについては200ミリコア×10Pod≒2コアに達してしまい、これ以上リソース要求できない状態です。
では、NameSpace: example-testに対して、同じApache WebサーバのYAMLファイルをApplyしてみます。
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が起動してきません。
NameSpace: example-devにDeployしたPodの数を7まで減らします。
お、NameSpace: example-testの方のPodが3つ起動しました。ではNameSpace: example-testの方のPodを3から4に増やすことを試みます。
増えません。
では、NameSpace: example-devにDeployしたPodの数を7から6に減らします。
両NameSpaceのPod数合計は常に10が上限です。つまり同じexampleテナント配下の2つのNameSpace間でQuotaがシェアされていることがわかりました。
なお、現在のexampleテナント全体で利用されているリソース状況については、OpenShiftのコンソール画面でも確認することができます。「管理者向け表示」にて「管理」→「ResourceQuota」と進むと、「AppliedClusterResourceQuotas」というCRを発見できます。このインスタンス名「example」こそが、実はMTOで設定したTenantのQuotaそのものなんですね。
では、NameSpace: example-devのApache Webサーバコンテナを3つまで減らし、NameSpace: example-testの方はDeploymentを削除し元に戻しておきます。
次に、NameSpaceを作成しようと試みます。以下のYAMLをApplyしてみます。
apiVersion: v1
kind: Namespace
metadata:
name: sample
駄目でした。Editor権限のユーザでは、勝手にNameSpaceを追加することはできません。あくまで用意されたTenant及びその配下のNameSpaceの範疇でリソースの編集権限を与えられているに過ぎず、Tenantそのものを拡張・縮小したりすることはできないようです。
次にuser0@example.comで再ログインしてください。
Viewer権限しか無いuser0@example.comにはSandbox環境は与えられません。
(´・ω・) カワイソス
先ほどexample-devにDeployしたApache Webサーバを確認する事は可能です。
ただし、編集したり削除したりすることができません。
編集メニューはすべてグレーアウトし、Pod数変更などもできません。ConfigMapを作成しようとしても、GUIに「作成」ボタンすら見えません。
YAMLファイルをApplyしようとしても駄目です
apiVersion: v1
kind: ConfigMap
metadata:
name: user1-configmap
namespace: example-user1-example-sandbox
data:
USER1_KEY: hogehoge
Tenant配下のNameSpaceに対する操作権限は本当に見るだけ(Viewer)の様です。
最後に、Owner権限を持つユーザuser2@example.comで再ログインします。
NameSpaceを作成しようと試みますが、
apiVersion: v1
kind: Namespace
metadata:
name: sample
やはり駄目です。
しかし、作成しようとするNameSpaceに以下の様なラベルを付与してください。
apiVersion: v1
kind: Namespace
metadata:
name: sample
labels:
stakater.com/tenant: example
あら不思議!sampleという新しいNameSpaceを作成することができました。
再度user1@example.comでログインし直して同じ様にやってみても駄目です。
つまり、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を確認、クリックしてください。
YAMLのspec.integrationsより下を以下の通り書き換えます。
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に自動的にラベルが付与されています。
Extensionsの作成
更にExtensionsというカスタムリソースを作成します。こちらはTenantに紐づくArgoCDのプロジェクト(AppProject)を自動的に作成してくれる便利なCRです。
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は以下の通りかと思います。
OpenShift GitOps Operatorをインストール
さて、このままCluster-admin権限を持つアカウントにて、OpenShift GitOps Operatorもインストールしておきます。OperatorHubにて検索して、デフォルト設定でインストールします。
お〜!exampleというAppProjectが自動的に作成されていますね〜
テンション上がってきました。では、Editor権限をもつuser1@example.comにてOpenShift GitOpsにログインしてみます。
タイル右上のリンクからOpenShift GitOpsのコンソール画面に遷移し、「LOG IN VIA OPENSHIFT」の方から、user1@example.comのID/PWにてシングルサインオンします。
「Setteings」→「Project」と進むと、user1@example.comがAppProject: exampleに参加していることがわかります。
では何かアプリケーションをDeployしてみましょう。Postgresqlコンテナを試しにDeployします。ArgoCDアプリケーションのYAMLは以下の通りです。
本記事で利用するManifestはすべて以下のGitlabリポジトリにおいているのでご参考にしてください。
https://gitlab.com/masaki-oomura/multi-tenant-operator-demo.git
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ファイルを入れておきました。
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の名称を指定します
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リポジトリで管理するのはおすすめしません。
Hashicorp Vaultを使おう!
では、ArgoCDのコンソール画面にて「CREATE APPLICATION」をクリックし、application.yamlをApplyして「CREATE」をクリックします。
アプリケーションがDeployできません。
まず、PVCを見てみるとstorageClass: gp3-csiに対して11ギガバイトの容量を要求しています。しかしexampleテナントにおいては10ギガバイトまでしか利用できませんので、Quota超過です。おっとうっかり。一度アプリケーションをDeleteしてください。
では、Manifestを修正して5ギガバイトまでに直しましょう。修正後のManifestが格納されたリポジトリパスを参照するArgoCDアプリケーションは以下です。
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を試みます。
また駄目でした。そういえば、ExtensionsでSecretの同期を禁止していました。うっかりうっかり。テヘペロ。SecretがApplyできないと環境変数を参照できないためDeploymentもApplyできません。先ほど同様、ArgoCDアプリケーションは削除しておいてください。
では、先にNameSpace: example-devにSecretを手動でApplyしておき、GitリポジトリからはSecretは除外しましょう。
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です。あくまでデモ用途です。
コンソール画面のGUIからYAMLをApplyしました。仏の顔も三度まで。Secretを除外したManifest一式を参照するArgoCDアプリケーションは以下です。以下のArgoCDアプリケーションをApplyします。
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: []
やったぜ。
OpenShiftのコンソールからもPostgresqlコンテナがDeployできたことを確認できました。
なおViewer権限しか持っていないuser0@example.comからは、当たり前ですがArgoCDにログインできてもアプリケーションのDeployはできません。
また、他のユーザが作成したアプリケーションを参照することはできますが、削除もできません。
このようにして、MTOを利用してArgoCDのマルチテナンシーまで実現できます。ArgoCDのAppProjectに対するRBACと、MTOで設定したTenant向けのRBACがいい感じに同期してくれます。
NameSpaceにリソース配布してみる
さて、最後にTenant配下のNameSpaceに対して一斉にリソースを配布してみます。想像できる活用事例としては、
- コンテナイメージレジストリへのアクセス情報(
Secret)を配布する - 新しいプロジェクトが立ち上がったので、対応する
Tenantを作成し、Platform Engineering的な文脈から、配下のNameSpaceにサンプルアプリケーションやパイプラインを配布してあげる
などが考えられます。Tenant配下のNameSpaceに一斉にリソースを配布できるのはかなり便利です。今回はDocker HubにあるプライベートレジストリからコンテナイメージをPullする為に必要な認証情報を配布したいとします。具体的には以下のSecretを配布したいと考えましょう。
kind: Secret
apiVersion: v1
metadata:
name: docker-pull-secret
data:
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJteS1kb2NrZXItdXNlcm5hbWUiLCJwYXNzd29yZCI6Im15LWFjY2Vzcy10b2tlbiIsImVtYWlsIjoibXktZW1haWxAZXhhbXBsZS5jb20iLCJhdXRoIjoiYlhrdFpHOWphMlZ5TFhWelpYSnVZVzFsT20xNUxXRmpZMlZ6Y3kxMGIydGxiZz09In19fQ==
type: kubernetes.io/dockerconfigjson
プライベートレジストリに存在するコンテナイメージをpullしてくる為には認証情報が必要です。そのためには管理者からアクセストークンをDocker Hub上で発行してもらう必要があります。
管理者のDocker Hubアカウントで https://app.docker.com/settings/personal-access-tokens/create にアクセスするとアクセストークンを発行できます。ここで発行したアクセストークンをメモっておきます。
上記Secret: docker-pull-secretの.dockerconfigjson:以下の文字列は、以下の.jsonがBASE64エンコードされたものです。
{"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で簡単に作成できます。
平文でvalueを入れれば同じSecretができます。なお、「パスワード」欄はアクセストークンを入れてください。このSecretを参照してDeployment等のリソースにてコンテナイメージをPullすることができます。例えば以下のように記載することで、プライベートレジストリに格納されたコンテナイメージを認証を経てPull、クラスタ上にPodとしてスケジュールすることができます。
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を作成しました。
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を配布することができます。
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を指定しても良いでしょう。
ではtemplategroupinstances.yamlもCluster-admin権限を持つアカウントにてApplyしておきます。これまでの作業をしてきて、今クラスタ内に作成されたカスタムリソースは以下になっているでしょう。
ではdocker-pull-secretは配布されたのでしょうか?
user2@example.comでログイン済みの状態で各NameSpaceのシークレット一覧を見ると、すべてのNameSpaceにdocker-pull-secretがきちんと配布されていました。
このようにして、TemplateとTemplateGroupInstanceを使うと、簡単に指定したNameSpaceに同じリソースを一元的に配布できます。各Tenant内で共通的に利用する認証情報や、サンプルアプリのパイプラインやマニフェストを配布することで、Tenantに参加するユーザにより良い開発者体験を提供できることでしょう。
MTOコンソールでテナントのコストを可視化しよう
実はMTOにはコンソール画面を提供するコンポーネントがあります。先にどんな事がわかるのかを示します。
ログインすると、Dashboardに遷移します。
ここでは、ユーザが参加しているTenantの情報が一元的に確認できます。
こんな感じでTenantのリソース消費状況をチャートで見たりできます。Tenant配下のNameSpaceごとの利用状況も確認できます。
NameSpace名をクリックすると、NameSpace毎かつワークロード種別毎のより詳細な利用状況が確認できます。
MTOコンソールを有効化しよう
MTOのデフォルト設定ではコンソール機能が無効化されています。有効化はIntegrationConfigを編集することで可能です。Cluster-admin権限を持つアカウントの「管理者向け表示」にて、「Operator」→「インストール済みのOperator」と進み、MTOのカスタムリソース欄からIntegrationConfigを選択します。
「tenant-operator-config」のYAML編集画面にて以下のように変更してください
(中略)
spec:
(中略)
components:
console: true
showback: true
(中略)
「保存」をクリックすると、NameSpace: multi-tenant-operatorのトポロジー画面でMTOコンソール関連のコンポーネントが起動してきます。すべてのコンポーネントが起動してくるまでに2,3分かかるのでしばし待ちます。
左の塊がMTOコンソールを構成するコンポーネント達になります。MTOコンソール自体のURLは「tenant-operator-console」のRouteで提供されます。
クリックしてアクセスしてみます。
サインイン画面にはなりますが、ユーザを作成していないのでここから先には進めません。ちなみに、デフォルトユーザとして
- Username:
mto - Password:
mto
でログインすることはできます。ただしデフォルトユーザは何のTenantにも参加していないので、何も見れません。ユーザ登録の仕組みや認証の仕組みは別途MTOコンソールコンポーネントたちの中で起動してきた「Keycloak」を使って整える必要があります。
Keycloakを設定変更してユーザ登録機能を開放する
MTOコンソールコンポーネント達の「tenant-operator-keycloak」のRouteからKey Cloakログイン画面に遷移します。Adminのアクセス情報は以下です
- Username or email: admin
- Password: admin
Keycloakは、オープンソースの認証基盤ソフトウェアです。OAuth 2.0、OpenID Connect、SAML 2.0 などの業界標準のプロトコルをサポートしており、既存のシステムや外部認証プロバイダー(Google、GitHub、LDAPなど)と簡単に統合できます。これにより、企業や開発チームが既存の認証基盤にKeycloakをシームレスに導入できる点が大きな強みです。認証機能を有する様々なソフトウェアに組み込まれています。
なお余談ですが、Red HatがEnterpriseサポートを提供している「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に変えてください。
Clientsに「mto-console」があります。
ただ、ここは特にいじりません。「Realm settings」の「Login」タブをクリックしてください。
デフォルト設定だとMTOコンソールのログイン画面にユーザ登録機能がありませんので、ユーザ登録できるように機能を有効化してください。以下のスクリーンショットの通りにトグルスイッチをONにします。
トグルをONにするとすぐにMTOコンソールに反映されます。MTOコンソール画面を更新してみます。
MTOコンソールのログイン認証をOpenShiftの認証機能にブローカーさせる
MTOコンソールのユーザ登録機能が有効化されました。ただ、できればOpenShiftコンソールのログインとSSOしたいので、IdP(Identity provider)としてOpenShift自体の認証機能を設定します。「Identity providers」をクリックし、「OpenShift v4」を選択します。
ここに必要な情報を色々いれるとOpenShiftの認証機能にブローカーしてくれるようになります。
これらの情報はOpenShiftの認証機能側から払い出される値になるので、それを今から登録します。OpenShiftのコンソール画面にログインしようとしたり、OpenShift GitOpsで「LOG IN VIA OPENSHIFT」をクリックすると、
この画面にいつも行くと思うのですが、これもOpenShift上で動いているアプリケーションです。こいつにクライアント側からリダイレクトしてもらう為には、OAuthClientというカスタムリソースを作成する必要があります。Cluster-admin権限を持つアカウントの「管理者向け表示」にて、「ホーム」→「検索」をクリックし、リソースで「OAuthClient」と検索すると、いくつかのクライアントが出てきます。ちなみに「console」はOpenShift自体のコンソール(これもひとつのクライアント)です。
「OAuthClientの作成」をクリックして新しいインスタンスを作成できます。あるいは以下のYAMLをApplyしてもOKです。
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です。
「Redirect URI」欄に出ている文字列をコピペしておいてください。必要な情報を入れて「作成」をクリックすれば、OauthClientが作成されます。
次に「Base URL」を取得します。これはOpenShiftのAPIサーバのアドレスです。KeycloakからOpenShiftの認証画面にリダイレクトされるのに必要です。この情報は簡単に入手できます。OpenShiftのコンソール画面で「ログインユーザ名」をクリックし、「ログインコマンドのコピー」をクリック、認証が完了すると「Display Token」というボタンが出るので、それをクリックします。
こんなコマンドが出てくる画面に行きます。「Base URL」とは、https://apiから始まり.comで終わる部分です。:443は不要です。
自身のクラスタ名を入れて「Add」をクリックしてください。「Trust Email」は必要に応じてONにしてください。「Save」をクリックすると反映されます。
さて、これでMTOコンソールのサインイン手段にOpenShift自体の認証機能を使うことができるようになりました。再度MTOコンソールのサインイン画面を更新します。
サインイン手段として「OpenShift v4」が出てきましたので、こちらをクリックします。
無事OpenShiftの認証画面にリダイレクトされました。とりあえずuser2@example.comでログインしてみます。まだユーザが存在しなかったので、登録画面に遷移しました。
とりあえず必須情報を求められるのでなにか適当に入れて「Submit」をクリックします。
無事にログインできました。
一度ログアウトしてから再度ログインする場合は、OpenShift自体のログイン情報だけですぐにMTOコンソールにもログインできるようになっています。わざわざMTOコンソールだけの為にログイン情報を管理するのはめんどくさいので、これは便利ですね。
Show back機能を見てみる
MTOコンソールのShow backメニューでは、ログインしているユーザが参加しているTenantごとのリソース消費実績を遡及して確認できます。
これによりTenant単位でのリソース消費量を週や月、年単位で集計し、最適化の議論の材料に使ったり、費用按分のためのエビデンスとして利用する事が可能だと思われます。
こちらのShow backで可視化されるリソース消費量とコスト換算する際の係数はIntegrationConfigで変更できます。IntegrationConfigに以下の様に追記して、係数を設定できる模様です。
(中略)
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の概況を見てみました。公式のドキュメントには他にも様々な拡張機能の情報が載っているので、興味があれば御覧ください。例えば、定期的にTenant内の特定のNameSpaceにDeployされているPodを0スケールしてくれる機能(ハイバネーション機能)なんかもあるそうですよ。
ただし、私の見た感じお世辞にもわかりやすいとは思えなかったので、ドキュメントが今後改善されるといいなぁ〜という感じでした。例えば、この記事で説明したKeycloak周りの説明はほとんどありません。またドキュメント通りにHashicorp Vaultとの連携も試してみたんですが、うまくいきませんでした。どなたかうまく行った方は教えて下さい><
おわり。


















































































