目次
14 章

RBAC / NetworkPolicy / ResourceQuota

1つのクラスタに複数のチーム · 環境が一緒に住むマルチテナント運用の隔離を作る3つのポリシーオブジェクトを扱います。RBAC の Role · ClusterRole · ServiceAccount · RoleBinding モデル、NetworkPolicy の default-deny パターンと CNI 依存、ResourceQuota と LimitRange の対の関係までを一連の流れで整理しながら2部を締めくくります。

2部の最後の章です。第8章 StatefulSet / DaemonSet / Job / CronJob から 第13章 オートスケーリング まで、ワークロードを運用するモデルを一層ずつ積み上げてきました。コントローラ4種、永続データ、外部進入点、リソース要求 · 上限、ヘルスチェック、オートスケーリングまで — Pod 1個を安定的に立ち上げ、負荷に合わせて増やし減らす一連の流れが手に入りました。本章ではその上に一層さらに載せるテーマを扱います — 1つのクラスタに複数のチーム · 環境が一緒に住む状況のセキュリティとリソース統制 です。キーワードは3つです。RBAC (誰が何をできるか)、NetworkPolicy (どのトラフィックが通るか)、ResourceQuota (どれだけ作れるか)。3つのオブジェクトすべてがネームスペース単位ポリシーだという共通点があり、第7章 Namespace とラベル で「Namespace 自体はセキュリティ境界ではない」と押さえておいたその空白を、この3つのオブジェクトが埋めます。

本章の終わりには 1つのクラスタの上に複数の環境 · チームが安全に一緒に住める隔離の3次元 が手に入ります。2部の振り返りと次の部 (3部 深さ) の案内も一緒に込めます。

3つのポリシーの共通座標 — ネームスペース単位 #

第7章 で Namespace を扱うとき一行を残しておきました — Namespace 自体はセキュリティ境界ではありません。 オブジェクト名を分ける論理的な区画でしかなく、本当の隔離はその上に載るポリシーオブジェクトたちが作るという話でした。その上に載るポリシーの本体が本章のテーマ3つです。

次元オブジェクト説明
権限Role / ClusterRole / RoleBinding / ClusterRoleBinding誰がどのオブジェクトにどの動詞を使えるか
トラフィックNetworkPolicyどの Pod がどの Pod と通信できるか
リソースResourceQuota / LimitRange1つのネームスペースがどれだけ作れるか

3つのオブジェクトすべてがネームスペース単位で適用されるか (NetworkPolicy、ResourceQuota、LimitRange、Role / RoleBinding)、ネームスペースに連携して適用されます (ClusterRole + RoleBinding の組み合わせ)。1つのクラスタの上に dev / staging / prod、またはチーム A / チーム B が一緒に住むマルチテナント運用の隔離は、この3次元が合わさったとき初めて完成します。

本章の流れは単純です。権限から — RBAC を最も詳しく扱い、その次にトラフィック (NetworkPolicy)、その次にリソース (ResourceQuota / LimitRange) の順で進みます。

RBAC — 誰が何をできるか #

RBAC (Role-Based Access Control) は K8s API 呼び出し単位で権限を表現するモデルです。kubectl get podskubectl create deploymentkubectl delete secret のようなすべての動作は結局 K8s API サーバに対する HTTP リクエストです。RBAC はそのリクエスト1件1件に対して「この主体がこの動詞をこのリソースに使えるか」を検査します。

モデルは4つのオブジェクトで表現されます。権限のまとまり (Role / ClusterRole) と、その権限と主体をつなぐオブジェクト (RoleBinding / ClusterRoleBinding) です。

オブジェクト何かスコープ
Role権限のまとまり (verbs + resources)ネームスペース
ClusterRole権限のまとまり (verbs + resources)クラスタ (すべてのネームスペース共有)
RoleBindingRole または ClusterRole を主体に付与ネームスペース
ClusterRoleBindingClusterRole を主体に付与クラスタ全域

ここで一つ微妙な部分があります。RoleBinding は ClusterRole を参照することもできます。 ClusterRole は権限のまとまりで RoleBinding はそのまとまりをどのネームスペースで誰に与えるか決めるオブジェクトなので、「標準 ClusterRole (例: viewedit) を1つ作っておいてネームスペースごとに RoleBinding で別々の人に付与」するパターンが運用で最もよくあります。

