IT

KubernetesのNamespaceベース・マルチテナント設計:RBAC × NetworkPolicy

はじめに

Kubernetesで複数のチームやサービスを1つのクラスターに同居させる「マルチテナント」は、コスト効率と運用負荷の観点から多くの組織が採用するアーキテクチャだ。しかしその実装は「Namespaceを分ければいい」という単純な話ではない。RBAC(Role-Based Access Control)とNetworkPolicyを組み合わせて初めて、テナント間のアクセス分離通信分離が実現できる。

本記事ではNamespaceベースのマルチテナントに絞り、設計パターンと各コンポーネントの役割を体系的に解説する。

対象読者
Kubernetesをすでに運用しており、マルチテナント設計を整理・強化したい中上級エンジニア

マルチテナントの分離レイヤーを整理する

Kubernetesにおけるテナント分離には複数のレイヤーがある。まず全体像を把握しておこう。

本記事では①②を中心に扱う。③④は別途検討が必要な領域だ。

Namespaceだけでは何も分離されない

まず重要な前提として、Namespaceを作るだけでは分離は実現しない

Kubernetesのデフォルト挙動は以下の通り

分離したいものデフォルト追加設定が必要
APIアクセス分離されない(全Namespaceにアクセス可能)RBAC
ネットワーク通信分離されない(全Pod間通信が可能)NetworkPolicy
リソース使用量分離されない(上限なし)ResourceQuota

つまり「Namespaceベースのマルチテナント」とは、これらを組み合わせて設計することを指す。

3. RBAC設計パターン

3-1. RBACの基本構造

RBACは4つのリソースで構成される。

ClusterRole / Role               → 権限の定義(何ができるか)
ClusterRoleBinding / RoleBinding → 権限の付与(誰に与えるか)

スコープの違いを整理すると:

リソーススコープ用途
RoleNamespace内テナントへの権限付与
ClusterRoleクラスター全体プラットフォームチームの管理権限
RoleBindingNamespace内Role / ClusterRoleをNamespace内ユーザーに付与
ClusterRoleBindingクラスター全体ClusterRoleをクラスター全体で付与

マルチテナントで重要なのは、ClusterRoleをRoleBindingで参照できるという点だ。テナントに共通の権限セットをClusterRoleとして定義し、各Namespaceのみ有効なRoleBindingで付与するパターンが多用される。

3-2. 典型的なテナント権限設計

以下の3ロールを定義するのが一般的なパターンだ。

① developer ロール(Namespace内の通常操作)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: tenant-developer
rules:
  - apiGroups: ["", "apps", "batch"]
    resources: ["pods", "deployments", "services", "configmaps", "jobs"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]           # Secretはread-onlyに絞る
  - apiGroups: [""]
    resources: ["pods/log", "pods/exec"]
    verbs: ["get", "create"]

② viewer ロール(読み取り専用)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: tenant-viewer
rules:
  - apiGroups: ["", "apps"]
    resources: ["pods", "deployments", "services", "configmaps"]
    verbs: ["get", "list", "watch"]

③ Namespaceへの付与(RoleBinding)

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-a-developer
  namespace: team-a          # このNamespaceにのみ有効
subjects:
  - kind: Group
    name: team-a-engineers   # OIDCやSSO経由のグループ名
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole          # ClusterRoleを参照
  name: tenant-developer
  apiGroup: rbac.authorization.k8s.io

3-3. よくある設計ミスと対策

ミス①: wildcardの使用

# NG: 将来追加されるリソースにも権限が及ぶ
verbs: ["*"]
resources: ["*"]

# OK: 明示的に列挙する
verbs: ["get", "list", "watch"]

ミス②: ClusterRoleBindingで付与してしまう

# NG: クラスター全体にdeveloper権限が付与される
kind: ClusterRoleBinding   # ← 危険
subjects:
  - kind: Group
    name: team-a-engineers

# OK: Namespace内のRoleBindingで付与する
kind: RoleBinding           # ← Namespace内に限定
metadata:
  namespace: team-a

ミス③: ServiceAccountの放置

デフォルトのServiceAccountは最小権限で運用し、必要なワークロードには専用のServiceAccountを発行する。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: team-a
automountServiceAccountToken: false  # 不要なら自動マウントを無効化

4. NetworkPolicy設計パターン

4-1. NetworkPolicyが機能するための前提

