🐕

KubernetesのHierarchical Namespacesによるpropagationを完全に理解した

2021/12/22に公開

はじめに

この記事ではKubernetesのHierarchical Namespaces(以下、HNS)について紹介します。

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

Kubernetesのクラスタを運用していると、マルチテナンシーやリソース効率化などのためにnamespaceを活用することがあります。namespaceはRBACやNetworkPoliciesなどを境界づけるために有用ですが、一方で制約や管理の難しさの問題もあります。そうした
問題を解消する手段のひとつとして、マルチテナンシーのワーキンググループにおいてHNSの開発が進められています。

HNSではその名の通り、階層型namespaceを作れます。この階層構造のメリットとして、一部のresourceをnamespaceで分離しつつ、階層の親子関係での伝搬(propagation)を可能にします。個人的には、AWSの組織単位(Organization Unit)やGCPのフォルダに近いものをイメージしています。

開発状況

記事の公開時点での最新バージョンはv0.9.0です。
productionで利用可能なv1.0のETAが2021 lateとされているため、そう遠くはないうちにリリースされると予想しています。
クラウドベンダーではGCPでのみベータ版として対応しています。

https://cloud.google.com/anthos-config-management/docs/concepts/hierarchy-controller

前置きはこのぐらいにして、実際にnamespaceを作りながら試してみましょう。

サンプルコードはこちらのリポジトリに置いておきます。
https://github.com/snagasawa/kubernetes-samples/tree/main/hierarchical-namespaces

kubectl-hnsのinstallとHierarchical Namespace Controllerのapply

はじめにkindでクラスタを作成し、Krewで kubectl-hns をインストールします。続いてHierarchical Namespace Controller(以下、HNC)のmanifestをapplyします。

$ kind create cluster --name hns-cluster-sample
$ kubectl krew install hns
$ kubectl hns version
kubectl-hns version v0.9.0

$ kubectl apply -f https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/download/v0.9.0/hnc-manager.yaml

https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/tag/v0.9.0

階層型Namespaceを作ってみる

従来通りのkubectl create namespaceでparent namespaceを作り、そのあとにkubectl hns createでそれぞれchild namespace、grandchild namespaceを作成します。

$ kubectl create ns parent
$ kubectl hns create child -n parent
$ kubectl hns create grandchild -n child

kubectl get namespacesで確認できますが、一見すると今までと変わらないnamespaceのように見えます。

$ kubectl get namespaces -A | grep -E "parent|child"
child                Active   2m2s
grandchild           Active   47s
parent               Active   2m28s

しかし、kubectl hns treeを実行するとこのようにnamespaceの階層を確認できます。kubectl hns createで作られたnamespaceの前には [s] が表示されています。これらはSubnamespaceと呼ばれ、従来のnamespace(Full Namespace)とは明確に区別されます。

$ kubectl hns tree parent
parent
└── [s] child
    └── [s] grandchild

[s] indicates subnamespaces

kubectl hns describeで階層の親子関係を詳細に確認できます。

$ kubectl hns describe parent
Hierarchy configuration for namespace parent
  No parent
  Children:
  - child (subnamespace)
  No conditions

No recent HNC events for objects in this namespace
$ kubectl hns describe child
Hierarchy configuration for namespace child
  Parent: parent
  Children:
  - grandchild (subnamespace)
  No conditions

No recent HNC events for objects in this namespace
$ kubectl hns describe grandchild
Hierarchy configuration for namespace grandchild
  Parent: child
  No children
  No conditions

No recent HNC events for objects in this namespace

kubectl describe namespaceで確認すると、HNC用のラベルとアノテーションが付与されています。

key value 意味
Label *.tree.hnc.x-k8s.io/depth ある祖先のnamespaceを基準とした階層の深さ
Label hnc.x-k8s.io/included-namespace (調べたもののわからず)
Annotations hnc.x-k8s.io/subnamespace-of subnamespaceの親
$ kubectl describe namespaces parent
Name:         parent
Labels:       hnc.x-k8s.io/included-namespace=true
              kubernetes.io/metadata.name=parent
              parent.tree.hnc.x-k8s.io/depth=0
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.
$ kubectl describe namespaces child
Name:         child
Labels:       child.tree.hnc.x-k8s.io/depth=0
              hnc.x-k8s.io/included-namespace=true
              kubernetes.io/metadata.name=child
              parent.tree.hnc.x-k8s.io/depth=1
Annotations:  hnc.x-k8s.io/subnamespace-of: parent
Status:       Active

No resource quota.