本章は RBAC のマニフェストと運用パターンまでを扱います。Aggregated ClusterRole、impersonation、EKS の IRSA · GKE の Workload Identity のような外部 IAM マッピング、トークンライフサイクルのような深さは 第16章 RBAC / ServiceAccount の深層 で本格的に扱います。

Subject — 権限を受ける側 #

権限が付与される主体 (Subject) は3つです。

  • User — 人のユーザです。K8s 自体にはユーザのデータベースがなく、外部認証 (OIDC、x509 クライアント証明書、クラウド IAM マッピングなど) がユーザ名を教えてくれます。
  • Group — ユーザのまとまりです。User と同様に外部認証がグループ情報を K8s に伝えます。
  • ServiceAccount — Pod が K8s API にアクセスするとき使う ID です。人ではなくワークロードのアイデンティティです。

この3つのうち K8s マニフェストで直接作って管理するのは ServiceAccount がほぼ唯一です。User と Group は外部認証の産物なので K8s の中にオブジェクトとして存在しません。RoleBinding の subjects フィールドに書かれるだけです。

ServiceAccount — Pod の ID #

すべての Pod はある ServiceAccount の ID を持ってクラスタに住んでいます。マニフェストに spec.serviceAccountName を書かないとそのネームスペースの default ServiceAccount が自動で結ばれます。Pod の中で kubectl のようなツールで K8s API にアクセスすると、その呼び出しはその ServiceAccount の権限で行われます。

ネームスペースの ServiceAccount 確認
kubectl get serviceaccounts -n default
出力例
NAME      SECRETS   AGE
default   0         3d

基本の default ServiceAccount はどの権限も付与されていません。RBAC を適用した運用クラスタで Pod の中から kubectl get pods を試みると次のメッセージが出るのが正常です。

権限なしメッセージ
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:default" cannot list resource "pods" in API group "" in the namespace "default"

このメッセージを見て RoleBinding で権限を結んでやれば同じコマンドが通ります。次の例でその流れを追いかけます。

RBAC マニフェスト1まとまり #

最も単純なシナリオをマニフェスト1枚に整理してみます。dev ネームスペースに pod-reader という ServiceAccount を作り、その SA に「Pod を読める権限」を付与します。そしてその SA を使う Pod の中で kubectl get pods が通るか確認する流れです。

rbac-pod-reader.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-reader
  namespace: dev
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: dev
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader
  namespace: dev
subjects:
  - kind: ServiceAccount
    name: pod-reader
    namespace: dev
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

3つのオブジェクトの責任を一行ずつ押さえておきます。

  • ServiceAccount pod-readerdev ネームスペースの新しい ID です。まだ何の権限もありません。
  • Role pod-readerpods リソースに対して getlistwatch の動詞を許可する権限のまとまりです。apiGroups: [""] はコア API グループ (Pod、Service、ConfigMap など) を指します。
  • RoleBinding pod-reader — Role と ServiceAccount をつなぐオブジェクトです。subjects が受ける側、roleRef が与える側です。

この3つのオブジェクトを一度に適用したあと、その ServiceAccount を使う Pod を立ち上げてみます。

reader-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: reader
  namespace: dev
spec:
  serviceAccountName: pod-reader
  containers:
    - name: kubectl
      image: bitnami/kubectl:1.32
      command: ["sleep", "3600"]
Pod の中で kubectl 実行
kubectl apply -f rbac-pod-reader.yaml
kubectl apply -f reader-pod.yaml
kubectl exec -n dev reader -- kubectl get pods -n dev
出力例
NAME     READY   STATUS    RESTARTS   AGE
reader   1/1     Running   0          30s

同じ Pod の中で権限のない動作を試みると止められることも確認できます。

許可されていない動作
kubectl exec -n dev reader -- kubectl create deployment nginx --image=nginx -n dev
出力例 — 拒否
error: failed to create deployment: deployments.apps is forbidden: User "system:serviceaccount:dev:pod-reader" cannot create resource "deployments" in API group "apps" in the namespace "dev"

