Kubernetes Admission Controlについての技術調査

2025.8.12

はじめに

工学院大学工学部電気電子工学科4年の清水悠利と申します。

大学では、C言語とOpenCVを用いた画像解析アルゴリズムの研究に従事しており、それとは別に趣味でWebアプリの開発も行っています。
今回Sreake事業部で期限付きインターンをさせていただいている中でKubernetesのAdmission Controlについての技術調査を行いました。

Kubernetesアーキテクチャ

以下の画像のようにざっくりとKubernetes全体(コントロールプレーン)のアーキテクチャはこのようなコンポーネントで構成されていて、今回調査した Admission Control は apiserver の一機能です。

引用元:Kubernetes: 構成コンポーネント一覧 – Qiita

Kubernetesの「コントロールプレーン(Control Plane)」とは、上の図でいうMasterコンポーネント部分の仕組みを指します。

コントロールプレーンの部品について簡単にまとめると以下のようになります。

kube-apiserver(APIサーバー)

Kubernetesクラスタの司令塔であり、Kubernetesのすべての操作の出入り口として機能します。

  • etcd には直接アクセスせず、kube-apiserverが通信の中継点として機能します。
  • 認証(Authentication)、認可(Authorization)、リクエストの検証(Admission Control)などを行い、 クラスタの状態の管理と安全性を保証します。

etcd

Kubernetesにおける唯一の永続的なキーバリューストア(データベース)です。

  • Podの定義、ステータス、ノード情報など、クラスタ全体の状態情報(desired state + current state)を保存します。

kube-scheduler(スケジューラー)

新たに作成された Pod を適切なノードに割り当てる役割を持ちます。

kube-controller-manager(コントローラーマネージャ)

複数のコントローラーを統括し、常に理想状態(desired state)になるよう自動で調整を行います。

  • 例:Deployment Controller は、Deployment リソースを監視し、 指定された replicas 数の Pod が常に稼働しているように調整します。

Admission Control(アドミッションコントロール)とは

Admission Controlのアーキテクチャ

Admission Control は、Kubernetes の API サーバー(kube-apiserver)において「リクエストを受け付けた後、実際にオブジェクトを作成・更新する前」に行う一連の処理全体を指します。

引用元:https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/
  • リクエストは、API Server によって受け付けられた後、認証(Authentication)・認可(Authorization)を経て、Admission Controller によるポリシーの検証などが行われ、最終的に etcd に保存されるという一連のフローで処理されます。
  • Admission Control フェーズはさらに「Mutating(変更)」→「Validating(検証)」の順番で実行されます。

Admission Control の 2 つのフェーズ

下のValidatingAdmissionControlとMutatingAdmission Controlは、リクエストを受け入れるかどうかを判断する仕組みがあります。そして、Admission Control は大きく以下の 2 つのフェーズ(パス)で動作します。

  1. ValidatingAdmissionControl(バリデータフェーズ)
    どのような検証を行うかを Admission Plugin で制御できます。不正(ポリシー違反など)があれば拒否(Forbidden)します。
    例:ResourceQuota の超過チェック、PodSecurity ポリシー違反のチェック、など。
  2. MutatingAdmissionControl(ミューテータフェーズ)
    Admission Pluginを用いてオブジェクトに対して「ラベル追加」「セキュリティ設定の補完」「イミュータブルフィールドの設定」など、必要に応じて自動的に修正・追加を行います。
    例:DefaultIngressClass によって Ingress リソースに既定のクラスを付与、MutatingWebhook によるサイドカーコンテナの自動注入など。

その実装は主にWebhook(外部の検証サービス)とAdmission Policy(Kubernetes内で定義可能なルール)に大別されます。今回はAdmission Policyにできるだけ焦点を当てていきます。

Admission ControlのコアであるAdmission Pluginについて

Admission Pluginとは?

Kubernetes の API サーバー(kube-apiserver)内で動作する「個別のモジュール(機能)」を指します。Admission Plugin には、デフォルト有効なものと無効なものがあります。 以下の表にKubernetesv1.33で無効なものの中に有用なものがあるかどうかを整理し、表にまとめました。

現在非推奨であるAlwaysAdmitとAlwaysDenyは省く

プラグイン名種別機能概要使いどころ・ポイント
AlwaysPullImagesMutating & Validating– 新規 Pod の imagePullPolicy を常に Always に設定同じタグのままイメージを差し替えたいときに、ノード上のキャッシュされた古いイメージではなく常に最新のイメージをpullしたいときに有用
DenyServiceExternalIPsValidating– Service リソースの新規 externalIPs フィールド設定を禁止Service リソースに外部ネットワーク向けの IP を設定したくない環境化において有用
EventRateLimitValidating (α)– Event オブジェクトの生成・更新をレート制限し、API サーバの過負荷を防止イベント洪水対策に有効。α機能のため --admission-control-config-file で設定に有用
ExtendedResourceTolerationMutating– 拡張リソース(GPU/FPGA 等)ノード向けに、対応する Taint の Toleration を自動付与拡張リソース専用ノードへの Pod スケジューリングを制御する際に有用
ImagePolicyWebhookValidating– 外部 Webhook 経由でコンテナイメージの検証を実行脆弱性スキャンやカスタム承認フローをAdmission Control による評価の時に組み込みたい場合に利用
LimitPodHardAntiAffinityTopologyValidatingrequiredDuringSchedulingRequiredDuringExecution で指定可能な Anti-Affinity トポロジーキーを制限誤った podAntiAffinity 設定 (例えば Pod がスケジューリング不能に陥るようなケース)を防ぐのに有用
NamespaceAutoProvisionMutating– 存在しない Namespace 参照を検出すると自動で作成開発・テスト環境で Namespace の事前作成を省略して利便性向上
NamespaceExistsValidating-存在しない Namespace を参照するリクエストを拒否名前間違の早期検出、削除中のNamespaceへの不要なリソース作成の防止などのシナリオで有用
NodeRestrictionValidating– kubelet が操作可能な Node/Pod オブジェクトを自身に紐づくものだけに限定kubelet 権限分離とノードセキュリティ強化に有用
OwnerReferencesPermissionEnforcementValidatingmetadata.ownerReferencesblockOwnerDeletion の変更に対して、オブジェクト削除権限/最終化子更新権限をチェックリソース作成・更新時に不正な ownerReferences を設定して既存オブジェクトの所有権を奪われるのを防ぐため、参照先オブジェクトへの操作権限がない場合にリクエストを拒否する際に有用
PodNodeSelectorValidating (α)– Namespace 単位で許可された NodeSelector の設定・検証ワークロードを特定ノードに制限したい場合に有効
PodTolerationRestrictionMutating & Validating (α)– Namespace/クラスタレベルで定義された許可リストに沿って Pod の tolerations を検証し、許可された場合にのみリソース作成を許可テナント分離に限定せず、Pod の Toleration 設定を制御したい場合に有用
PodTopologyLabelsMutating– PodBinding(PodとNodeを紐づけるBindingサブリソース)に対してノードのトポロジー情報(zoneやregion)をコピーする仕組み。Pod 作成時にノードのトポロジーラベル(例:zone や region)を自動注入して、マルチゾーン/リージョン環境でのサブリソースポロジー依存のネットワークポリシーを容易に実現するのに有用。Feature Gate 有効時のみ

