K8s 中級 #7 RBAC / NetworkPolicy / ResourceQuota — セキュリティとリソースポリシー

読了 23分

K8s 中級シリーズの最後の記事です。#1 から #6 までワークロードを運用するモデルを 1 層ずつ積み上げてきました。コントローラ 4 種、永続データ、外部入口、リソース要求・上限、ヘルスチェック、オートスケーリングまで — Pod 1 つを安定的に立てて負荷に合わせて増減させる 1 サイクルが手に入りました。この記事ではその上にもう 1 層加えるテーマを扱います — 1 つのクラスタに複数のチーム・環境が一緒に住む状況のセキュリティとリソース統制 です。キーワードは 3 つです。RBAC(誰が何をできるか)、NetworkPolicy(どんなトラフィックが通るか)、ResourceQuota(どれくらい作れるか)。3 つのオブジェクトすべてが namespace 単位ポリシーである共通点があり、基礎 #7 で「Namespace 自体はセキュリティ境界ではない」と触れたその空白をこの 3 つが埋めます。シリーズの最後の記事なので、7 編全体の振り返りと次のトラック(K8s 上級)の予告も一緒に入れます。

このシリーズは K8s 中級 7 編です。

3 つのポリシーの共通座標 — namespace 単位 #

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

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

3 つのオブジェクトすべてが namespace 単位で適用されるか(NetworkPolicy、ResourceQuota、LimitRange、Role/RoleBinding)、namespace に結合して適用されます(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)namespace
ClusterRole権限の束 (verbs + resources)クラスタ (すべての namespace で共有)
RoleBindingRole または ClusterRole を主体に付与namespace
ClusterRoleBindingClusterRole を主体に付与クラスタ全域

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

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 を書かないと、その namespace の default ServiceAccount が自動で結ばれます。Pod 内で kubectl のようなツールで K8s API にアクセスすると、その呼び出しはその ServiceAccount の権限で実行されます。

namespace の 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 namespace に 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 つのオブジェクトの責務を 1 行ずつ押さえておきます。

  • ServiceAccount pod-readerdev namespace の新しい 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.30
      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

よくある罠 — 広すぎる 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 つでも外部に漏れれば、そのトークンでクラスタのすべての namespace のすべての Secret を読み、すべてのオブジェクトを削除できます。コンテナイメージ内のライブラリに脆弱性が 1 つあるだけでもその 1 点がクラスタ全体の事故に広がります。

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

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

view / edit / admin を RoleBinding で特定 namespace に限定すれば、人・チーム・サービスに適切な権限の束をすばやく付与でき、cluster-adminクラスタ運用者ほんの一握り にだけ ClusterRoleBinding で付与する形が標準です。

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

この 1 行を入れておくと、その Pod が万が一侵害されても K8s API に直接アクセスするトークンがありません。セキュリティガイドの常連の推奨事項です。

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

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

デフォルトはすべて通過 #

K8s ネットワークモデルのデフォルトは単純です — NetworkPolicy がなければすべての Pod が互いに通信できます。 同じ namespace でも別の namespace でも、Pod の IP さえわかれば自由にパケットを送れます。マルチテナントクラスタでこのデフォルト値がそのまま放置されると、ある namespace の Pod が別の namespace の 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 に入っているオブジェクトにすぎません。

default-deny → allow パターン #

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

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

podSelector: {} は空セレクタで「この namespace のすべての Pod」を指します。policyTypesIngressEgress が両方書かれていて ingress / egress ルールが 1 行もないので、この namespace のすべての 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同じ namespace の Pod ラベルマッチ
namespaceSelector別の namespace のすべての Pod (またはその namespace + podSelector の組み合わせ)
ipBlockCIDR 表現で IP 範囲 (外部 IP またはノード IP)

namespaceSelectorpodSelector を同じ from 項目に一緒に書くと「特定 namespace の特定ラベル Pod」になります。2 つを別々に書くと「その namespace のすべての Pod または同じ namespace のそのラベル 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 namespace の frontend Pod だけ」で、下のマニフェストは「env=prod namespace のすべての Pod または同じ namespace の 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 のようなサービスメッシュがその次元を扱います。そして NetworkPolicy はクラスタ内のトラフィックポリシーで、外部インバウンドは Ingress / LoadBalancer 次元 で、外部アウトバウンドは NAT ゲートウェイのセキュリティグループ で別途制御されます。1 つのクラスタのセキュリティの形は複数の層のポリシーが一緒に作っていきます。

ResourceQuota — namespace リソース合計上限 #

#4 でコンテナ単位リソースモデル — requestslimits — を扱いました。コンテナ 1 つの保証と上限を定めるオブジェクトでした。その上にもう 1 層加えるのが namespace 単位の合計上限 である ResourceQuota です。

運用シナリオは明確です。1 つのクラスタに dev / staging / prod、または複数のチームが一緒に住むとき、dev namespace がすべてのノードの 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 namespace に適用されると、次の合計が上限を超えられません。

  • その namespace 内のすべての 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 namespace にすでに 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 動作確認 #

namespace の 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 つの namespace がどこまで埋まっているかを確認するときに describe がもっとも速いツールです。

LimitRange と対 — コンテナ単位のデフォルト・上限 #

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