pod-reader Role は Pod の get / list / watch だけを与えて Deployment の create は与えなかったので、正確にその部分で拒否されます。

verbs と resources の核心 #

Role / ClusterRole の rules でよく使われる verbs を表に整理すると次のとおりです。

verb意味
get単一オブジェクトの照会
listオブジェクト一覧の照会
watch変更イベントの購読
createオブジェクト生成
updateオブジェクト更新 (全体)
patchオブジェクト部分更新
deleteオブジェクト削除
deletecollectionマッチするオブジェクトの一括削除

読み取りだけ許可するなら getlistwatch の3つを一緒に与えます。kubectl get のようなコマンドが内部的に list を使い、watch は informer キャッシュの更新に使われるからです。書き込みまで許可するなら createupdatepatchdelete を追加します。

resourcespodsservicesconfigmaps のように複数形の名前を書きます。apiGroups はそのリソースが属する API グループです。コアグループ (Pod / Service / ConfigMap / Secret など) は [""] (空文字列)、Deployment / StatefulSet / DaemonSet は ["apps"]、Job / CronJob は ["batch"]、Ingress は ["networking.k8s.io"] です。リソースがどのグループに属するかは kubectl api-resources で一度に確認できます。

リソースとグループ確認
kubectl api-resources

kubectl auth can-i — 権限の検証 #

RBAC を触っておくと次の質問がすぐに付いてきます — 「それでこのユーザは本当にこの動作ができるのか?」マニフェストをしばらく見ていても結果が一目で入ってこないことが多いです。K8s がこの質問に直接答えてくれるコマンドが kubectl auth can-i です。

現在の自分の権限確認
kubectl auth can-i create pods -n dev
kubectl auth can-i delete deployments -n prod

yes / no のどちらか一つが出力されます。別のユーザや ServiceAccount の立場で問いたいなら --as オプションで impersonation を使います。

別のユーザ · SA を仮定して問う
kubectl auth can-i create pods --as=alice -n dev
kubectl auth can-i list secrets --as=system:serviceaccount:dev:pod-reader -n dev

--as オプション自体にも権限が必要です (impersonation 権限)。運用クラスタの admin が新しく作った RBAC ポリシーが意図どおり動作するか確認するとき最もよく使われるパターンです。すべての権限を一度に見たいなら --list を付けます。

1人のユーザのすべての権限を列挙
kubectl auth can-i --list --as=alice -n dev

診断の完成された流れと一般的な権限拒否のトラブルシューティング木は 第27章 kubectl デバッグパターン で整理します。

よくある落とし穴 — 広すぎる ClusterRole #

RBAC を最初に適用するとき最もよく犯すミスが 面倒さに負けて ClusterRole cluster-admin を ServiceAccount に結ぶ パターンです。

アンチパターン — 絶対に運用に置かないこと
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: app-admin
subjects:
  - kind: ServiceAccount
    name: app
    namespace: dev
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

cluster-admin は K8s API 全体に対する無制限の権限です。この ClusterRoleBinding が適用された ServiceAccount のトークンが Pod 1個でも外部へ漏れると、そのトークンでクラスタのすべてのネームスペースのすべての Secret を読み、すべてのオブジェクトを消せます。コンテナイメージの中のライブラリに脆弱性が一つあるだけでもその一点がクラスタ全体の事故に広がります。

運用の標準原則は 最小権限 (least privilege) です。ワークロードが本当に必要な verbs と resources だけを正確に絞って Role にまとめ、その Role を RoleBinding で付与します。K8s があらかじめ作っておく標準 ClusterRole があり、これを RoleBinding でネームスペースに限定して使うパターンが一般的な出発点です。

ClusterRole説明
viewネームスペースのオブジェクトを読み取り。Secret は除く
editview + ほとんどのオブジェクトの書き込み。Role / RoleBinding など RBAC オブジェクトは除く
adminedit + そのネームスペースの中の RBAC オブジェクト管理
cluster-adminクラスタ全体に無制限