No LimitRange resource.
$ kubectl describe namespaces grandchild
Name:         grandchild
Labels:       child.tree.hnc.x-k8s.io/depth=1
              grandchild.tree.hnc.x-k8s.io/depth=0
              hnc.x-k8s.io/included-namespace=true
              kubernetes.io/metadata.name=grandchild
              parent.tree.hnc.x-k8s.io/depth=2
Annotations:  hnc.x-k8s.io/subnamespace-of: child
Status:       Active

No resource quota.

No LimitRange resource.

また、新たに追加されたresourceであるsubnamespaceanchorも確認できます。

$ kubectl api-resources | grep subnamespaceanchors
subnamespaceanchors               subns        hnc.x-k8s.io/v1alpha2                  true         SubnamespaceAnchor

$ kubectl get subnamespaceanchors -A
NAMESPACE   NAME         AGE
child       grandchild   105s
parent      child        113s

RoleとRoleBindingを伝搬させる

階層型namespaceができたところで、roleとrolebindingを伝搬させてみましょう。
以下のようなディレクトリとファイルを用意します。

$ tree .           
.
├── base
│   ├── kustomization.yaml
│   └── subnamespace-admin.yaml
├── child
│   └── kustomization.yaml
├── grandchild
│   └── kustomization.yaml
└── parent
    └── kustomization.yaml

defaultのserviceaccount(system:serviceaccount:${namespace}:default)にnamespacesとsubnamespaceanchorsのroleをbindingします。

base/subnamespace-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: subnamespace-admin
rules:
- apiGroups: ["hnc.x-k8s.io"]
  resources: ["namespaces", "subnamespaceanchors"]
  verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: subnamespace-admin-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: subnamespace-admin
subjects:
- kind: ServiceAccount
  name: default
base/kustomization.yaml
resources:
- subnamespace-admin.yaml
parent/kustomization.yaml
namespace: parent
namePrefix: parent-
resources:
- ../base
child/kustomization.yaml
namespace: child
namePrefix: child-
resources:
- ../base
grandchild/kustomization.yaml
namespace: grandchild
namePrefix: grandchild-
resources:
- ../base

parent namespaceのapply

parentのkustomization.yamlのみをapplyしてみます。

$ kubectl apply -k parent

parent namespaceにroleが作成されているのはもちろんですが、child namespaceとgranchild namespaceにも作成されている結果に注目してください。

$ kubectl get role -A | grep -E "NAME|parent"
NAMESPACE     NAME                                             CREATED AT
child         parent-subnamespace-admin                        2021-12-21T14:27:44Z
grandchild    parent-subnamespace-admin                        2021-12-21T14:27:44Z
parent        parent-subnamespace-admin                        2021-12-21T14:27:44Z

parent namespaceのroleは至って普通です。

$ kubectl describe role parent-subnamespace-admin -n parent
Name:         parent-subnamespace-admin
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources                         Non-Resource URLs  Resource Names  Verbs
  ---------                         -----------------  --------------  -----
  namespaces.hnc.x-k8s.io           []                 []              [*]
  subnamespaceanchors.hnc.x-k8s.io  []                 []              [*]

child namespaceとgrandchild namespaceのroleは、同じPolicyRuleでなおかつHNCのLabelが付与されています。

$ kubectl describe role parent-subnamespace-admin -n child
Name:         parent-subnamespace-admin
Labels:       app.kubernetes.io/managed-by=hnc.x-k8s.io
              hnc.x-k8s.io/inherited-from=parent
Annotations:  <none>
PolicyRule:
  Resources                         Non-Resource URLs  Resource Names  Verbs
  ---------                         -----------------  --------------  -----
  namespaces.hnc.x-k8s.io           []                 []              [*]
  subnamespaceanchors.hnc.x-k8s.io  []                 []              [*]
$ kubectl describe role parent-subnamespace-admin -n grandchild
Name:         parent-subnamespace-admin
Labels:       app.kubernetes.io/managed-by=hnc.x-k8s.io
              hnc.x-k8s.io/inherited-from=parent
Annotations:  <none>
PolicyRule:
  Resources                         Non-Resource URLs  Resource Names  Verbs
  ---------                         -----------------  --------------  -----
  namespaces.hnc.x-k8s.io           []                 []              [*]
  subnamespaceanchors.hnc.x-k8s.io  []                 []              [*]

rolebindingでも同様です。

