Learning Platform
Глоссарий Troubleshooting
Урок 19.03 · 25 мин
Продвинутый
admission controlmutating webhookvalidating webhookValidatingAdmissionPolicyKyvernoOPA GatekeeperPodSecurity

Admission controllers: mutating и validating

Между моментом, когда apiserver принял запрос от kubectl, и моментом записи в etcd, есть фаза, в которой объект можно изменить (mutating) или отвергнуть (validating). Это admission control — третий и последний барьер после authentication и authorization. Через admission в Kubernetes реализованы десятки критических вещей: автомонтирование SA-токенов, defaults LimitRange, enforcement Pod Security Standards, sidecar injection Istio/Linkerd, policy engines (Kyverno, OPA). Понимать admission — обязательно для любой production-системы.


Анатомия HTTP-запроса: что внутри request и response

Где admission стоит в request lifecycle

Жизненный цикл API request на apiserver:

  1. Authentication — кто это? (mTLS cert, bearer token, OIDC token, ServiceAccount token). Если неизвестный → 401 Unauthorized.
  2. Authorization — может ли он делать это действие? (RBAC, ABAC, Node authorizer). Если нет → 403 Forbidden.
  3. Mutating admission — можно ли модифицировать запрос? (built-in MutatingAdmissionWebhook + новая MutatingAdmissionPolicy на CEL — alpha с v1.32). Этот этап МЕНЯЕТ объект.
  4. Object schema validation — соответствует ли OpenAPI schema (для CRDs) / built-in типу.
  5. Validating admission — финальная проверка после всех мутаций (built-in ValidatingAdmissionWebhook + ValidatingAdmissionPolicy). Может отвергнуть, но не менять.
  6. Запись в etcd.
API request: путь от kubectl до etcd через admission
kubectl create -f pod.yamlHTTP POST на apiserver. Headers содержат токен/cert клиента, тело — Pod YAML.
Authenticationapiserver проверяет identity клиента: mTLS, bearer token, OIDC. Список authenticators настраивается через --authentication-* флаги. Если нет identity — 401.
AuthorizationRBAC проверяет: user system:serviceaccount:default:my-sa может ли verb=create на resource=pods в namespace=default? Если нет — 403.
Mutating AdmissionЗапускаются ВСЕ MutatingAdmissionWebhooks по очереди, плюс новая MutatingAdmissionPolicy на CEL (alpha с v1.32) — отдельный объект, не часть ValidatingAdmissionPolicy. Каждый может modify объект через JSON patch. Порядок не определён — webhook может затереть изменения другого. Это создаёт сложность в production.
Schema validationapiserver валидирует tело по type Pod (или по CRD schema для custom). Required fields, types, enums, pattern. Если не пройдёт — 400 Invalid.
Validating AdmissionВсе ValidatingAdmissionWebhooks + ValidatingAdmissionPolicy. Любой может вернуть deny — request rejected с указанным reason. Mutating до этого момента уже всё применил, validating только говорит yes/no.
etcd writeПосле всех проверок и мутаций apiserver сериализует объект в protobuf и записывает в etcd под ключом /registry/pods/default/my-pod. С этого момента объект существует, kubelet может его увидеть.
Watchers notifiedВсе клиенты (kubelet, scheduler, controllers, operators), подписанные на Pod через Watch API, получают ADDED event. Reconciliation начинается.
201 Createdapiserver возвращает 201 Created с финальным телом объекта (после всех мутаций admission). kubectl показывает 'pod/my-pod created'.
WARNING

Mutating всегда запускается ПЕРЕД validating, и не наоборот. Это критический инвариант: validating видит финальный объект после всех мутаций, поэтому может проверить consistency после изменений. Если бы validating шёл раньше, mutating мог бы испортить уже одобренный объект.


Built-in admission controllers

apiserver запускается с флагом --enable-admission-plugins--disable-admission-plugins). Список встроенных — около 30 штук, многие включены по умолчанию. Это in-process controllers, написанные в коде apiserver.