view / edit / admin を RoleBinding で特定のネームスペースに限定すると、人 · チーム · サービスに適度な権限のまとまりを速く付与でき、cluster-adminクラスタ運用者一握り にだけ ClusterRoleBinding で付与する形が標準です。マニフェスト自体を admission の段階で止めるポリシー (例: 「cluster-admin の連携を禁止」) は 第17章 Admission Controller の OPA Gatekeeper · Kyverno が扱う領域です。

automountServiceAccountToken #

ServiceAccount のトークンは基本的に Pod の中の /var/run/secrets/kubernetes.io/serviceaccount/token に自動でマウントされます。K8s API を呼ぶ予定のない Pod には、そのトークンすらマウントしない方が安全です。マニフェストの automountServiceAccountToken: false でオフにできます。

API 呼び出しをしない Pod — トークンをマウントしない
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  automountServiceAccountToken: false
  containers:
    - name: nginx
      image: nginx:1.27

この一行を入れておくと、その Pod が万一侵害されても K8s API に直接アクセスするトークンがありません。セキュリティガイドの常連の推奨事項です。トークンを外部の秘密ストア · IRSA と連携して「パスワード0」で運用する本格的なパターンは 第29章 シークレット運用 で扱います。

NetworkPolicy — Pod 間トラフィックの統制 #

RBAC が K8s API に対する権限を扱うとすれば、NetworkPolicy は Pod 間の IP トラフィック を扱います。同じクラスタの2つの Pod が互いの IP を知って通信しようとするとき、そのトラフィックが許可されるか検査するポリシーです。

基本はすべて通過 #

K8s ネットワークモデルの基本は単純です — NetworkPolicy がなければすべての Pod が互いに通信できます。 同じネームスペースであれ別のネームスペースであれ、Pod の IP さえ知れば自由にパケットを送れます。マルチテナントクラスタでこの基本値がそのまま置かれると、1つのネームスペースの Pod が別のネームスペースの DB Pod に自由にアクセスする形になります。

NetworkPolicy の動作ルールは次の2行に整理されます。

  • NetworkPolicy が1つでもマッチする Pod のトラフィックは、そのポリシーの policyTypes に書かれた方向に対して default-deny が適用されます。
  • そしてそのポリシーの ingress / egress ルールに明示的に許可されたトラフィックだけが通過します。

マッチするポリシーが1枚もない Pod は上のルールが発動しないのですべてのトラフィックが通ります。マッチするポリシーが1枚でもあると、そのポリシーの方向に対してはホワイトリストモデルへ転換されます。

CNI が NetworkPolicy を支援しなければならない #

NetworkPolicy は K8s マニフェスト次元では標準ですが、実際にトラフィックを止める作業は CNI (Container Network Interface) プラグインがします。 だから CNI が NetworkPolicy を支援しないとマニフェストを適用しても何も起こりません。トラフィックはそのまま通ります。

CNINetworkPolicy 支援
Calico支援
Cilium支援 (eBPF ベース)
Antrea支援
flannel未支援
EKS の amazon-vpc-cni別途オプションの有効化が必要 (Calico を一緒にインストールするか vpc-cni の NetworkPolicy オプションをオンにしなければならない)

運用クラスタで NetworkPolicy を使う計画なら、クラスタを作るときから CNI を選んでおかなければなりません。基本の EKS クラスタで NetworkPolicy マニフェストを適用して「なぜトラフィックが止まらないのか?」がよくある落とし穴です — CNI が支援しなければマニフェストはただ etcd に入っているオブジェクトでしかありません。CNI データプレーンの本格的なモデル (iptables ベース vs eBPF ベース、Calico vs Cilium) は 第15章 CNI の深層 で扱います。

default-deny → allow パターン #

運用の標準パターンは ネームスペースに default-deny ポリシーを1枚かけて、必要な通信だけを明示的に許可 することです。

default-deny-all.yaml — ネームスペースの中のすべての Pod に適用
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: prod
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

podSelector: {} は空のセレクタで「このネームスペースのすべての Pod」を指します。policyTypesIngressEgress がどちらも書かれていて ingress / egress のルールが1行もないので、このネームスペースのすべての Pod は入ってくるトラフィックも出ていくトラフィックもすべて遮断されます。