$ kubectl get rolebinding parent-subnamespace-admin-binding -n parent
NAME                                ROLE                             AGE
parent-subnamespace-admin-binding   Role/parent-subnamespace-admin   99m
$ kubectl get rolebinding -A \
    -l app.kubernetes.io/managed-by=hnc.x-k8s.io \
    -l hnc.x-k8s.io/inherited-from=parent
NAMESPACE    NAME                                ROLE                             AGE
child        parent-subnamespace-admin-binding   Role/parent-subnamespace-admin   99m
grandchild   parent-subnamespace-admin-binding   Role/parent-subnamespace-admin   99m

このようにsubnamespaceを持つnamespaceに特定のresourceを作成すると、その子孫のsubnamespaceにおいても複製されます。
これがHNSによる伝搬(propagation)です。

system:serviceaccount:parent:defaultの権限

実際にsystem:serviceaccount:parent:defaultのroleを確認してみましょう。

$ kubectl auth can-i --list -n parent --as system:serviceaccount:parent:default | grep -E "Resources|namespace"
Resources                                       Non-Resource URLs                     Resource Names   Verbs
namespaces.hnc.x-k8s.io                         []                                    []               [*]
subnamespaceanchors.hnc.x-k8s.io                []                                    []               [*]

例えば、parent namespaceにおいてsubnamespaceanchorのlistは取得可能ですが、podsのlistは取得できません。

$ kubectl get subns -n parent --as system:serviceaccount:parent:default
NAME    AGE
child   2d4h
$ kubectl get pods -n parent --as system:serviceaccount:parent:default
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:parent:default" cannot list resource "pods" in API group "" in the namespace "parent"

これはchild namespace、grandchild namespaceにおいても同様です。

$ kubectl get subns -n child --as system:serviceaccount:parent:default
NAME         AGE
grandchild   2d4h
$ kubectl get subns -n grandchild --as system:serviceaccount:parent:default
No resources found in grandchild namespace.

system:serviceaccount:parent:defaultのroleとrolebindingがparent namespaceの子孫のsubnamespaceでも複製され、有効であると確認できました。

SubnamespaceによるNamespaceの権限移譲

このpropagationが有効活用されうるのはどのようなケースでしょうか。

従来のnamespace(Full Namespace)の課題として、namespaceの権限はcluster wide scopeであり、namespaceの管理のためには強い権限を付与しなければなりませんでした。

先ほどの例でいうと、system:serviceaccount:parent:defaultにはあえてnamespaceのroleをbindしましたが、このroleではFull Namespaceを作成できません。

$ kubectl create ns test --as system:serviceaccount:parent:default
Error from server (Forbidden): namespaces is forbidden: User "system:serviceaccount:parent:default" cannot create resource "namespaces" in API group "" at the cluster scope

これはCluster Roleでの付与が必要です。

base/namespace-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: namespace-admin
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: namespace-admin-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: namespace-admin
subjects:
- kind: ServiceAccount
  name: default
$ kubectl create ns test --as system:serviceaccount:parent:default
namespace/test created
$ kubectl delete namespaces test --as system:serviceaccount:parent:default
namespace "test" deleted

一方で、subnamespaceはresource: ["subnamespaceanchors"]のroleのみで作成できます。

ここでクラスタにおけるnamespaceの権限分離について考えてみましょう。仮にクラスタの管理者と開発者で権限を分ける運用を想定した場合、管理者としてはnamespaceのclusterroleを開発者に委譲してむやみにnamespaceが作成される事態を避けたいと考えるかもしれません。そうしたときの代替案として、開発者が新しいnamespaceを必要になったときに、管理者にその都度依頼を出して作成してもらう運用が考えられます。これによって不用意な権限委譲は避けられますが、一方で都度依頼を出さなければいけない煩わしさが残ります。

そこで、subnamespaceがこの問題を解決します。

namespaceはnon-namescoped resourceですが、subnamespaceanchorはnamespaced resourceです。

$ kubectl api-resources | grep -E "NAME|namespace"
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
namespaces                        ns           v1                                     false        Namespace
subnamespaceanchors               subns        hnc.x-k8s.io/v1alpha2                  true         SubnamespaceAnchor

今回の例ではclusterroleを付与せず、すでに付与されているroleでsubnamespaceを作成できます。その権限はparent namespaceの階層下にのみ有効です。

$ kubectl hns create child2 -n parent --as system:serviceaccount:parent:default
$ kubectl hns create grandchild2 -n child2 --as system:serviceaccount:parent:default
$ kubectl hns tree parent
parent
├── [s] child
│   └── [s] grandchild
└── [s] child2
    └── [s] grandchild2

