👪

kubernetesのマルチテナント運用に役立ちそうな色々

2022/09/10に公開

背景

Kubernetesクラスタ運用において、マルチクラスタ戦略 vs マルチテナント戦略 の議論が度々巻き起こります。

マルチクラスタ運用はテナント同士の権限・ネットワーク隔離が容易な反面、運用工数がクラスタ数に比例して増加します。

マルチテナント運用はマルチクラスタの裏返しで、運用工数はそこまで増えない反面、テナント同士の権限・ネットワーク隔離を考慮する必要があります。

マルチテナント戦略を取るときの注意点としては、

Kubernetes policies such as RBAC, quotas, and network policies are essential to safely and fairly share clusters.
ref. https://kubernetes.io/docs/concepts/security/multi-tenancy

とある通り、

  • RBAC(Role Based Access Control: ロールベースアクセス制御)
    • クラスターにログインできるアカウントのロール管理
  • quota
    • あるテナントが大量にリソースを使用して他テナントのリソースを圧迫することがないようにする
  • network policy
    • 通信できないはずの他テナントに通信できないようにネットワーク管理
  • その他共有リソース(Node Volumeなど)の隔離

など、マルチクラスタのときはクラスタ単位で隔離できていたものを同じクラスタに乗せることになるので、同様に隔離できるような施策を取る必要があります。

Kubernetesはマルチテナント運用に必要な機能全てを公式に提供しているわけではなく、kubernetes-sigs/multi-tenancy(sig: Special Interest Group, 分科会) で議論はされていますがあまり活発ではないのが実情です。
https://github.com/kubernetes-sigs/multi-tenancy

とはいえ需要はあるので、k8sビルトインの機能を組み合わせたり有志の方々が作っている機能を取り入れたりしてマルチテナント環境を実現するのが一般的なアプローチとなっているようです。

というわけでマルチテナント運用に役立ちそうなものを色々調べて触ってみたいと思います。

kubernetes-sigs/hierarchical-namespaces

https://github.com/kubernetes-sigs/hierarchical-namespaces

背景でkubernetes-sigs/multi-tenancyがあまり活発ではないと言いましたが唯一活発なのがこれです。

ざっくり

https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/master/docs/user-guide/quickstart.md

  • namespaceに親子関係を設定できる
  • namespaceの親→子に指定した任意のk8s resourceを伝搬させられる

試してみる

以下のリンクを見ながらやっています。
https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/tag/v1.0.0
https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/master/docs/user-guide/quickstart.md

まずはインストール作業。

# hnsのversionを指定
$ HNC_VERSION=v1.0.0

# hnsをinstall
$ k apply -f https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/download/${HNC_VERSION}/default.yaml 

# hns用のpluginをinstall(hnsを扱いやすくなるらしい
# krew(kのplugin manager)が入ってない方はkrewを入れるか手動で入れてください
$ k krew update && k krew install hns

階層化するnamespaceを作ってみます。
この時点ではまだただのnamespaceです。

$ k create ns hoge-org
namespace/hoge-org created

$ k create ns service-1
namespace/service-1 created

階層化してみます。
labelを見ると親子nsとの関係が書いてあります。

$ k hns set service-1 --parent hoge-org
Setting the parent of service-1 to hoge-org
Succesfully updated 1 property of the hierarchical configuration of service-1

$ k hns tree hoge-org
hoge-org
└── service-1

$ k get ns hoge-org -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2022-08-21T04:32:52Z"
  labels:
    hnc.x-k8s.io/included-namespace: "true"
    hoge-org.tree.hnc.x-k8s.io/depth: "0"
    kubernetes.io/metadata.name: hoge-org
  name: hoge-org
  resourceVersion: "1960"
  uid: 1669a35b-92c6-4faa-82b2-d6b80e49da44
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

$ k get ns service-1 -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2022-08-21T04:33:52Z"
  labels:
    hnc.x-k8s.io/included-namespace: "true"
    hoge-org.tree.hnc.x-k8s.io/depth: "1"
    kubernetes.io/metadata.name: service-1
    service-1.tree.hnc.x-k8s.io/depth: "0"
  name: service-1
  resourceVersion: "2440"
  uid: e24c4b84-533f-4a18-9fef-72583a1612fa
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

hnsはデフォルトで、Role, RoleBindingsを自動的に子nsに継承します。
EKSでもaws-auth configmapを書くときに親となるnsにアクセスできるようにしておけば、勝手にあとはやってくれそうですね。

$ k -n hoge-org create role hoge-org-sre --verb=update --resource=deployments
role.rbac.authorization.k8s.io/hoge-org-sre created