この状態のままにしておくと Pod が DNS の照会すらできなくなるので、最低限 DNS トラフィックは解放しなければなりません。

allow-dns.yaml — kube-system の CoreDNS へ出ていく 53/UDP、53/TCP を許可
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: prod
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

その次にワークロードごとに必要な通信を1枚ずつ追加します — frontend が backend へ行く 80/TCP、backend が DB へ行く 5432/TCP のような具合です。

NetworkPolicy マニフェスト — frontend → backend #

最もよくある1コマを1枚に書いておくと次のとおりです。backend Pod が frontend Pod から入ってくる 8080/TCP トラフィックだけを受けるようにする ingress ポリシーです。

backend-allow-frontend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-frontend
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

読み方は次のとおりです。

  • spec.podSelector — このポリシーが適用される Pod です。app=backend のラベルが付いた Pod にだけ適用されます。
  • policyTypes: [Ingress] — 入ってくる方向に対するポリシーであることを明示します。egress はこのポリシーでは制御しません。
  • ingress[0].from — どこから来るトラフィックを許可するか。app=frontend のラベルが付いた Pod から来ます。
  • ingress[0].ports — どのポートに対して。8080 / TCP だけを許可します。

from のセレクタは3つの中から選べます — この3つは別々にまたは一緒に使えます。

セレクタ意味
podSelector同じネームスペースの Pod ラベルマッチ
namespaceSelector別のネームスペースのすべての Pod (またはそのネームスペース + podSelector の組み合わせ)
ipBlockCIDR 表現で IP 範囲 (外部 IP またはノード IP)

namespaceSelectorpodSelector を同じ from の項目に一緒に書くと「特定のネームスペースの特定のラベルの Pod」になります。2つを別々に書くと「そのネームスペースのすべての Pod または同じネームスペースのそのラベルの Pod」の意味になってしまうので、意図した形が何かを正確に見て書かなければなりません。

namespaceSelector + podSelector を1項目で — AND
ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            env: prod
        podSelector:
          matchLabels:
            app: frontend
別々に書いた場合 — OR
ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            env: prod
      - podSelector:
          matchLabels:
            app: frontend

この2つのマニフェストの意味が異なるという点が NetworkPolicy の常連の落とし穴です。上のマニフェストは「env=prod ネームスペースの frontend Pod だけ」で、下のマニフェストは「env=prod ネームスペースのすべての Pod または同じネームスペースの frontend Pod」です。

egress ルール — 出ていく方向 #

ingress の鏡が egress です。backend Pod が DB へ 5432 ポートで出ていく通信だけを許可するポリシーは次の形です。

backend-egress-to-db.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-egress-to-db
  namespace: prod
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

このポリシー1枚が適用されると backend Pod は postgres Pod の 5432 / TCP 以外にはどこへも出ていけません。上で作った default-deny + allow-dns の組み合わせと合わせると、backend Pod の出ていく通信は「DNS + DB」の2つに限定されます。外部インターネットへ行く通信をワークロードごとに正確にホワイトリストで狭めておくのが運用セキュリティの標準の形です。

NetworkPolicy の限界 #

NetworkPolicy は L3 / L4 ポリシー です。IP、ポート、プロトコルだけを見ます。HTTP メソッドやパスのような L7 ポリシーは NetworkPolicy の範囲を外れます — Cilium の L7 ポリシーや Istio / Linkerd のようなサービスメッシュがその次元を扱います (後続の K8s 深掘り本の領域です)。そして NetworkPolicy はクラスタの中のトラフィックポリシーで、外部インバウンドは 第10章 Ingress の Ingress · LoadBalancer の次元 で、外部アウトバウンドは NAT ゲートウェイのセキュリティグループ で別途統制されます。1つのクラスタのセキュリティの形は複数の層のポリシーが一緒に作っていきます。

ResourceQuota — ネームスペースのリソース合計上限 #

第11章 resources.requests / limits でコンテナ単位のリソースモデル — requestslimits — を扱いました。コンテナ1個の保証と上限を決めるオブジェクトでした。その上に一層さらに載せるのが ネームスペース単位の合計上限 である ResourceQuota です。