新たにchild2 namespace、grandchild2 namespaceを作成できました。

一方で、roleはparent namespaceのscopeのため、それ以外のnamespaceに対してsubnamespaceを作成できません。

$ kubectl hns create child3 -n default --as system:serviceaccount:parent:default

Could not create subnamespace anchor.
Reason: subnamespaceanchors.hnc.x-k8s.io "child3" is forbidden: User "system:serviceaccount:parent:default" cannot create resource "subnamespaceanchors" in API group "hnc.x-k8s.io" in the namespace "default"

これまでのsystem:serviceaccount:parent:defaultの権限をまとめると、おおよそ以下のとおりです(すべての組み合わせまでは確認できていません)。

操作 コマンド例 権限あり 権限がない理由
新規Full Namespaceの作成 kubectl create namespace new-namespace × namespaceのClusterRoleがないため
既存のFull Namespaceへの操作 kubectl get namespaces
kubectl delete namespace another-namespace
× namespaceのClusterRoleがないため
自身のFull Namespaceへの操作 kubectl describe namespace parent
kubectl delete namespace parent
× namespaceのClusterRoleがないため
新規subnamespaceの作成 kubectl hns create new-subnamespace -n parent
既存のsubnamespaceへの操作 kubectl delete subnamespaceanchor child -n parent
kubectl delete subns grandchild -n child
子孫ではないsubnamespaceへの操作 kubectl get subnamespaceanchors -n default × subnamespaceanchorのRoleがparent namespace scopeのため

このようにnamespaceに閉じられたsubnamespaceanchorのroleによって、clusterroleを付与せずnamespaceを管理できます。

child namespace・grandchild namespaceのapply

残りのchild namespace、grandchild namespaceのkustomization.yamlもapplyしてみましょう。

$ kubectl apply -k child
$ kubectl apply -k grandchild

subnamespace-adminとsubnamespace-admin-bindingがchild namespaceに作られ、grandchild namespaceに伝搬します。grandchildのそれらはgrandchild namespaceにのみ作成されます。

$ kubectl get role -A | grep -E "NAME|parent|child"
NAMESPACE     NAME                                             CREATED AT
child         child-subnamespace-admin                         2021-12-21T19:57:22Z
child         parent-subnamespace-admin                        2021-12-21T19:54:48Z
grandchild    child-subnamespace-admin                         2021-12-21T19:57:22Z
grandchild    grandchild-subnamespace-admin                    2021-12-21T19:57:28Z
grandchild    parent-subnamespace-admin                        2021-12-21T19:55:14Z
parent        parent-subnamespace-admin                        2021-12-21T14:27:44Z
$ kubectl get rolebinding -A | grep -E "NAME|parent|child"
NAMESPACE     NAME                                                ROLE                                                  AGE
child         child-subnamespace-admin-binding                    Role/child-subnamespace-admin                         7m10s
child         parent-subnamespace-admin-binding                   Role/parent-subnamespace-admin                        9m44s
grandchild    child-subnamespace-admin-binding                    Role/child-subnamespace-admin                         7m10s
grandchild    grandchild-subnamespace-admin-binding               Role/grandchild-subnamespace-admin                    7m3s
grandchild    parent-subnamespace-admin-binding                   Role/parent-subnamespace-admin                        9m18s
parent        parent-subnamespace-admin-binding                   Role/parent-subnamespace-admin                        5h36m

system:serviceaccount:child:defaultの権限

system:serviceaccount:child:default の権限を確認します。
祖先となるparent namespaceへの操作はもちろん失敗します。

$ kubectl get subns -n parent --as system:serviceaccount:child:default
Error from server (Forbidden): subnamespaceanchors.hnc.x-k8s.io is forbidden: User "system:serviceaccount:child:default" cannot list resource "subnamespaceanchors" in API group "hnc.x-k8s.io" in the namespace "parent"
$ kubectl hns create child-2 -n parent --as system:serviceaccount:child:default

Could not create subnamespace anchor.
Reason: subnamespaceanchors.hnc.x-k8s.io "child-2" is forbidden: User "system:serviceaccount:child:default" cannot create resource "subnamespaceanchors" in API group "hnc.x-k8s.io" in the namespace "parent"

child namespace階層下の操作は成功します。

$ kubectl get subns -n child --as system:serviceaccount:child:default
NAME         AGE
grandchild   25m
$ kubectl get subns -n grandchild --as system:serviceaccount:child:default
No resources found in grandchild namespace.
$ kubectl hns create grandchild-2-by-child -n child --as system:serviceaccount:child:default
Successfully created "grandchild-2-by-child" subnamespace anchor in "child" namespace

