はじめに
工学院大学工学部電気電子工学科4年の清水悠利と申します。
大学では、C言語とOpenCVを用いた画像解析アルゴリズムの研究に従事しており、それとは別に趣味でWebアプリの開発も行っています。
今回Sreake事業部で期限付きインターンをさせていただいている中でKubernetesのAdmission Controlについての技術調査を行いました。
Kubernetesアーキテクチャ
以下の画像のようにざっくりとKubernetes全体(コントロールプレーン)のアーキテクチャはこのようなコンポーネントで構成されていて、今回調査した Admission Control は apiserver の一機能です。

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)において「リクエストを受け付けた後、実際にオブジェクトを作成・更新する前」に行う一連の処理全体を指します。

- リクエストは、API Server によって受け付けられた後、認証(Authentication)・認可(Authorization)を経て、Admission Controller によるポリシーの検証などが行われ、最終的に etcd に保存されるという一連のフローで処理されます。
- Admission Control フェーズはさらに「Mutating(変更)」→「Validating(検証)」の順番で実行されます。
Admission Control の 2 つのフェーズ
下のValidatingAdmissionControlとMutatingAdmission Controlは、リクエストを受け入れるかどうかを判断する仕組みがあります。そして、Admission Control は大きく以下の 2 つのフェーズ(パス)で動作します。
- ValidatingAdmissionControl(バリデータフェーズ)
どのような検証を行うかを Admission Plugin で制御できます。不正(ポリシー違反など)があれば拒否(Forbidden
)します。
例:ResourceQuota
の超過チェック、PodSecurity
ポリシー違反のチェック、など。 - 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は省く
プラグイン名 | 種別 | 機能概要 | 使いどころ・ポイント |
---|---|---|---|
AlwaysPullImages | Mutating & Validating | – 新規 Pod の imagePullPolicy を常に Always に設定 | 同じタグのままイメージを差し替えたいときに、ノード上のキャッシュされた古いイメージではなく常に最新のイメージをpullしたいときに有用 |
DenyServiceExternalIPs | Validating | – Service リソースの新規 externalIPs フィールド設定を禁止 | Service リソースに外部ネットワーク向けの IP を設定したくない環境化において有用 |
EventRateLimit | Validating (α) | – Event オブジェクトの生成・更新をレート制限し、API サーバの過負荷を防止 | イベント洪水対策に有効。α機能のため --admission-control-config-file で設定に有用 |
ExtendedResourceToleration | Mutating | – 拡張リソース(GPU/FPGA 等)ノード向けに、対応する Taint の Toleration を自動付与 | 拡張リソース専用ノードへの Pod スケジューリングを制御する際に有用 |
ImagePolicyWebhook | Validating | – 外部 Webhook 経由でコンテナイメージの検証を実行 | 脆弱性スキャンやカスタム承認フローをAdmission Control による評価の時に組み込みたい場合に利用 |
LimitPodHardAntiAffinityTopology | Validating | – requiredDuringSchedulingRequiredDuringExecution で指定可能な Anti-Affinity トポロジーキーを制限 | 誤った podAntiAffinity 設定 (例えば Pod がスケジューリング不能に陥るようなケース)を防ぐのに有用 |
NamespaceAutoProvision | Mutating | – 存在しない Namespace 参照を検出すると自動で作成 | 開発・テスト環境で Namespace の事前作成を省略して利便性向上 |
NamespaceExists | Validating | -存在しない Namespace を参照するリクエストを拒否 | 名前間違の早期検出、削除中のNamespaceへの不要なリソース作成の防止などのシナリオで有用 |
NodeRestriction | Validating | – kubelet が操作可能な Node/Pod オブジェクトを自身に紐づくものだけに限定 | kubelet 権限分離とノードセキュリティ強化に有用 |
OwnerReferencesPermissionEnforcement | Validating | – metadata.ownerReferences や blockOwnerDeletion の変更に対して、オブジェクト削除権限/最終化子更新権限をチェック | リソース作成・更新時に不正な ownerReferences を設定して既存オブジェクトの所有権を奪われるのを防ぐため、参照先オブジェクトへの操作権限がない場合にリクエストを拒否する際に有用 |
PodNodeSelector | Validating (α) | – Namespace 単位で許可された NodeSelector の設定・検証 | ワークロードを特定ノードに制限したい場合に有効 |
PodTolerationRestriction | Mutating & Validating (α) | – Namespace/クラスタレベルで定義された許可リストに沿って Pod の tolerations を検証し、許可された場合にのみリソース作成を許可 | テナント分離に限定せず、Pod の Toleration 設定を制御したい場合に有用 |
PodTopologyLabels | Mutating | – PodBinding(PodとNodeを紐づけるBindingサブリソース)に対してノードのトポロジー情報(zoneやregion)をコピーする仕組み。 | Pod 作成時にノードのトポロジーラベル(例:zone や region)を自動注入して、マルチゾーン/リージョン環境でのサブリソースポロジー依存のネットワークポリシーを容易に実現するのに有用。Feature Gate 有効時のみ |
※種別の(α)はα版であることを示す。
「デフォルトで無効なプラグインの評価」とは逆に、ここではデフォルトで有効になっているプラグインの中からいくつかピックアップし、再評価を行いました。基本的には有効のままで使われがちだが、用途によっては注意すべき点もあるため、再確認しています。
プラグイン名 | 種別 | 機能概要 | 使いどころ・ポイント |
---|---|---|---|
NamespaceLifecycle | Validating | – 削除中の Namespace へのオブジェクト作成を禁止 – default、kube-system、 kube-public、kube-node-leaseのような重要 なNamespace の誤削除を防止 | Namespace 周りの整合性を保証するため、常に有効化推奨 |
LimitRanger | Mutating & Validating | – Namespace に設定された LimitRange でリソース制限を検証 – Podにリソース制限(CPUやメモリ)指定がない場合の Pod にデフォルト制限を自動付与 | リソースの誤った登録を防ぎつつ、細かい制限ポリシーを強制したいときに必須 |
ServiceAccount | Mutating & Validating | – Pod 作成時に ServiceAccount とそのトークン Secret を自動セット- 不要な Secret マウント制御 | Pod ごとの認証情報管理を自動化し、セキュリティ向上。ServiceAccount を使うなら必須 |
DefaultStorageClass | Mutating | – PVC(PersistentVolumeClaim)で StorageClass 未指定時にデフォルトの StorageClass を付与 | ストレージクラス指定漏れを防ぎ、運用ポリシーを統一したい場合に有効 |
DefaultIngressClass | Mutating | – Ingress リソースでクラス未指定時にデフォルトの IngressClass を付与 | 複数 IngressController がある環境で「どれを使うか」が曖昧になるのを防ぐ |
MutatingAdmissionWebhook | Mutating | – 外部 Webhook(独自サービス)にリクエストを送り、戻り値 JSON でオブジェクトを書き換え | 社内ポリシーに基づく自動ラベリングや、Pod/コンテナにリソース要求・制限(requests/limits)をデフォルトで注入するなど柔軟な拡張が必要なとき |
ValidatingAdmissionWebhook | Validating | – 外部 Webhook に検証依頼、失敗時はリクエストを拒否 | 独自のポリシーエンジン(例:自社セキュリティ基準)でKubernetes リソースの操作を厳密にチェックしたい場合 |
ResourceQuota | Validating | – Namespace ごとのリソース使用量クォータを超えた申請を拒否 | 複数のユーザーやチームが同じクラスタを共有する環境では、一部のユーザーやアプリケーションによるリソースの独占を防ぐのに有効 |
PodSecurity | Validating | – 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: 100m
/limits.cpu: 200m
が指定されていれば、Podに対してこれらの値を付与します。
- Namespaceで
- 範囲外設定の拒否(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 を自動的に付与します。
- Pod の YAML に
- ServiceAccount トークンのマウント設定
- Pod spec に
automountServiceAccountToken: true
(デフォルト)であれば、作成した ServiceAccount のシークレット(例えば/var/run/secrets/kubernetes.io/serviceaccount/token
)を Pod 内へマウントします。 automountServiceAccountToken: false
が明示的に指定されている場合、MountせずにPodを作成します。
- Pod spec に
検証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 プラグインが提供するデフォルトストレージを使えるようになります。
- PVC マニフェストに
検証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」に設定されたコントローラーが適用されます。
- Ingress マニフェストに
検証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"
を超えるため拒否されます。
- 新しい Pod が作成されると、APIサーバーは既存の Namespace 名前空間内で現在消費されているリソース量を合算し、上記の
検証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
:ポリシーのバージョン(例:latest
やv1.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-privileged
:privileged
ポリシー(制限なし)ns-baseline
:baseline
ポリシー(最低限の制限)ns-restricted
:restricted
ポリシー(最も厳格)
---
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環境を構築できます。
参考文献
- Kubernetes のアーキテクチャとは?特徴と基本コンポーネントからデータ保護の方法まで詳しく解説
https://www.rworks.jp/cloud/kubernetes-op-support/kubernetes-column/kubernetes-entry/29132/ - 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/ - 5分でKubernetesのアドミッションコントローラーについて学ぶ
https://sysdig.jp/blog/kubernetes-admission-controllers/ - Kubernetes: 構成コンポーネント一覧
https://qiita.com/tkusumi/items/c2a92cd52bfdb9edd613