運用シナリオは明確です。1つのクラスタに dev / staging / prod、または複数のチームが一緒に住むとき、dev ネームスペースがすべてのノードの CPU を使い尽くして prod ワークロードのリソースが足りなくなる事故を防がなければなりません。ResourceQuota がその空白を埋めます。

ResourceQuota マニフェスト #

dev-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "50"
    services: "20"
    configmaps: "30"
    secrets: "30"
    persistentvolumeclaims: "10"
    requests.storage: 100Gi

この ResourceQuota が dev ネームスペースに適用されると次の合計が限度を超えられません。

  • そのネームスペースの中のすべての Pod の requests.cpu の合計 ≤ 4コア
  • requests.memory の合計 ≤ 8Gi
  • limits.cpu の合計 ≤ 8コア
  • limits.memory の合計 ≤ 16Gi
  • オブジェクトの個数 — Pod 50個、Service 20個、ConfigMap · Secret 各30個、PVC 10個
  • PVC の requests.storage の合計 ≤ 100Gi

上限を超えるオブジェクトの生成は K8s API サーバで拒否されます。dev ネームスペースにすでに4コアが割り当てられた状態で追加 Pod の requests.cpu: 1 を作ろうとすると、その Pod の生成リクエストは admission の段階で拒否され次のメッセージが出ます。

ResourceQuota 超過時の拒否
Error from server (Forbidden): error when creating "...": pods "..." is forbidden: exceeded quota: dev-quota, requested: requests.cpu=1, used: requests.cpu=4, limited: requests.cpu=4

ResourceQuota 動作の確認 #

ネームスペースの ResourceQuota 使用量
kubectl get resourcequota -n dev
kubectl describe resourcequota dev-quota -n dev
describe 出力例
Name:            dev-quota
Namespace:       dev
Resource         Used    Hard
--------         ----    ----
configmaps       12      30
limits.cpu       6       8
limits.memory    12Gi    16Gi
pods             18      50
persistentvolumeclaims  3   10
requests.cpu     3       4
requests.memory  6Gi     8Gi
requests.storage 30Gi    100Gi
secrets          15      30
services         8       20

Used / Hard の表が1ページに出る形です。運用クラスタで1つのネームスペースがどこまで埋まっているか確認するとき describe が最も速いツールです。

LimitRange と対 — コンテナ単位の基本値 · 上限 #

ResourceQuota の微妙な落とし穴の一つが — ResourceQuota が有効化されたネームスペースに Pod を作るとき、コンテナに requests / limits を書かないと拒否されます。 ResourceQuota が合計を計算するにはすべてのコンテナのリソース値が必要なのに、マニフェストに書かれていないコンテナは合計の計算ができないからです。

この部分の運用の安全網が LimitRange です。LimitRange はコンテナ単位で基本値と最大値 · 最小値を決めるオブジェクトです。第11章 で一度押さえたオブジェクトで、本章では ResourceQuota と対でもう一度整理します。

dev-limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: dev
spec:
  limits:
    - type: Container
      default:
        cpu: 200m
        memory: 256Mi
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      max:
        cpu: "2"
        memory: 2Gi
      min:
        cpu: 50m
        memory: 64Mi

この LimitRange が適用されたネームスペースに Pod マニフェストが入ってくると次のことが起こります。

  • コンテナに requests がなければ defaultRequest (100m / 128Mi) が自動で埋められます。
  • コンテナに limits がなければ default (200m / 256Mi) が自動で埋められます。
  • コンテナの requestslimitsmax (2 / 2Gi) を超えると拒否されます。
  • min (50m / 64Mi) 未満なら拒否されます。

ResourceQuota と LimitRange の責任を分けておくと一行ずつ整理されます。

オブジェクト単位何を決めるか
LimitRangeコンテナ基本値 · 最大 · 最小
ResourceQuotaネームスペース合計上限、オブジェクト個数の上限

運用の形は2つを一緒に置くことです。LimitRange が壊れたマニフェストの空白を埋めてくれて、ResourceQuota がその埋められた値たちの合計が限度を超えないように止めます。2つが一緒にあって初めてマルチテナント運用のリソースポリシーが安定的に回ります。