system:serviceaccount:grandchild:defaultの権限

system:serviceaccount:grandchild:default も同様です。

権限なし

$ kubectl get subns -n parent --as system:serviceaccount:grandchild:default
$ kubectl get subns -n child --as system:serviceaccount:grandchild:default
$ kubectl hns create child-2 -n parent --as system:serviceaccount:grandchild:default
$ kubectl hns create child-2 -n child --as system:serviceaccount:grandchild:default

権限あり

$ kubectl get subns -n grandchild --as system:serviceaccount:grandchild:default
$ kubectl hns create great-grandson-by-grandchild -n grandchild --as system:serviceaccount:grandchild:default

例としてこのような階層になります。

$ kubectl hns tree parent
parent
└── [s] child
    ├── [s] grandchild
    │   └── [s] great-grandson-by-grandchild
    └── [s] grandchild-2-by-child

実践的なHNS

より実践的には以下のようなHNSの階層も考えられるでしょう(どこまでシングルクラスタで粘るかの是非はさておき)。

$ kubectl hns tree -A
department-a
├── [s] team-1
│   ├── [s] product-x
│   │   ├── [s] product-x-dev
│   │   ├── [s] product-x-prd
│   │   ├── [s] product-x-qa
│   │   └── [s] product-x-stg
│   └── [s] product-y
│       ├── [s] product-y-dev
│       ├── [s] product-y-prd
│       ├── [s] product-y-qa
│       └── [s] product-y-stg
└── [s] team-2
    └── [s] product-z
department-b
└── [s] team-3

propagate可能なresource

その他の仕様の一部をかんたんに紹介します。
デフォルトではRBACのroleとrolebindingのみです。
kubectl hns configまたはkubectl get hncconfigurationで確認・変更できます。

$ kubectl hns config set-resource [resource] --group [group] --mode [Propagate|Remove|Ignore]
$ kubectl hns config describe
Synchronized resources:
* Propagating: rolebindings (rbac.authorization.k8s.io/v1)
* Propagating: roles (rbac.authorization.k8s.io/v1)

Conditions:
$ kubectl get -o yaml hncconfiguration
apiVersion: v1
items:
- apiVersion: hnc.x-k8s.io/v1alpha2
  kind: HNCConfiguration
  metadata:
    creationTimestamp: "2021-12-19T11:39:43Z"
    generation: 57
    name: config
    resourceVersion: "70215"
    uid: 1a2275de-b5b4-4be3-849a-3d0939384732
  spec: {}
  status:
    resources:
    - group: rbac.authorization.k8s.io
      mode: Propagate
      numPropagatedObjects: 26
      numSourceObjects: 3
      resource: rolebindings
      version: v1
    - group: rbac.authorization.k8s.io
      mode: Propagate
      numPropagatedObjects: 15
      numSourceObjects: 3
      resource: roles
      version: v1
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

他には設定により以下のresourceや、custom resourceもpropagateできます。

  • Network Policies
  • Limit Ranges
  • Resource Quotas
  • Secrets
  • Config Maps

https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/334c3e8a06bc84db4bcb90a0b1df185b4fb36a82/docs/user-guide/concepts.md#policy-inheritance-and-object-propagation

propagationのモード

以下の3つのモードによって設定を変更できます。

  • Propagate
    子孫のnamespaceにresourceを複製する。
  • Remove
    複製元のresourceは保持して複製されたresourceを削除する。
  • Ignore
    複製元のresourceの作成・変更・削除を伝搬しなくなるが、複製されたresourceを削除せずに残す。roleとrolebindingを除いた他のresourceのデフォルト。

https://github.com/kubernetes-sigs/hierarchical-namespaces/blob/master/docs/user-guide/how-to.md#modify-the-resources-propagated-by-hnc

まとめ

実際に階層型namespaceを作成してresourceのpropagationを試してみました。今回紹介した仕様はほんの一部にすぎないので、より詳しく知りたい方はユーザーガイドに一通り目を通してみてください。

https://github.com/kubernetes-sigs/hierarchical-namespaces/tree/master/docs/user-guide

参考リンク

https://kubernetes.io/blog/2020/08/14/introducing-hierarchical-namespaces/
https://kubernetes.io/blog/2021/04/15/three-tenancy-models-for-kubernetes/
https://github.com/kubernetes-sigs/hierarchical-namespaces
https://cloud.google.com/anthos-config-management/docs/concepts/hierarchy-controller

Discussion