$ k get role -n hoge-org
NAME           CREATED AT
hoge-org-sre   2022-08-21T05:23:51Z

$ k get role -n service-1
NAME           CREATED AT
hoge-org-sre   2022-08-21T05:23:51Z

Role, RoleBindings以外のresourceを伝搬できるようにするときはconfigを書き換えます。
下のオブジェクトのspec.resourcesに、

- resource: secrets
  mode: Propagate

という感じで追加することで設定を追加できます。
Annotationにpropagate.hnc.x-k8s.io/<select|treeSelect|none>を書くとresourceごとに上書きすることもできます。
詳しくは
https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/master/docs/user-guide/how-to.md#limit-the-propagation-of-an-object-to-descendant-namespaces を見てください。

modeは三種類あります。

  • Propagate: オブジェクトを親から子に伝播し、親が削除されたら子も削除します。Role, RoleBindingsのデフォルト動作です。子オブジェクトを直接変更しようとすると怒られてできないようになっています。
  • Delete: Propagateで伝搬したコピーをすべて削除します。継承元のオブジェクトには影響ありません。
  • Ignore: このリソースの変更を停止します。新規または変更されたオブジェクトは伝搬されず、親で削除されたオブジェクトも削除されません。hnc.x-k8s.io/inherited-fromラベルは削除されません。 Role, RoleBindings以外のresourceについて、設定がない場合のデフォルト動作です。
$ k get hncconfiguration config -o yaml
apiVersion: hnc.x-k8s.io/v1alpha2
kind: HNCConfiguration
metadata:
  creationTimestamp: "2022-08-21T04:20:55Z"
  generation: 2
  name: config
  resourceVersion: "7186"
  uid: fd12940f-f305-4ccf-8438-f6ab589dceb8
spec: {}
status:
  resources:
  - group: rbac.authorization.k8s.io
    mode: Propagate
    numPropagatedObjects: 0
    numSourceObjects: 0
    resource: rolebindings
    version: v1
  - group: rbac.authorization.k8s.io
    mode: Propagate
    numPropagatedObjects: 1
    numSourceObjects: 1
    resource: roles
    version: v1

所感

名前の通りnamespaceの管理と親子間のresource伝搬に特化しているのでそれ以外は特にできない印象です。
namespaceのresourceQuotaをnsごとではなくtreeごとにできるように議論した痕跡はあるものの結局まだ実装されていないようなので、あまり使えなさそうです。
CRDはあるものの、ドキュメントに入っていないかつreleaseにも含まれていなくてWIPっぽいですね。
https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/529259da83b27bf07191354789165767e1c3aaf3/config/crd/bases/hnc.x-k8s.io_hierarchicalresourcequotas.yaml

clastix/capsule

https://capsule.clastix.io/

CNCF memberの企業が作っているマルチテナント支援のコンポーネントです。

ざっくり

複数の Namespace を Tenant と呼ばれるグループでまとめ、そのテナント内でNamespace, Network Policies, Security Policies, Resource quota, Limit Range, RBACなどをテナントごとに扱えるようにしてくれるようです。

試してみる

インストール方法は2種類。

  1. 単一YAMLファイル
  2. Helm Chart

Helm Chart のほうが好みなので今回は Helm で入れることにします。今回は特にパラメーターをいじることはなさそうなので Helm じゃなくてもよさそうではありますが。

$ helm repo add clastix https://clastix.github.io/charts

$ helm install capsule clastix/capsule -n capsule-system --create-namespace

$ helm status capsule -n capsule-system

Tenant を作ってみます。

$ k create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
kind: Tenant
metadata:
  name: hoge
spec:
  owners:
  - name: alice
    kind: User
EOF

$ k get tenant
NAME   STATE    NAMESPACE QUOTA   NAMESPACE COUNT   NODE SELECTOR   AGE
hoge   Active                     0                                 89s

tenant owner の使い心地を見るために、上で作ったhoge tenant の owner alice になってみます。

今回は kind cluster で検証をしているので、clastix/capsule/hack/create-user.sh を使います。

$ ../../clastix/capsule/hack/create-user.sh alice hoge
creating certs in TMPDIR /tmp/tmp.5cK1OEAWJ7 
merging groups /O=capsule.clastix.io
Generating RSA private key, 2048 bit long modulus (2 primes)
.....................+++++
......................+++++
e is 65537 (0x010001)
certificatesigningrequest.certificates.k8s.io/alice-hoge created
certificatesigningrequest.certificates.k8s.io/alice-hoge approved
kubeconfig file is: alice-hoge.kubeconfig
to use it as alice export KUBECONFIG=alice-hoge.kubeconfig