scopes / scopeSelector — 一部の Pod にだけ #

ResourceQuota は基本的にネームスペースのすべての Pod に適用されますが、scope を狭められます。よく使われるパターンは PriorityClass 別の分離です — 例えば high-priority ワークロードのリソース合計だけを別に制限したり、BestEffort QoS の Pod だけを別途制限する具合です。

high-priority Pod にだけ適用される quota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: high-priority-quota
  namespace: dev
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 4Gi
  scopeSelector:
    matchExpressions:
      - operator: In
        scopeName: PriorityClass
        values: ["high"]

運用の基本の形では scope なしで全体に適用する1枚から始め、必要なとき scope を持つポリシーを追加する方式が無難です。

3つのポリシーの協業 — マルチテナントクラスタの隔離 #

3つのオブジェクトが作る隔離の形を1つの図に整理しておくと次のとおりです。

3次元の協業
[ RBAC ]                 誰がオブジェクトを作れるか
   │                     (verbs × resources × namespace)
[ NetworkPolicy ]        作られた Pod が誰と通信するか
   │                     (podSelector × from/to × ports)
[ ResourceQuota ]        そのネームスペースがどれだけ作れるか
   │                     (cpu/memory 合計 + オブジェクト個数)
[ LimitRange ]           コンテナ単位の基本値 · 上限
                         (default / max / min)

3つのオブジェクトが別々に回っているように見えますが、実際には1つのネームスペースの隔離を一緒に作ります。dev ネームスペースに RBAC を適用して dev チームだけがその中のオブジェクトを触れるようにし、NetworkPolicy で dev の Pod が prod の DB へ行くトラフィックを止め、ResourceQuota で dev が使い尽くせる CPU · メモリ · オブジェクト個数を限定します。この3つがすべて適用されているとき、dev で発生した事故が staging と prod へ漏れない隔離が成立します。

この隔離がきれいに回るにはもう一つの前提が必要です — ネームスペース自体をよく分けておくこと です。環境別 (dev / staging / prod)、チーム別 (team-a / team-b)、またはサービス単位でネームスペースをどう分けるかのポリシーは、クラスタを最初にセットアップするときに決めておかなければならない決定です。この決定を曖昧にしたまま RBAC / NetworkPolicy / ResourceQuota を適用しようとすると、ポリシーの目をどこに合わせるべきか毎回揺れます。

2部の振り返り — 7章で手に入ったもの #

2部の最後の章なので一度整理します。1部がマニフェスト1枚を読み書きする段階まで連れていってくれたとすれば、2部はその上に運用の目を一層ずつ加えました。

  • 第8章StatefulSet / DaemonSet / Job / CronJob です。Deployment ではないコントローラ4種で、アイデンティティとディスクが必要なワークロード、ノードごとに1つずつ立ち上がらなければならないワークロード、一度きりの作業、周期実行の作業の4つのパターンを扱います。
  • 第9章PV / PVC / StorageClass です。永続データモデルとして、静的 · 動的プロビジョニング、accessModes、reclaimPolicy、volumeBindingMode、allowVolumeExpansion を整理し、StatefulSet の volumeClaimTemplates が何を作るかを説明します。
  • 第10章Ingress と Ingress Controller です。外部進入点を集約するオブジェクトと、そのマニフェストを実際のトラフィックルーティングへ解いてくれるコントローラを扱い、HTTP / HTTPS、TLS 終端、仮想ホスト、パスベースのルーティングまで整理します。
  • 第11章resources.requests / limits です。コンテナ単位のリソース要求と上限を説明し、requests がスケジューリングの基準、limits が OOM · CPU スロットリングの基準だという点と、QoS の3等級が evict 優先順位を決める流れを扱います。
  • 第12章Health check です。liveness / readiness / startup probe の3つで、コンテナの生きていること · サービスの準備ができたこと · 初期化段階を K8s がどう判定するかを見ます。
  • 第13章HPA / VPA / Cluster Autoscaler です。Pod 数、Pod リソース、ノード数が負荷に合わせて自動で変わる3次元を説明し、metrics-server · custom metrics · HPA + VPA の衝突の落とし穴も押さえます。
  • 第14章 (本章)RBAC / NetworkPolicy / ResourceQuota です。セキュリティとリソースポリシーを通じて、誰が、どのトラフィックが、どれだけ許可されるかのマルチテナントクラスタの隔離の3次元を扱います。