ControllerMut / ValЧто делает
NamespaceLifecycleVBlock создание объектов в not-existing или terminating namespaces. Прозрачно защищает от orphan state.
LimitRangerM+VПрименяет defaults из LimitRange в namespace (resources.requests/limits), валидирует, не превышает ли spec лимиты.
ResourceQuotaVСчитает, не превысит ли создание этого объекта ResourceQuota namespace-а.
ServiceAccountM+VAuto-mount ServiceAccount tokens в Pod (projected volume). Создаёт default SA. Подставляет imagePullSecrets.
PodSecurity (с v1.25)VEnforces Pod Security Standards: privileged / baseline / restricted на основе labels namespace-а. Замена deprecated PSP.
DefaultStorageClassMЕсли PVC создаётся без storageClassName и есть default StorageClass — подставляет его.
NodeRestrictionVkubelet может modify только свои Node и Pods на ней. Защищает от compromised kubelet.
RuntimeClassVValidates RuntimeClass references в Pods.
TaintNodesByConditionMАвто-taint Node при изменении conditions (NotReady, MemoryPressure).
PriorityM+VResolve PriorityClass → integer priority.
EventRateLimitVThrottle Events создание (rate limit для шумных контроллеров).
MutatingAdmissionWebhookMЗапускает все external mutating webhooks (см. ниже).
ValidatingAdmissionWebhookVЗапускает все external validating webhooks.
CertificateApproval, CertificateSigningVВнутренние проверки workflow CertificateSigningRequest.
TIP

Чтобы увидеть, какие admission plugins включены в кластере, можно посмотреть kubectl -n kube-system get pod kube-apiserver-<node> -o yaml | grep enable-admission-plugins. На managed-кластере (EKS/GKE) этот флаг скрыт provider-ом, но обычно включены все рекомендованные.


Mutating vs Validating: разница

  • Mutating — может изменить объект через JSON Patch. Запускается первым.
    • Inject sidecar (Istio Envoy, Linkerd proxy).
    • Set defaults (LimitRange defaults, default StorageClass).
    • Inject labels / annotations (e.g. cost-center attribution).
    • Replace image tag через image policy (e.g. :latest:v1.2.3).
  • Validating — только говорит accept или reject. Не меняет объект. Запускается после всех mutating.
    • Enforce policy: «все Pods должны иметь resource limits», «нельзя использовать :latest image».
    • Verify cross-field constraints, которые не выражаются в OpenAPI schema.
    • Custom security checks: image signature verification, hash, registry whitelist.

Архитектурное последствие: mutating webhook может выпустить broken объект, если он сам сделал ошибку (например, добавил неверный label). Validating сразу после него поймает problem и отвергнет. Поэтому правило: mutating должен быть консервативным, validating — гарантирует invariants.


Webhook admission controllers: расширение через HTTP endpoint

Built-in admission хорошо, но что если нужна custom logic? Например, «все Deployment должны иметь label team: <team-name>, и this label должен матчить registered team в нашей системе». Это нельзя выразить через built-in admission. Для этого есть webhook admission — extensibility через HTTP endpoint.

Регистрация — через MutatingWebhookConfiguration / ValidatingWebhookConfiguration objects:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: enforce-team-label
webhooks:
- name: team-label.example.com
  clientConfig:
    service:                            # webhook — это Service в кластере
      name: team-policy-webhook
      namespace: policy-system
      path: /validate
      port: 443
    caBundle: <base64 CA cert>          # для mTLS apiserver → webhook
  rules:
  - operations: [CREATE, UPDATE]
    apiGroups: [apps]
    apiVersions: [v1]
    resources: [deployments]
  failurePolicy: Fail                   # если webhook не отвечает — REJECT request
  sideEffects: None
  admissionReviewVersions: [v1]
  matchConditions:                      # v1.30+ CEL prefilter
  - name: exclude-system-ns
    expression: 'object.metadata.namespace != "kube-system"'
  timeoutSeconds: 5
  namespaceSelector:                    # ограничить scope namespace-ами
    matchLabels: {policy-enforced: "true"}

Сам webhook — это HTTP endpoint, обычно Pod в кластере (Service). apiserver делает POST на endpoint с AdmissionReview запросом (JSON с объектом, operation, user info), webhook возвращает AdmissionReview ответ с allowed: true/false + patches (для mutating).

DANGER

failurePolicy: Fail — критичная настройка. Если webhook Pod упал или сетевой timeout, apiserver не может получить ответ. С failurePolicy: Fail (по умолчанию для validating) все CREATE Deployments будут блокироваться по всему кластеру, пока webhook не починят. Это часто становится outage incident-ом: deploy CI/CD блокируется, scaling блокируется. С failurePolicy: Ignore рискуете bypass policy, но кластер живёт. Production-best practice: использовать failurePolicy: Fail плюс namespaceSelector с явным excludelist для kube-system, cert-manager, и т.д. — иначе ловите cycles при первом upgrade.