※種別の(α)はα版であることを示す。

「デフォルトで無効なプラグインの評価」とは逆に、ここではデフォルトで有効になっているプラグインの中からいくつかピックアップし、再評価を行いました。基本的には有効のままで使われがちだが、用途によっては注意すべき点もあるため、再確認しています。

プラグイン名種別機能概要使いどころ・ポイント
NamespaceLifecycleValidating– 削除中の Namespace へのオブジェクト作成を禁止
– default、kube-system、 kube-public、kube-node-leaseのような重要 なNamespace の誤削除を防止
Namespace 周りの整合性を保証するため、常に有効化推奨
LimitRangerMutating & Validating– Namespace に設定された LimitRange でリソース制限を検証
– Podにリソース制限(CPUやメモリ)指定がない場合の Pod にデフォルト制限を自動付与
リソースの誤った登録を防ぎつつ、細かい制限ポリシーを強制したいときに必須
ServiceAccountMutating & Validating– Pod 作成時に ServiceAccount とそのトークン Secret を自動セット- 不要な Secret マウント制御Pod ごとの認証情報管理を自動化し、セキュリティ向上。ServiceAccount を使うなら必須
DefaultStorageClassMutating– PVC(PersistentVolumeClaim)で StorageClass 未指定時にデフォルトの StorageClass を付与ストレージクラス指定漏れを防ぎ、運用ポリシーを統一したい場合に有効
DefaultIngressClassMutating– Ingress リソースでクラス未指定時にデフォルトの IngressClass を付与複数 IngressController がある環境で「どれを使うか」が曖昧になるのを防ぐ
MutatingAdmissionWebhookMutating– 外部 Webhook(独自サービス)にリクエストを送り、戻り値 JSON でオブジェクトを書き換え社内ポリシーに基づく自動ラベリングや、Pod/コンテナにリソース要求・制限(requests/limits)をデフォルトで注入するなど柔軟な拡張が必要なとき
ValidatingAdmissionWebhookValidating– 外部 Webhook に検証依頼、失敗時はリクエストを拒否独自のポリシーエンジン(例:自社セキュリティ基準)でKubernetes リソースの操作を厳密にチェックしたい場合
ResourceQuotaValidating– Namespace ごとのリソース使用量クォータを超えた申請を拒否複数のユーザーやチームが同じクラスタを共有する環境では、一部のユーザーやアプリケーションによるリソースの独占を防ぐのに有効
PodSecurityValidating– Pod のセキュリティコンテキストが Pod Security Standard に準拠しているかチェック過去の PodSecurityPolicy の後継。最小権限や特権禁止を厳格化したいときに有効

Admission Pluginの適切なプラグイン選択・構成の例

公式Documentの「Security Checklist」にはAdmission Pluginの適切なプラグイン選択・構成の一例が記載されています。そして、「デフォルト有効のものは基本的機能・セキュリティに直結するため原則継続して有効化します。追加で有効化すべきプラグインはユースケース・セキュリティポリシーに応じて検討する」と記述され、以下のようにデフォルト無効だが推奨されるプラグイン例ユースケース次第で検討するプラグイン例も紹介されています。

  • デフォルト無効だが推奨されるプラグイン例
    • DenyServiceExternalIPs
    • NodeRestriction
  • ユースケース次第で検討するプラグイン例
    • AlwaysPullImages
    • ImagePolicyWebhook

Admission Pluginの有効化/無効化の方法

以下に Admission Plugin の有効化/無効化方法を記載します。

有効

kube-apiserver \
  --enable-admission-plugins=NamespaceLifecycle,LimitRanger,..

-enable-admission-plugins フラグにプラグイン名をカンマ区切りで指定します。

無効

kube-apiserver \
  --disable-admission-plugins=PodNodeSelector,AlwaysDeny,...

デフォルトで有効なプラグインから特定のものだけ外したい場合に使います。

デフォルト有効プラグイン(Kubernetes v1.33)

CertificateApproval, CertificateSigning,
CertificateSubjectRestriction, DefaultIngressClass,
DefaultStorageClass, DefaultTolerationSeconds,
LimitRanger, MutatingAdmissionWebhook,
NamespaceLifecycle, PersistentVolumeClaimResize,
PodSecurity, Priority, ResourceQuota,
RuntimeClass, ServiceAccount,
StorageObjectInUseProtection,
TaintNodesByCondition, ValidatingAdmissionPolicy,
ValidatingAdmissionWebhook

(詳細は kube-apiserver -h | grep enable-admission-plugins)

Admission Controlの検証

今回検証に利用したコードは以下のGitHub Repositoryにあります。

GitHub – matthewyuh246/k8s-admission-control

Admission Plugin の検証用のKindクラスタを作成

以下のようなkindクラスターYAMLファイルを用意し、Control Plane起動時に指定のAdmission Pluginを有効化/無効化します。

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
kubeadmConfigPatches:
  - |
    kind: ClusterConfiguration
    apiServer:
      authorizationMode: "RBAC"
      extraArgs:
        enable-admission-plugins: "NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultIngressClass,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,PodSecurity,DenyServiceExternalIPs,AlwaysPullImages"
        disable-admission-plugins: "AlwaysAdmit"

nodes:
  - role: control-plane
    image: kindest/node:v1.33.1
  - role: worker
    image: kindest/node:v1.33.1

Admission Pluginをいくつか検証する

Admission Plugin の中で気になったものをいくつかピックアップして検証してみます。

LimitRanger

LimitRangerの概要