この7章をすべて追ってきた時点なら、会社のクラスタのマニフェストディレクトリでどんな種類のオブジェクトに出会っても、その意図と運用上の落とし穴を一行で読める段階です。kind: StatefulSet を見ると volumeClaimTemplates と headless Service を自然に思い浮かべ、kind: Ingress を見るとその後ろの Ingress Controller が何かを問い、resources の下の空の limits を見ると OOM の危険と LimitRange の不在を同時に疑うようになります。マニフェスト1枚がクラスタの中でどう回るかのモデルが頭の中に定着した段階です。

練習問題 #

  1. 上の本文の rbac-pod-reader.yamlreader-pod.yaml を順に適用したあと、kubectl exec -n dev reader -- kubectl get pods -n devkubectl exec -n dev reader -- kubectl create deployment nginx --image=nginx -n dev の2つのコマンドの結果を記録します。その次に同じ SA に deploymentscreate 権限をさらに付与するには Role の rules をどう追加すべきかをマニフェスト一行で書き、kubectl auth can-i create deployments --as=system:serviceaccount:dev:pod-reader -n dev で検証します。
  2. prod ネームスペースに §「default-deny → allow パターン」の default-deny-allallow-dns の2つのポリシーを適用したあと、frontend → backend 8080 / TCP だけを許可するポリシー1枚を追加します。その次に backend Pod の中から別のネームスペースの任意の Service へ curl を試みて遮断されるか、許可された frontend からは通るかを時間順に記録します。CNI が NetworkPolicy を支援しないクラスタ (例: 基本の flannel) では同じポリシーがトラフィックを止められないという §「CNI が NetworkPolicy を支援しなければならない」のモデルとどうつながるかを一段落でメモします。
  3. dev ネームスペースに dev-quotadev-limits の2つのオブジェクトを一緒に適用します。requests / limits を抜かした Deployment マニフェストを apply したとき LimitRange が自動で埋めてくれる値、max を超えるマニフェストが拒否されるメッセージ、ResourceQuota の合計が限度に達したとき新しい Pod の生成が拒否されるメッセージをそれぞれ記録します。3つのメッセージがどの段階 (admission、validation) で出るかを §「3つのポリシーの協業」の図と合わせて一段落に整理します。

一行まとめ: マルチテナントクラスタの隔離は、RBAC (K8s API 権限)、NetworkPolicy (Pod 間の IP トラフィック)、ResourceQuota + LimitRange (ネームスペースのリソース合計 + コンテナ単位の基本値) の3次元が合わさったとき成立する。運用の標準原則は RBAC の最小権限、NetworkPolicy の default-deny + allow、ResourceQuota と LimitRange の対の運用だ。Namespace 自体はセキュリティ境界ではなく、この3つのポリシーが載って初めて本当の隔離が作られる。

次の章 #

2部が終わりました。3部 深さの最初の章である 第15章 CNI の深層 からは視点を一段さらに移します — マニフェストの動作を支える データプレーン · ポリシーエンジン · API 拡張 の深さへ入っていきます。本章の NetworkPolicy が実際にどう止められるかの答えが第15章の Calico / Cilium / eBPF です。

3部全体の筋書きは次のとおりです。

テーマ
第15章CNI の深層 — Calico · Cilium · eBPF
第16章RBAC · ServiceAccount の深層 — Aggregated ClusterRole · Impersonation · IRSA · Workload Identity
第17章Admission Controller — OPA Gatekeeper · Kyverno
第18章CRD と Operator パターン — controller-runtime
第19章可観測性 — Prometheus · Grafana · Loki · OpenTelemetry
第20章GitOps — ArgoCD · Flux

3部を終えるとクラスタをセットアップする人の視野で K8s を見られる段階に到達します。その次の 4部 EKS 実戦 では、AWS EKS の上に実際のサービスを最初から載せて運用する一連の流れを追いかけます。

X