ValidatingAdmissionPolicy: CEL без webhook (v1.30 GA)

ValidatingAdmissionPolicy (VAP) — новая фича Kubernetes (GA в v1.30), которая позволяет писать validation policies через CEL expressions прямо в apiserver, без external webhook service. Это in-tree evaluator — быстрее, дешевле, нет network roundtrip, нет caBundle, нет service mesh.

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: require-resource-limits
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups: [""]
      apiVersions: [v1]
      operations: [CREATE, UPDATE]
      resources: [pods]
  validations:
  - expression: |
      object.spec.containers.all(c,
        has(c.resources.limits) &&
        has(c.resources.limits.cpu) &&
        has(c.resources.limits.memory)
      )
    message: "All containers must have CPU and memory limits"
    reason: Forbidden
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: require-resource-limits-binding
spec:
  policyName: require-resource-limits
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels: {policy: enforce}

VAP — две сущности: ValidatingAdmissionPolicy (политика, переиспользуемая) и ValidatingAdmissionPolicyBinding (применение к scope). С v1.32 появилась mutating версия MutatingAdmissionPolicy (beta) — defaults через CEL.

Когда выбрать VAP, когда webhook:

  • VAP — простые правила, выразимые на CEL: проверки наличия полей, regex, comparisons. Быстрее, надёжнее (нет внешнего сервиса).
  • Webhook — сложная логика: нужно вызвать внешний API (e.g. registry trust), хранить state, делать ML inference, integrate с CMDB.

Kyverno и OPA Gatekeeper: policy engines

Писать webhooks с нуля для каждой policy утомительно. Существуют policy engines, которые ставятся как операторы и предоставляют декларативный язык policies:

  • Kyverno — Kubernetes-native, YAML-based policy language. Не требует CEL/Rego. Policies — это CR (ClusterPolicy, Policy). Поддерживает validate, mutate, generate (auto-create ConfigMap при namespace создании), verifyImages (cosign signatures). Стандарт de-facto в 2024-2026.
  • OPA Gatekeeper — wrapper над Open Policy Agent. Policies на Rego (более мощный, но steeper learning curve). Templates (ConstraintTemplate) + Constraints. Хорош для compliance-driven orgs.

Оба работают через ValidatingAdmissionWebhook (и MutatingAdmissionWebhook для Kyverno mutate). С точки зрения K8s — обычные webhooks; уникальность в developer experience выше webhook protocol.

Пример Kyverno policy:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: Enforce        # или Audit
  rules:
  - name: require-team-label
    match:
      any:
      - resources:
          kinds: [Deployment, StatefulSet, DaemonSet]
    validate:
      message: "Workload must have label 'team'"
      pattern:
        metadata:
          labels:
            team: "?*"                    # любое непустое значение

PodSecurity admission — встроенная замена PSP

С v1.25 в kube-apiserver включён PodSecurity admission — реализация Pod Security Standards через built-in admission. Это замена deprecated PodSecurityPolicy (PSP), которая была удалена в v1.25.

Работает через namespace labels:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted       # log violation
    pod-security.kubernetes.io/warn: restricted        # warning в kubectl output

Три levels: privileged (no restrictions), baseline (запрет очевидно опасных вещей), restricted (hardened defaults). Три actions: enforce (block), audit (audit log), warn (warning).

Подробно про PSS — в модуле 14 «Security». Здесь важно понимать: PSS — это admission. Это не «отдельный механизм», это плагин в общей admission цепочке.


CKAD scope

CKAD требует концептуального понимания admission:

  • Знать, что admission — это фаза перед записью в etcd.
  • Понимать разницу mutating и validating.
  • Знать про Pod Security Standards — это admission, и он работает через namespace labels.
  • Опознавать, что данный YAML — это MutatingWebhookConfiguration / ValidatingAdmissionPolicy.
  • НЕ требуется писать custom webhooks или CEL expressions.
# Discovery в кластере
kubectl get mutatingwebhookconfigurations
kubectl get validatingwebhookconfigurations
kubectl get validatingadmissionpolicy

# Если что-то блокируется — посмотреть, какие webhooks применяются
kubectl get validatingwebhookconfigurations -o yaml | less