NetworkPolicyはCNIプラグインが対応していないと機能しない。 デフォルトのKubernetesクラスター(特にkindやminikubeのデフォルト設定)では通信制御が適用されないため、本番環境では必ずCilium、Calico、Antrea などNetworkPolicyをサポートするCNIを使用すること。

4-2. デフォルト拒否ポリシー(Deny-All)

マルチテナントのベースラインとして、各Namespaceにデフォルト拒否ポリシーを設定するのが基本だ。

# Ingress(受信)のデフォルト拒否
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: team-a
spec:
  podSelector: {}          # Namespace内のすべてのPodに適用
  policyTypes:
    - Ingress
---
# Egress(送信)のデフォルト拒否
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: team-a
spec:
  podSelector: {}
  policyTypes:
    - Egress

これにより、明示的に許可したトラフィック以外はすべてドロップされる。

4-3. 必要な通信を許可するポリシー

デフォルト拒否の上で、必要な通信を追加していく。

① 同一Namespace内の通信を許可

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: team-a
spec:
  podSelector: {}
  ingress:
    - from:
        - podSelector: {}     # 同一Namespace内のPodからの通信を許可
  egress:
    - to:
        - podSelector: {}
  policyTypes:
    - Ingress
    - Egress

② DNS解決を許可(必須)

デフォルト拒否にするとDNSも止まるため、CoreDNSへのEgressを忘れずに許可する。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: team-a
spec:
  podSelector: {}
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
  policyTypes:
    - Egress

③ 共有サービス(monitoring, ingress)からのアクセスを許可

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-monitoring-scrape
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: my-app
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring
          podSelector:
            matchLabels:
              app: prometheus
      ports:
        - port: 8080
  policyTypes:
    - Ingress

4-4. テナント間通信の制御パターン

パターンA: 完全分離(テナント間通信を禁止)

# Namespaceのラベルを活用した分離
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-cross-tenant
  namespace: team-a
spec:
  podSelector: {}
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              tenant: team-a    # 自テナントNamespaceからのみ許可
  policyTypes:
    - Ingress

Namespaceに適切なラベルを付与しておくことが前提になる。

kubectl label namespace team-a tenant=team-a
kubectl label namespace team-b tenant=team-b

パターンB: 特定テナント間の通信のみ許可

# team-aからteam-bの特定サービスへのアクセスのみ許可
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-team-a
  namespace: team-b
spec:
  podSelector:
    matchLabels:
      app: shared-api
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              tenant: team-a
      ports:
        - port: 8080
  policyTypes:
    - Ingress

5. RBAC × NetworkPolicy の組み合わせで実現する分離モデル

ここまでの要素をまとめると、Namespaceベースのマルチテナントは以下の二軸で分離を実現する。

6. 運用上の注意点

Namespace作成をセルフサービス化しない

テナントが自由にNamespaceを作れると、NetworkPolicyやResourceQuotaが付与されない素のNamespaceが増殖する。Namespace作成はプラットフォームチームが管理し、テンプレート化したマニフェストセット(Namespace + RBAC + NetworkPolicy + ResourceQuota)を一括適用する仕組みを整えるべきだ。

NetworkPolicyの検証を怠らない

NetworkPolicyは「設定したつもりが効いていない」という事態が起きやすい。kubectl execでcurlを実行するか、netshootなどのデバッグコンテナを使って実際の通信可否を確認する習慣をつけよう。

# デバッグ用Podでの疎通確認
kubectl run netshoot --rm -it --image=nicolaka/netshoot \
  -n team-a -- curl http://service.team-b.svc.cluster.local:8080

RBACの棚卸しを定期的に行う

不要なRoleBindingが蓄積すると権限が形骸化する。kubectl auth can-i --listrakkessなどのツールで定期的に権限の棚卸しを行うことを推奨する。

まとめ

Namespaceベースのマルチテナントを機能させるには:

  1. RBAC: ClusterRoleで権限を定義し、RoleBindingで各Namespaceにスコープを限定する
  2. NetworkPolicy: デフォルト拒否をベースラインに置き、必要な通信を明示的に追加する
  3. 運用: Namespace作成のテンプレート化・NetworkPolicyの疎通確認・RBAC棚卸しをプロセスに組み込む

この3つが揃って初めて「テナントが互いに影響を与えない」状態が実現できる。次のステップとしては、vclusterを使ったクラスターレベル分離や、OPA/GatekeeperによるAdmission Controlの強化が視野に入ってくる。


参考: Kubernetes RBAC公式ドキュメント / NetworkPolicy公式ドキュメント

-IT
-,