Pod や Container の manifest が APIサーバーに送られた直後に動作します。

  • Resource Requests/Limits のデフォルト設定(オートマチックな Mutation)
    • Namespaceで LimitRange オブジェクトが定義されている場合、Pod/Container の manifest に CPU やメモリ(requests/limits)が明示されていなければ、LimitRangerが「適切なデフォルト値」を自動的に付与してくれます。
    • 例えば、あるLimitRangeで requests.cpu: 100mlimits.cpu: 200m が指定されていれば、Podに対してこれらの値を付与します。
  • 範囲外設定の拒否(Validation)
    • ユーザーが Pod や Container に対してLimitRangeで定義された最大値や最小値を超えたリソースを設定しようとした場合、エラーで拒否します。
    • 例えば、LimitRangeで max.memory: 500Mi が定義されているNamespaceで memory: 1Gi のPodを作成しようとすると、そのリクエストは「リソース割り当てが最大値を超えている」という理由で拒否されます。

検証1. LimitRangeで設定したデフォルト値が適応されているか試す

テスト用Namespaceを作成

$ kubectl create ns lr-test

LimitRangeのYAMLファイルを作成

apiVersion: v1
kind: LimitRange
metadata:
  name: pod-default-limits
  namespace: lr-test
spec:
  limits:
    # ── Pod 全体の min/max 制限
    - type: Pod
      min:
        cpu: "100m"
        memory: "128Mi"
      max:
        cpu: "500m"
        memory: "512Mi"

    # ── 各コンテナの default/defaultRequest, min/max 制限
    - type: Container
      min:
        cpu: "100m"
        memory: "128Mi"
      max:
        cpu: "500m"
        memory: "512Mi"
      defaultRequest:
        cpu: "150m"
        memory: "192Mi"
      default:
        cpu: "200m"
        memory: "256Mi"

LimitRangeのYAMLファイルを適用

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./LimitRange/lr-test-limitrange.yaml
limitrange/pod-default-limits created

resourcesを指定しないPodマニフェストを作成

apiVersion: v1
kind: Pod
metadata:
  name: nginx-no-resources
  namespace: lr-test
spec:
  containers:
  - name: nginx
    image: nginx:stable

PodのYAMLファイルを適用

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./LimitRange/pod-without-resources.yaml
pod/nginx-no-resources created

デフォルト値が自動的に補完されるか確認

kubectl -n lr-test get pod nginx-no-resources \
  -o jsonpath='{.spec.containers[0].resources}'
{"limits":{"cpu":"200m","memory":"256Mi"},"requests":{"cpu":"150m","memory":"192Mi"}}

検証2. Container型の下限テスト(同様に上限もテストできる)

resourcesのcpuが最低値100mを下回る50mを設定したpodのYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: nginx-below-min
  namespace: lr-test
spec:
  containers:
    - name: nginx
      image: nginx:stable
      resources:
        requests:
          cpu: "50m"

上のYAMLファイルを適用し、エラーが出ることを確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl create -f ./LimitRange/pod-below-min.yaml
Error from server (Forbidden): error when creating "./LimitRange/pod-below-min.yaml": pods "nginx-below-min" is forbidden: [minimum cpu usage per Pod is 100m, but request is 50m, minimum cpu usage per Container is 100m, but request is 50m]

検証3. Pod型の合計上限超過テスト

コンテナを2つ配置し、それぞれ 300m を指定 → 合計 600m で Pod 全体の max(500m)を超過するPodを作成

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-max-exceed
  namespace: lr-test
spec:
  containers:
    - name: nginx-a
      image: nginx:stable
      resources:
        requests:
          cpu: "300m"
        limits:
          cpu: "300m"
    - name: nginx-b
      image: nginx:stable
      resources:
        requests:
          cpu: "300m"
        limits:
          cpu: "300m"

適用したときエラーが出れば成功

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl create -f ./LimitRange/pod2-max-exceed.yaml
Error from server (Forbidden): error when creating "./LimitRange/pod2-max-exceed.yaml": pods "nginx-pod-max-exceed" is forbidden: maximum cpu usage per Pod is 500m, but limit is 600m

結論

  • LimitRange はコンテナ単位と Pod 単位、双方でリソースの最小・最大制限を強制しつつ、指定漏れにはあらかじめ設定したデフォルト値を適用してくれます。
  • これにより、リソースの誤った登録を防ぎつつ、ポリシーの統一(最低限の保護と上限の保証)が自動化されることがわかりました。

ServiceAccount

ServiceAccountの概要

Pod の作成リクエスト受信時動作します。

  • ServiceAccount の自動割り当て
    • Pod の YAML に serviceAccountName が指定されていない場合、Namespace の default ServiceAccount を自動的に付与します。
  • ServiceAccount トークンのマウント設定
    • Pod spec に automountServiceAccountToken: true(デフォルト)であれば、作成した ServiceAccount のシークレット(例えば /var/run/secrets/kubernetes.io/serviceaccount/token)を Pod 内へマウントします。
    • automountServiceAccountToken: false が明示的に指定されている場合、MountせずにPodを作成します。

検証1. 存在しないServiceAccountを参照するPodの作成テスト

存在しないServiceAccountのpodのyamlファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: test-invalid-sa
spec:
  serviceAccountName: no-such-sa
  containers:
    - name: nginx
      image: nginx:stable

適用時にエラーが出るか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ServiceAccount/pod-invalid-sa.yaml
Error from server (Forbidden): error when creating "./ServiceAccount/pod-invalid-sa.yaml": pods "test-invalid-sa" is forbidden: error looking up service account default/no-such-sa: serviceaccount "no-such-sa" not found

検証2. 正しいServiceAccountを作成してPodを起動

testというServiceAccountを作成

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl create sa test
serviceaccount/test created

ServiceAccountを指定したPodのYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: test-valid-sa
spec:
  serviceAccountName: test
  containers:
    - name: nginx
      image: nginx:stable

適用後、成功しているか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ServiceAccount/pod-va
lid-sa.yaml
pod/test-valid-sa created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get pod test-valid-sa -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP           NODE                 NOMINATED NODE   READINESS GATES
test-valid-sa   1/1     Running   0          21s   10.244.0.6   kind-control-plane   <none>           <none>

検証3. デフォルトServiceAccountの自動付与確認

ServiceAccountを指定しないPodのYAMLファイルの作成

apiVersion: v1
kind: Pod
metadata:
  name: test-default-sa
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sleep", "3600"]