$ export KUBECONFIG=alice-hoge.kubeconfig

$ k config current-context
alice-hoge

まずはnamespaceを作ってみます。

$ k create ns hoge-foo
namespace/hoge-foo created

$ k create ns hoge-foo2
namespace/hoge-foo2 created

$ k get ns hoge-foo -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2022-09-10T02:06:56Z"
  labels:
    capsule.clastix.io/tenant: hoge
    kubernetes.io/metadata.name: hoge-foo
    name: hoge-foo
  name: hoge-foo
  ownerReferences:
  - apiVersion: capsule.clastix.io/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: Tenant
    name: hoge
    uid: 6241d243-707b-4563-89d6-6f2f7ba3f91c
  resourceVersion: "17358"
  uid: 7e7ab627-7157-49d6-a40c-5bc7c1e864e3
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

少し悪巧みをしてみます。

# default User で tenant hoge2 を作成
# alice は使えず、bob だけ使えるようにする
$ k create -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
kind: Tenant
metadata:
  name: hoge2
spec:
  owners:
  - name: bob
    kind: User
EOF

# alice に戻って先程作った namespace の tenant を付け替えてみる
$ k label ns hoge-foo --overwrite capsule.clastix.io/tenant=hoge2
Error from server (Cannot assign the desired namespace to a non-owned Tenant): admission webhook "owner.namespace.capsule.clastix.io" denied the request: Cannot assign the desired namespace to a non-owned Tenant

悪さはできないように塞がれているようです。

それでは tenant の中に作った namespace hoge-foo の中にpodを立ててみます。

$ k -n hoge-foo run nginx --image=docker.io/nginx
pod/nginx created

$ k get -n hoge-foo po nginx
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          35s

# default namespace の pod は見えない
$ k get po -n default
Error from server (Forbidden): pods is forbidden: User "alice" cannot list resource "pods" in API group "" in the namespace "default"

$ k delete po nginx -n hoge-foo
pod "nginx" deleted

Tenant に Resource Quota を設定してみます。

$ k apply -f - << EOF
apiVersion: capsule.clastix.io/v1beta1
kind: Tenant
metadata:
  name: hoge
spec:
  owners:
  - name: alice
    kind: User
  namespaceOptions:
    quota: 3
  resourceQuotas:
    scope: Tenant
    items:
    - hard:
        limits.cpu: "2"
        limits.memory: 2Gi
        requests.cpu: "1"
        requests.memory: 1Gi
        pods: "2"
  limitRanges:
    items:
    - limits:
      - default:
          cpu: 500m
          memory: 512Mi
        defaultRequest:
          cpu: 100m
          memory: 10Mi
        type: Container
EOF

$ k -n hoge-foo run nginx --image=docker.io/nginx
pod/nginx created

$ k get po nginx -n hoge-foo -o jsonpath='{.spec.containers[0].resources}' | yq eval -P
limits:
  cpu: 500m
  memory: 512Mi
requests:
  cpu: 100m
  memory: 10Mi

$ k -n hoge-foo run nginx-2 --image=docker.io/nginx
pod/nginx-2 created

# tenant hoge の namespace hoge-foo で 3 pod 作ろうとするとエラー
$ k -n hoge-foo run nginx-3 --image=docker.io/nginx
Error from server (Forbidden): pods "nginx-3" is forbidden: exceeded quota: capsule-hoge-0, requested: pods=1, used: pods=2, limited: pods=2

$ k delete po nginx-2 -n hoge-foo
pod "nginx-2" deleted

# namespace hoge-foo2 側で pod を作ってみる
$ k -n hoge-foo2 run nginx-2 --image=docker.io/nginx
pod/nginx-2 created

# namespace hoge-foo2 側で3台目の pod を作ろうとするとエラー
# tenant をまたいで resourceQuota を適用できている
$ k -n hoge-foo2 run nginx-3 --image=docker.io/nginx
Error from server (Forbidden): pods "nginx-3" is forbidden: exceeded quota: capsule-hoge-0, requested: pods=1, used: pods=1, limited: pods=1

所感

hierarchical-namespaces に比べると高機能な印象で、ドキュメントも整備されていて読みやすいです。
特に hierarchical-namespaces が実現できていない複数 namespace をまたいだ ResourceQuotas などが嬉しい機能ですね。

実際に使う際にはもう少し自分の組織のユースケースにマッチするかを検証する必要がありそうですが、筋は良さそうです。

その他

Discussion