この部分の運用安全網が LimitRange です。LimitRange はコンテナ単位でデフォルト値と最大・最小を定めるオブジェクトです。

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 が適用された namespace に Pod マニフェストが入ってくると、次のように動作します。

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

ResourceQuota と LimitRange の責務を分けると 1 行ずつ整理されます。

オブジェクト単位何を定めるか
LimitRangeコンテナデフォルト値・最大・最小
ResourceQuotanamespace合計上限、オブジェクト個数上限

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

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

ResourceQuota は基本的に namespace のすべての 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 ]        その namespace がどれくらい作れるか
   │                     (cpu/memory 合計 + オブジェクト個数)
[ LimitRange ]           コンテナ単位のデフォルト・上限
                         (default / max / min)

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

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

シリーズ振り返り — K8s 中級 7 編で手に入ったもの #

最後の記事なので 7 編を一度押さえておきます。基礎シリーズがマニフェスト 1 枚を読み書きする段階まで連れていってくれたなら、中級シリーズはその上に運用の肌理を 1 層ずつ加えました。

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

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

次のトラック — K8s 上級 #

K8s 中級シリーズで意図的に持ち越した深いテーマたちが K8s 上級トラックの筋書きです。6 編で束ねる予定で、表で先にまとめておくと次のとおりです。

テーマ説明
CNI 深さ — Calico / Cilium / eBPFクラスタネットワークの実際のデータプレーン。iptables ベースと eBPF ベースの違い、NetworkPolicy の実行段の形。
RBAC / ServiceAccount 深さAggregated ClusterRole、impersonation、外部 IAM マッピング(EKS の IRSA、GKE の Workload Identity)、トークン lifecycle。
Admission Controller / OPA Gatekeeper / Kyvernoポリシーエンジン。マニフェストが etcd に入る前に検査・変形する段階。「limits なしのコンテナを拒否」「特定ラベルを強制」のようなポリシー。
CRD と Operator パターンKubernetes API 拡張。CustomResourceDefinition で新しいオブジェクト種を定義、controller-runtime ベースの Operator でそのオブジェクトを運用。
オブザーバビリティPrometheus + Grafana + Loki、kube-state-metrics、OpenTelemetry。クラスタ・ワークロードのメトリクス・ログ・トレースの標準スタック。
GitOps — ArgoCD / Fluxマニフェストの source of truth を git に置く運用モデル。drift detection、multi-cluster、sync policy。

この 6 つのテーマをすべて扱い終えると、クラスタをセットアップする人の視野で K8s を見られる段階まで一歩さらに入ります。マニフェストを上手く書く段階から、どんなポリシーエンジンを敷くか・どんなオブザーバビリティスタックを選ぶか・GitOps パイプラインをどう組むかを決定する段階に移るトラックです。

その次 — K8s 実戦 #

上級トラックの次は K8s 実戦 6 編を準備しています。上級までが K8s のオブジェクトモデルとポリシーの深さを扱うなら、実戦はその上に本物のサービスを 1 つ載せて運用する 1 サイクルです。

テーマ説明
EKS クラスタセットアップAWS EKS クラスタを最初から、IAM、VPC、ノードグループ、アドオン。
アプリデプロイ骨格Deployment + Service + Ingress + ConfigMap + Secret の 1 セット、Helm chart で整理。
DB 連動RDS / Aurora を Pod から安全に呼ぶ道、Secrets Manager 統合、コネクションプール。
CI/CD パイプラインGitHub Actions でコンテナビルド → ECR push → ArgoCD sync。
モニタリング・アラームCloudWatch + Prometheus、核心アラームルールセット、on-call 流れ。
運用チェックリストアップグレード、バックアップ・リカバリ、コスト点検、セキュリティ点検の定期運用サイクル。

この 2 つのトラック(上級 + 実戦)を経ると K8s を導入して運用する人 の視野がほぼすべて入ります。中級シリーズが終わる地点がその大きな絵の真ん中です — オブジェクトモデルは手に入り、その上に乗るポリシー・拡張・運用の深さが次のトラックです。

締めくくり #

K8s 中級シリーズ 7 編を締めくくりました。この記事ではマルチテナントクラスタの隔離を作る 3 つのポリシーオブジェクト — RBAC、NetworkPolicy、ResourceQuota — を 1 サイクルでまとめました。RBAC が K8s API の権限を、NetworkPolicy が Pod 間トラフィックを、ResourceQuota(と相棒の LimitRange)が namespace のリソース合計を制御し、3 つの次元が一緒に適用されているときにはじめて 1 つのクラスタの上に複数の環境・チームが安全に一緒に住めるというモデルを追いました。シリーズ全体で見ると、基礎 7 編がマニフェスト 1 枚を読み書きする段階、中級 7 編がそのマニフェストが運用クラスタでどう回るかの深さを 1 層ずつ加えた段階でした。次のトラックである K8s 上級 では CNI・RBAC の深さからポリシーエンジン、CRD/Operator、オブザーバビリティ、GitOps まで — クラスタをセットアップして運用する視野のテーマたちを順次扱います。その次の K8s 実戦 では EKS の上に本物のサービスを載せる 1 サイクルを最初から最後まで追います。

X