適用後、PodのServiceAccountNameを確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get pod test-default-sa -o jsonpath='{.spec.serviceAccountName}'
default

シークレットがマウントされているかも確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl exec test-default-sa -- ls /var/run/secrets/kubernetes.io/serviceaccountaccount
ca.crt
namespace
token

検証4. 他ネームスペースのServiceAccountを参照したときの挙動

Kubernetesでは、Podファイル定義できるServiceAccountは同一ネームスペース内のものに限られます。
PodとServiceAccountのNamespaceを別々にします

Namespace: ns-a

apiVersion: v1
kind: Namespace
metadata:
  name: ns-a
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-a
  namespace: ns-a

Namespace: ns-b

apiVersion: v1
kind: Namespace
metadata:
  name: ns-b
---
apiVersion: v1
kind: Pod
metadata:
  name: test-cross-sa
  namespace: ns-b
spec:
  serviceAccountName: sa-a # 別のネームスペース(ns-a)のSAを指定
  containers:
    - name: nginx
      image: nginx:stable

上の二つのYAMLファイルを適用して確認(以下のようなエラーが出てれば成功)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl  apply -f ./ServiceAccount/sa-a.yaml
namespace/ns-a created
serviceaccount/sa-a created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ServiceAccount/pod-ref-sa-in-a.yaml
namespace/ns-b created
Error from server (Forbidden): error when creating "./ServiceAccount/pod-ref-sa-in-a.yaml": pods "test-cross-sa" is forbidden: error looking up service account ns-b/sa-a: serviceaccount "sa-a" not found

検証5. automountServiceAccountToken の検証

Podごとにトークンの自動マウントを禁止できます。以下のPodでは、SAトークンがマウントされないことを確認します。

automountServiceAccountTokenをfalseにしてトークンが自動マウントされないようなYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: no-token-pod
spec:
  serviceAccountName: default
  automountServiceAccountToken: false
  containers:
    - name: busybox
      image: busybox
      command: ["sleep", "3600"]

上のYAMLファイルを適用してマウントの機能が動いているか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ServiceAccount/pod-no
-automount.yaml
pod/no-token-pod created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl exec no-token-pod -- ls /var/run/secrets/kubernetes.io/serviceaccount
ls: /var/run/secrets/kubernetes.io/serviceaccount: No such file or directory
command terminated with exit code 1

検証6. RBACとの組み合わせテスト

ServiceAccount自体はただのアイデンティティなので、実際にはRBAC設定と組み合わせることで「Podから何ができるか」が決まります。

最小権限でRoleを作成(何もできないRole)→権限を付与する場合

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-limited
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-noperms
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bind-noperms
subjects:
  - kind: ServiceAccount
    name: sa-limited
roleRef:
  kind: Role
  name: role-noperms
  apiGroup: rbac.authorization.k8s.io

RoleBinding済みServiceAccountでPodを作成

apiVersion: v1
kind: Pod
metadata:
  name: pod-noperms
  namespace: default
spec:
  serviceAccountName: sa-limited
  containers:
    - name: curl
      image: curlimages/curl
      command:
        - sh
        - -c
        - |
          # Pod内からK8s APIへGET /api
          curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
            -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
            https://kubernetes.default.svc/api || echo "FAIL"

          # Podを継続的に実行状態に保つ
          echo "API test completed. Keeping pod running..."
          sleep 3600

上二つのYAMLファイルを適用して内部からAPIサーバへアクセスを試みます(403 Forbidden(または FORBIDDEN の表示)が出れば成功)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ServiceAccount/sa-rbac-noperms.yaml
serviceaccount/sa-limited unchanged
role.rbac.authorization.k8s.io/role-noperms configured
rolebinding.rbac.authorization.k8s.io/bind-noperms created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f /ServiceAccount/pod-wi
th-limited-sa.yaml
pod/pod-noperms created
matthewyuh246@matthewyuh246:~/k8s-admission-control$kubectl exec pod-noperms -- \\
  curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
    -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    https://kubernetes.default.svc/api/v1/namespaces/default/pods || echo "FORBIDDEN"
cat: /var/run/secrets/kubernetes.io/serviceaccount/token: No such file or directory
error: Internal error occurred: unable to upgrade connection: container not found ("curl")
FORBIDDEN

RoleのYAMLファイルでlist, getの権限を付与

apiVersion: v1
kind: ServiceAccount
metadata:
  name: sa-limited
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-noperms
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: bind-noperms
subjects:
  - kind: ServiceAccount
    name: sa-limited
roleRef:
  kind: Role
  name: role-noperms
  apiGroup: rbac.authorization.k8s.io

get、listが有効になっているか確認(Podの情報が返ってきたら成功、今回は長いので省略)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl exec pod-noperms -c curl -- sh -c "\
  TOKEN=\$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) && \
  curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
    -H \"Authorization: Bearer \$TOKEN\" \
    https://kubernetes.default.svc/api/v1/namespaces/default/pods || echo FORBIDDEN"

結論

  • 存在しない SA を指定すると Admission プラグインが 拒否 されます
  • 正しく SA を作成すれば Pod は 起動 します
  • SA 指定がない Pod には自動で default SA が 付与 されます
  • クロスネームスペース参照 → 拒否されます
  • automountServiceAccountToken 無効化 → トークン非マウント
  • TokenRequest API → 有効期限付きトークンのマウント
  • RBAC連携 → ServiceAccountの権限によるAPIアクセス制御

DefaultStorageClass

DefaultStorageClassの概要

PersistentVolumeClaim(PVC)の作成リクエスト受信時に動作します。

  • ストレージクラスが未指定のPVCへのデフォルト割り当て
    • PVC マニフェストに spec.storageClassName が含まれておらず、かつ StorageClass に storageclass.kubernetes.io/is-default-class: "true" のアノテーションが付与されている場合、DefaultStorageClassプラグインが自動的にその Default な StorageClass の名前を storageClassName に書き込みます(Mutation)。
    • これによりユーザーは明示的にStorageClass名を書かなくても、自動的にクラウドプロバイダやCSI プラグインが提供するデフォルトストレージを使えるようになります。

検証1. storageClassName を指定せず、DefaultStorageClassが適応されているか

StorageClassの用意
2 つの StorageClass のYAMLファイルを作成し、そのうち片方をデフォルトに設定

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-sc
  annotations:
    storageclass.kubernetes.io/is-default-class: "true" # ← これがデフォルトにするためのキー