Killer-моменты

  • Admission — это in-cluster middleware для всех API requests, после authn/authz перед etcd. Любая полезная политика в кластере проходит через admission.
  • Mutating всегда до validating. Validating видит финальное состояние объекта.
  • failurePolicy: Fail для validating webhook = single point of failure. Если webhook Pod упадёт — все matching requests блокируются. Тщательный namespaceSelector обязателен.
  • ValidatingAdmissionPolicy (CEL) — будущее policy. In-tree, без external service, без caBundle, без deployment. Используйте VAP вместо webhook когда возможно.
  • Sidecar injection (Istio, Linkerd) — это mutating webhook. Просто observation: «в namespace с label istio-injection: enabled каждый Pod на create получает sidecar contaier» — это classic admission webhook pattern.
  • PSP мёртв с v1.25. PodSecurity admission заменил его — проще, через namespace labels.

Проверка знанийKnowledge check
В чём разница между admission controller и authorization (RBAC)? Они оба могут отвергнуть request — зачем оба?
ОтветAnswer
Authorization (RBAC) проверяет: 'может ли этот user/SA делать verb=X на resource=Y в namespace=Z?'. Это coarse-grained access control. RBAC не видит тело request — оно проверяется ПОСЛЕ. Admission видит тело и может принимать решения по содержимому: 'этот Pod без resource limits — отвергнуть', 'этот image не из доверенного registry — отвергнуть'. RBAC отвечает на 'имеет ли право создавать Pods?'. Admission — 'этот конкретный Pod подходит под нашу policy?'. RBAC — coarse-grained, admission — fine-grained content-based. Они оба необходимы и работают независимо.
Проверка знанийKnowledge check
MutatingWebhookConfiguration настроен injectить sidecar Envoy в каждый Pod в namespace istio-injection=enabled. Pod создаётся, kubectl get pod показывает 2 containers (app + envoy). Где истинно появился второй container?
ОтветAnswer
В mutating admission phase. Когда kubectl create pod проходит authn/authz, apiserver вызывает все MutatingAdmissionWebhooks по очереди. Istio sidecar injector webhook получает Pod object, проверяет namespace label, и возвращает JSON patch, добавляющий container 'envoy' + initContainer 'istio-init' + volumes. apiserver применяет patch, и затем validating admission видит уже Pod с двумя containers. После этого объект пишется в etcd. kubelet потом видит спецификацию с двумя containers и запускает оба. То есть на диск пишется уже Pod с sidecar — кто бы потом ни читал, видит two-container pod. Sidecar injection прозрачен для kubelet и controllers.
Проверка знанийKnowledge check
ValidatingWebhookConfiguration с failurePolicy: Fail настроен на CREATE Deployments. Webhook Pod упал. Что произойдёт при kubectl apply -f deployment.yaml?
ОтветAnswer
apiserver попытается вызвать webhook endpoint — получит connection refused или timeout. Из-за failurePolicy: Fail apiserver интерпретирует это как 'webhook не подтвердил approve' и отвергает request с 500 Internal Server Error и сообщением вроде 'failed calling webhook'. ВСЕ Deployment apply будут блокироваться в matching scope (или во всём кластере, если нет namespaceSelector). Это classic outage scenario: deploy CI/CD не работает, новые workloads не создаются, scaling замораживается. Mitigation: (1) failurePolicy: Ignore (рискованно для security policies); (2) тщательный namespaceSelector, исключающий system namespaces (kube-system, cert-manager, и т.д.); (3) high availability webhook Deployment (replicas: 3, anti-affinity, PDB); (4) matchConditions с CEL prefilter, чтобы webhook вызывался реже.
Проверка знанийKnowledge check
Когда выбрать ValidatingAdmissionPolicy (CEL), а когда писать ValidatingWebhook?
ОтветAnswer
VAP — для простых, in-cluster проверок, которые выражаются на CEL: 'у Pod есть resource limits', 'image из доверенного registry по regex', 'namespace имеет нужные labels'. Преимущества: in-tree (быстрее, нет network call к webhook Pod), нет caBundle, нет deployment, нет failurePolicy outage risk, нет TLS-mgmt. Webhook — когда нужно: вызвать external API (registry trust, image scanner Trivy/Snyk, CMDB), хранить state между запросами (rate limit поведение), делать асинхронные операции, использовать сложную imperative логику (не выражается на CEL). Также webhook нужен для mutating сложного типа (хотя c v1.32 появилась MutatingAdmissionPolicy beta для простых defaults). Большинство простых policies лучше писать как VAP — это будущее. Сложные policies остаются webhooks (Kyverno, Gatekeeper, custom).

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какова правильная очерёдность фаз API request на kube-apiserver?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5