provisioner: kubernetes.io/no-provisioner # テスト用に動的プロビジョニングは無効
volumeBindingMode: Immediate
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow-sc
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: Immediate

上のYAMLファイルを適用し、確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultStorageClass/s
torageclasses.yaml
storageclass.storage.k8s.io/fast-sc created
storageclass.storage.k8s.io/slow-sc created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get sc
NAME                 PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
fast-sc (default)    kubernetes.io/no-provisioner   Delete          Immediate              false                  9s
slow-sc              kubernetes.io/no-provisioner   Delete          Immediate              false                  9s
standard (default)   rancher.io/local-path          Delete          WaitForFirstConsumer   false                  59m

PVCのStorageClassを指定しません

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-no-sc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: "128Mi"
  # storageClassName: (未指定)

上のYAMLファイルを適用して、storageClassNameを確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultStorageClass/pvc-no-sc.yaml
persistentvolumeclaim/pvc-no-sc created
matthewyuh246@matthewyuh246:~/k8s-admission-control$  kubectl get pvc pvc-no-sc -o yaml | grep storageClassName
  storageClassName: fast-sc

検証2. storageClassName を指定し、DefaultStorageClassが適応されているか

StorageClassを明示的に指定したPVCのYAMLファイルの作成

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-with-slow-sc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Mi
  storageClassName: slow-sc

上のYAMLファイルを適用して、確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultStorageClass/pvc-with-slow-sc.yaml
persistentvolumeclaim/pvc-with-slow-sc created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get pvc pvc-with-slow-sc -o yaml | grep storageClassName
      {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"pvc-with-slow-sc","namespace":"default"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"1Mi"}},"storageClassName":"slow-sc"}}
  storageClassName: slow-sc

結論

  • storageClassName が指定されていないにもかかわらず、DefaultStorageClass プラグインにより fast-sc が付与されます
  • 明示指定した slow-sc のまま変わりません

DefaultIngressClass

DefaultIngressClassの概要

Ingress リソースの作成リクエスト受信時に動作します。

  • IngressClass が未指定の Ingress リソースへのデフォルト割り当て
    • Ingress マニフェストに spec.ingressClassName が含まれていない場合、クラスタ内の StorageClass と同様の概念で、ingressclass.kubernetes.io/is-default-class: "true" の Annotation を持つ IngressClass があれば、その名前を spec.ingressClassName として自動設定します。
    • これによりユーザーはどの Ingress コントローラーを使うか指定しなくても、あらかじめ「デフォルトIngressClass」に設定されたコントローラーが適用されます。

検証1. Ingress リソースを“ingressClassName なし”で作成し、自動で設定されているか

IngressClassリソースを作成
admission Plugin は、クラスタ内に「デフォルトに指定された IngressClass」が 1 つだけある場合に、Ingress の spec.ingressClassName を自動的に埋めます。まずは 2 つの IngressClass を用意し、そのうち一方にデフォルト指定を付与します。

nginx用IngressClass(デフォルト)

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/ingress-nginx

alb用IngressClass(非デフォルト)

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: alb
spec:
  controller: example.com/alb-controller

上のYAMLファイルを適用

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f DefaultIngressClass/ingressclass-nginx.yaml
ingressclass.networking.k8s.io/nginx created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f DefaultIngressClass/ingressclass-alb.yaml
ingressclass.networking.k8s.io/alb created

テスト用 Deployment & Service を作成

Ingress のバックエンドとして動作させる、シンプルな HTTP サーバーを立てます。

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: echo
  name: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  strategy: {}
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
        - image: hashicorp/http-echo:0.2.3
          name: echo
          args:
            - "-text=hello"
          ports:
            - containerPort: 5678

Service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: echo
  name: echo
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 5678
  selector:
    app: echo
  type: ClusterIP

上二つのYAMLファイルを適用

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultIngressClass/echo-deployment.yaml
deployment.apps/echo created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultIngressClass/echo-service.yaml
service/echo created

Ingress リソースを“ingressClassName なし”で作成

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  rules:
    - host: example.com
      http:
        paths:
          - backend:
              service:
                name: echo-svc
                port:
                  number: 80
            path: /
            pathType: Prefix

上のYAMLファイルを適用し、自動設定を確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultIngressClass/test-ingress.yaml
ingress.networking.k8s.io/test-ingress created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get ingress test-ingress -o yaml | grep ingressClassName
  ingressClassName: nginx

これは、ingressclass.kubernetes.io/is-default-class: "true" の付いた nginx が自動的に選ばれた結果

検証2. 明示指定時はデフォルトが適応されない

alb を明示的に指定

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress-alb
spec:
  ingressClassName: alb
  rules:
    - host: alb.example.com
      http:
        paths:
          - backend:
              service:
                name: echo-svc
                port:
                  number: 80
            path: /
            pathType: Prefix

上のYAMLファイルを適用して、ingressclassnameを確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./DefaultIngressClass/test-ingress-alb.yaml
ingress.networking.k8s.io/test-ingress-alb configured
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get ingress test-ingress-alb -o 
yaml | grep ingressClassName
      {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{},"name":"test-ingress-alb","namespace":"default"},"spec":{"ingressClassName":"alb","rules":[{"host":"alb.example.com","http":{"paths":[{"backend":{"service":{"name":"echo-svc","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
  ingressClassName: alb

結論

  • ingressclassのリソースでデフォルトingressClassNameを指定するとingress自体のリソースでclassNameを指定しなくても自動設定されます(明示的に指定する場合は指定した方が優先されます)

ResourceQuota

ResourceQuotaの概要

Namespace 内でのリソース作成リクエスト(Pod、Service、PVC など)を受けた直後に動作します。

  • ResourceQuotaリソースの定義
    • Namespace 毎に、CPU 合計量、メモリ合計量、オブジェクト数(Pod、PVC、Service、ConfigMapなど)の合計数上限を定義し、超過を防ぎます。
  • Quota 超過チェック
    • 新しい Pod が作成されると、APIサーバーは既存の Namespace 名前空間内で現在消費されているリソース量を合算し、上記の hard 値を超えないかを検証します。超えていれば「あらかじめ定義されたQuota制限を超過している」としてリクエストを拒否します。
    • たとえばすでに Pod を作っていて CPU リソースを 1.5 コア消費している状態で、新たに「0.6 コア」を要求してくる Pod を作ろうとすると、合計が 2.1 となり requests.cpu: "2" を超えるため拒否されます。

検証1. ResourceQuota の定義したリソース制限内に収まる正常系のPodを起動

Namespaceの作成

apiVersion: v1
kind: Namespace
metadata:
  name: quota-test

ResourceQuotaの定義を作成(Pod 数・CPU・メモリの上限を設定)

apiVersion: v1
kind: ResourceQuota
metadata:
  name: pod-quota
spec:
  hard:
    pods: "2"
    requests.cpu: "500m"
    requests.memory: "200Mi"
    limits.cpu: "1"
    limits.memory: "400Mi"

クォータ内のリソース作成(Pod)

apiVersion: v1
kind: Pod
metadata:
  name: small-pod
spec:
  containers:
    - name: nginx
      image: nginx:stable
      resources:
        requests:
          cpu: "100m"
          memory: "50Mi"
        limits:
          cpu: "200m"
          memory: "100Mi"

Namespace, ResourceQuota, PodのYAMLファイルを適用し、正常にpodが作成できるか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/namespace.yaml
namespace/quota-test created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/resourcequota.yaml
resourcequota/pod-quota created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/small-pod.yaml
pod/small-pod created

検証2. リソース数超過テスト(上限Pod数を超える)

pod2のYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: small-pod-2
spec:
  containers:
    - name: nginx
      image: nginx:stable
      resources:
        requests:
          cpu: "100m"
          memory: "50Mi"
        limits:
          cpu: "200m"
          memory: "100Mi"

pod3のYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: small-pod-3
spec:
  containers:
    - name: nginx
      image: nginx:stable
      resources:
        requests:
          cpu: "100m"
          memory: "50Mi"
        limits:
          cpu: "200m"
          memory: "100Mi"

podを3つ作成し、エラーが出るか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/small-pod-2.yaml
pod/small-pod-2 created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/small-pod-3.yaml
Error from server (Forbidden): error when creating "./ResourceQuota/small-pod-3.yaml": pods "small-pod-3" is forbidden: exceeded quota: pod-quota, requested: pods=1, used: pods=2, limited: pods=2

検証3. リソース超過テスト(CPU/メモリを超える)

リクエスト量を CPU 600m/メモリ 100Mi としてpodのYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: big-request-pod
spec:
  containers:
    - name: nginx
      image: nginx:stable
      resources:
        requests:
          cpu: "600m"
          memory: "100Mi"
        limits:
          cpu: "800m"
          memory: "150Mi"

上のYAMLファイルを適用し、エラーが出るか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./ResourceQuota/big-request-pod.yaml
Error from server (Forbidden): error when creating "./ResourceQuota/big-request-pod.yaml": pods "big-request-pod" is forbidden: exceeded quota: pod-quota, requested: limits.cpu=800m,pods=1,requests.cpu=600m, used: limits.cpu=400m,pods=2,requests.cpu=200m, limited: limits.cpu=1,pods=2,requests.cpu=500m

結論

  • ResourceQuota は Namespace ごとにリソースの上限(Pod 数やCPU/メモリのリクエスト・リミット)を設定し、それを超えるリソース作成を禁止します
  • Pod 数が上限(hard.pods=2)を超えると、3つ目の Pod は拒否されます
  • 合計 CPU リクエスト(hard.requests.cpu=500m)や合計メモリリクエスト(hard.requests.memory=200Mi)を上回る Pod 作成も同様に禁止されます
  • 以上から、ResourceQuota によって Namespace 内のリソース利用を確実に制御できることが確認できました

PodSecurity

PodSecurityの概要

PodSecurity Admission Plugin は、以下のようにNamespace に付与されたラベルをもとに、そのネームスペースに作成される Pod のセキュリティ設定が「許可・拒否」のどちらに該当するかを判定します。

  • ラベルキー:pod-security.kubernetes.io/enforce
  • ラベル値(例):
    • privileged : 事実上セキュリティ制限なし
    • baseline : 一般的なコンテナワークロードとして推奨される最低限のセキュリティ設定を満たす
    • restricted : 最も厳しい制限。例えば root ユーザーでの実行や特権コンテナは NG など

加えて、以下のラベルを併せて設定できます(省略可能):

  • pod-security.kubernetes.io/enforce-version:ポリシーのバージョン(例:latestv1.25.0 など)
  • pod-security.kubernetes.io/audit:監査(audit)用の閾値を設定
  • pod-security.kubernetes.io/warn:警告(warn)用の閾値を設定

ここでは主に enforce ラベル を使って、作成した Pod がポリシーに適合しない場合に「Admission が拒否される」ことを確認します。

検証1. Podのprivilegedポリシー(ほぼ制限なし)の動作確認

NamespaceとLabelの準備

まず、3種類のネームスペースを作成し、それぞれに異なる enforce ラベルを付与

  • ns-privilegedprivileged ポリシー(制限なし)
  • ns-baselinebaseline ポリシー(最低限の制限)
  • ns-restrictedrestricted ポリシー(最も厳格)
---
apiVersion: v1
kind: Namespace
metadata:
  name: ns-privileged
  labels:
    pod-security.kubernetes.io/enforce: "privileged"
    pod-security.kubernetes.io/enforce-version: "latest"
---
apiVersion: v1
kind: Namespace
metadata:
  name: ns-baseline
  labels:
    pod-security.kubernetes.io/enforce: "baseline"
    pod-security.kubernetes.io/enforce-version: "latest"
---
apiVersion: v1
kind: Namespace
metadata:
  name: ns-restricted
  labels:
    pod-security.kubernetes.io/enforce: "restricted"
    pod-security.kubernetes.io/enforce-version: "latest"

上のYAMLファイルを適用した後、確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/namespaces-podsecurity.yaml
namespace/ns-privileged created
namespace/ns-baseline created
namespace/ns-restricted created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get namespaces --show-labels | grep ns-
ns-baseline          Active   35s    kubernetes.io/metadata.name=ns-baseline,pod-security.kubernetes.io/enforce-version=latest,pod-security.kubernetes.io/enforce=baseline
ns-privileged        Active   35s    kubernetes.io/metadata.name=ns-privileged,pod-security.kubernetes.io/enforce-version=latest,pod-security.kubernetes.io/enforce=privileged
ns-restricted        Active   35s    kubernetes.io/metadata.name=ns-restricted,pod-security.kubernetes.io/enforce-version=latest,pod-security.kubernetes.io/enforce=restricted

privilegedポリシーに違反しない特権コンテナの作成

apiVersion: v1
kind: Pod
metadata:
  name: pod-privileged-priv
  namespace: ns-privileged
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "sleep 3600"]
      securityContext:
        privileged: true

上のYAMLファイルを正常に適用できるか確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/pod-privileged.yaml
pod/pod-privileged-priv created

検証2. PodのBaseline ポリシーの動作確認(正常系)

baseline 標準では「一般的なコンテナワークロードとして推奨される最低限の制限」を要求し、

以下のような項目がチェックされます。

  • root 権限のまま実行しない(runAsNonRoot: true を要求)
  • ホストのプロセス名前空間やネットワーク名前空間を使わない
  • 特権モードでない (privileged: false)
  • ホストパスのマウントを抑制する など

Baselineポリシーを満たす設定でpodのYAMLファイルを作成(runAsNonRoot: true を指定し、privileged は false)

apiVersion: v1
kind: Pod
metadata:
  name: pod-baseline-pass
  namespace: ns-baseline
spec:
  securityContext:
    runAsNonRoot: true
  containers:
  - name: nginx
    image: nginx:stable
    securityContext:
      runAsNonRoot: true
      allowPrivilegeEscalation: false

上のYAMLファイルを適用し、確認

(以下のようなエラーが出ていれば、正しく機能しています)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/pod-baseline-pass.yaml
pod/pod-baseline-pass created

検証3. PodのBaseline ポリシーの動作確認(異常系)

Baselineポリシーに違反する設定でpodのYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: pod-baseline-violate
  namespace: ns-baseline
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "sleep 3600"]
      securityContext:
        privileged: true

上のYAMLファイルを適用し、確認

(以下のようなエラーが出ていれば、正しく機能しています)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/pod-baseline-violate.yaml
Error from server (Forbidden): error when creating "./PodSecurity/pod-baseline-violate.yaml": pods "pod-baseline-violate" is forbidden: violates PodSecurity "baseline:latest": privileged (container "busybox" must not set securityContext.privileged=true)

検証4. Podの Restricted ポリシーの動作確認(正常系)

restricted 標準はもっとも厳しく、「最小限の権限しか持たない」ことを要求し、以下のような項目がチェックされます。

  • runAsNonRoot: true は必須
  • readOnlyRootFilesystem: true などファイルシステムにも制限あり
  • ホストネットワークや特権モードは一切不可
  • 全てのボリュームが制限付き など

Restrictedポリシーを満たす設定でpodのYAMLファイルを作成(コンテナは root ではなく非 root で実行, ルートファイルシステムを書き込み禁止)

apiVersion: v1
kind: Pod
metadata:
  name: pod-restricted-pass
  namespace: ns-restricted
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    # Pod 全体にも seccompProfile を設定しておくとよい(コンテナ個別でも可)
    seccompProfile:
      type: RuntimeDefault

  containers:
    - name: nginx
      image: nginx:stable
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        readOnlyRootFilesystem: true
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
        seccompProfile:
          type: RuntimeDefault

上のYAMLファイルを適用し、確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/pod-restricted-pass.yaml
pod/pod-restricted-pass created

検証5. Podの Restricted ポリシーの動作確認(異常系)

restricted で禁止されている「root ユーザーでの実行」「書き込み可能なルートファイルシステム」をあえて付与したYAMLファイルを作成

apiVersion: v1
kind: Pod
metadata:
  name: pod-restricted-violate
  namespace: ns-restricted
spec:
  securityContext:
    runAsNonRoot: false   # root 実行を許可
  containers:
  - name: busybox
    image: busybox
    command: ["sh", "-c", "sleep 3600"]
    securityContext:
      runAsNonRoot: false
      readOnlyRootFilesystem: false

上のYAMLファイルを適用し、確認

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ./PodSecurity/pod-restricted-violate.yaml
Error from server (Forbidden): error when creating "./PodSecurity/pod-restricted-violate.yaml": pods "pod-restricted-violate" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "busybox" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "busybox" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod and container "busybox" must not set securityContext.runAsNonRoot=false), seccompProfile (pod or container "busybox" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

結論

  • 各ネームスペースごとに、ポリシーを満たす Pod(pass)と満たさない Pod(violate)を用意
  • kubectl apply -f <YAML> して、Admission Plugin が正しく動作しているか確認
  • PodSecurity Admission Plugin が有効になっている Kind クラスター内で、実際に「許可される Pod」「拒否される Pod」を検証
  • 必要に応じて、さらに細かいセキュリティContext(たとえば PSP 相当のアノテーションや LinuxCapabilities、seccompProfile など)を追加して「より厳密な検証」を行うことが必要

DenyServiceExternalIPs

DenyServiceExternalIPsの概要

  • DenyServiceExternalIPs は、Kubernetes の Service リソースの spec.externalIPs フィールドに対する「新規設定(net-new usage)」を拒否する検証用 Admission Plugin です。具体的には、ユーザーが新たに Service に externalIPs を設定しようとするリクエストを API サーバーが受け付けないようにします。
  • externalIPs を Service に設定すると、クラスタ外の IP から直接トラフィックを受けられる可能性が生まれます。これによりセキュリティ的リスク(予期せぬ外部公開やトラフィックの横取りなど)が高まるため、大半のユースケースでは不要または管理が難しい機能とみなされます。なので、DenyServiceExternalIPsを有効化をすることで既存の Service に設定済みの externalIPs を残しつつ、新規追加のみを阻止する挙動となるため、「意図しない設定変更」を防止しやすい設計です。
  • 利用シナリオとしては社内オンプレ/マネージドクラウド問わず、クラスタ内部サービスが不意に外部に露出するリスクを排除したい場合や外部 IP の管理を厳格化し、必要な場合は別の仕組み(LoadBalancer 型 Service や Ingress+ロードバランサー設定、MetalLB などの外部公開経路)で統制したいとき。

検証. DenyServiceExternalIPs を有効化したときの拒否動作の確認

テスト用 ServiceのYAMLファイルを作成

apiVersion: v1
kind: Service
metadata:
  name: test-deny-externalip
  namespace: default
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: dummy
  externalIPs:
    - 203.0.113.123

上のYAMLファイルを適用(以下のようなエラーメッセージが出ている場合は正常)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f DenyServiceExternalIPs/service.yaml
Error from server (Forbidden): error when creating "DenyServiceExternalIPs/service.yaml": services "test-deny-externalip" is forbidden: Use of external IPs is denied by admission control

結論

  • API サーバー起動後は Service の externalIPs 新規設定リクエストが拒否されます

AlwaysPullImages

AlwaysPullImagesの概要

  • AlwaysPullImagesプラグインは、新規に作成されるPodに対して必ずimagePullPolicy: Alwaysを強制するミューテーティングAdmission Controllerです。これにより、ノード上にキャッシュされている既存イメージを利用せず、常にレジストリから最新のイメージをプルするように強制します。
  • これにより、特にマルチテナント環境において、あるユーザーがプライベートレジストリからイメージをプルしたあとに、そのイメージがノードに残っている状況で、別ユーザーが同じイメージ名でPodを立ててしまい、認可されていないキャッシュ利用が起こるリスクを防ぎます。ほかにも同じタグのままイメージを差し替えたいときに、ノード上のキャッシュされた古いイメージではなく常に最新のイメージをpullすることを強制できます。
  • 利用シナリオとしては、プライベートレジストリの認証情報を必要とするイメージを複数ユーザーが共有するようなクラスタやセキュリティポリシーとして、「必ずレジストリから最新を取得して検証済みのイメージを使う」運用を徹底したい場合。

検証. AlwaysPullImagesのプラグインを有効化したときの動作検証

テスト用PodのYAMLファイルを作成(imagePullPolicyを指定)

apiVersion: v1
kind: Pod
metadata:
  name: test-alwayspull-before
  namespace: default
spec:
  containers:
    - name: nginx
      image: nginx:1.23.1
      imagePullPolicy: IfNotPresent

適用後、以下コマンドでミューテーション結果を確認

(出力がAlwaysとなっていれば、ミューテーションが正しく機能しています)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ImagePullPolicy/test-pod.
yaml
pod/test-alwayspull-before created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get pod test-alwayspull-before -o jsonpath="{.spec.containers[0].imagePullPolicy}"
Always

上のYAMLファイルのPod を作成した直後に describe

(Events セクションを参照するとPodのYAMLでimagePullPolicyをIfNotPresentにしていてもPullされていることが分かります)

matthewyuh246@matthewyuh246:~/k8s-admission-control/ImagePullPolicy$ kubectl describe -n test  pod test-alwayspull-before
Name:             test-alwayspull-before
Namespace:        test
Priority:         0
Service Account:  default
Node:             admission-test-worker/172.18.0.3
Start Time:       Fri, 27 Jun 2025 10:46:35 +0900
Labels:           <none>
Annotations:      <none>
Status:           Running
IP:               10.244.1.6
IPs:
  IP:  10.244.1.6
Containers:
  nginx:
    Container ID:   containerd://8276497d67aea63702d74b840518334317a8395a7d42c93487f7d3f8135fec9b
    Image:          nginx:1.23.1
    Image ID:       docker.io/library/nginx@sha256:2f770d2fe27bc85f68fd7fe6a63900ef7076bc703022fe81b980377fe3d27b70
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Fri, 27 Jun 2025 10:46:37 +0900
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-fnfkl (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True 
  Initialized                 True 
  Ready                       True 
  ContainersReady             True 
  PodScheduled                True 
Volumes:
  kube-api-access-fnfkl:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  3s    default-scheduler  Successfully assigned test/test-alwayspull-before to admission-test-worker
  Normal  Pulling    4s    kubelet            Pulling image "nginx:1.23.1"
  Normal  Pulled     2s    kubelet            Successfully pulled image "nginx:1.23.1" in 1.834s (1.834s including waiting). Image size: 56784238 bytes.
  Normal  Created    2s    kubelet            Created container: nginx
  Normal  Started    2s    kubelet            Started container nginx

テスト用PodのYAMLファイルを作成(imagePullPolicyを未指定)

apiVersion: v1
kind: Pod
metadata:
  name: test-alwayspull-no-spec
  namespace: default
spec:
  containers:
    - name: busybox
      image: busybox:1.35
      # imagePullPolicy: 未指定
      command: ["sleep", "3600"]

適用後、以下コマンドでミューテーション結果を確認

(出力がAlwaysとなっていれば、ミューテーションが正しく機能しています)

matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl apply -f ImagePullPolicy/test-pod2
.yaml
pod/test-alwayspull-no-spec created
matthewyuh246@matthewyuh246:~/k8s-admission-control$ kubectl get -n test pod test-alwayspull-no-spec -o jsonpath="{.spec.containers[0].imagePullPolicy}"
Always

結論

  • AlwaysPullImagesは新規に作成されるPodに対して必ずimagePullPolicy: Alwaysを適用して「Podが常にレジストリからイメージを取得する」ことを強制します。

まとめ

Admission ControlはKubernetes の API サーバー(kube-apiserver)において「リクエストを受け付けた後、実際にオブジェクトを作成・更新する前」に行う一連の処理全体を指し、ValidatingAdmission(バリデータフェーズ)とMutatingAdmission(ミューテータフェーズ) 2 つのフェーズ(パス)で動作します。そして、この2つのフェーズを行うモジュールであるAdmission PluginはAdmission Controlのコアです。

そして、有効化するAdmission Control プラグインは、最小権限の原則に沿ってPodSecurity AdmissionやLimitRanger/ResourceQuotaなど基本機能をNamespace単位で早期適用し、Mutating/Validating WebhookやCELベースのValidatingAdmissionPolicyはスコープ・タイムアウト・failurePolicyを最小化して設計・高可用化したうえで、ステージング環境で段階的に検証・監視・ロギング・ロールバック手順を整備してから有効化すべきです。

今回はAdmission Pluginをいくつかに絞って様々な検証を行いました。これらのAdmission Pluginをうまく組み合わせることでより堅牢なKubernetes環境を構築できます。

参考文献

  1. Kubernetes のアーキテクチャとは?特徴と基本コンポーネントからデータ保護の方法まで詳しく解説
    https://www.rworks.jp/cloud/kubernetes-op-support/kubernetes-column/kubernetes-entry/29132/
  2. Kubernetes公式Document
    https://kubernetes.io/ja/docs/concepts/overview/components/
    https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
    https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/
    https://kubernetes.io/docs/concepts/security/security-checklist/
  3. 5分でKubernetesのアドミッションコントローラーについて学ぶ
    https://sysdig.jp/blog/kubernetes-admission-controllers/
  4. Kubernetes: 構成コンポーネント一覧
    https://qiita.com/tkusumi/items/c2a92cd52bfdb9edd613

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