Admission и Pod Security: контроль на входе

Рейтинг: 78.5% · 14 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Admission и Pod Security: контроль на входе

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе (вы здесь)
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Боль: кластер пускает внутрь что угодно

Представь ситуацию. Разработчик выкатывает Deployment, а в нём - контейнер с image: myapp:latest, securityContext с privileged: true, hostPath на /var, ни одного resource limit и образ тянется с какого-то dockerhub.io/randomuser. RBAC проверил, что у разработчика есть право create pods в этом namespace - и всё, etcd радостно записал манифест, kubelet запустил привилегированный контейнер с доступом к корню ноды. Поздравляю, у тебя в проде дыра.

RBAC отвечает на вопрос "кто и что может делать" (create/get/delete на ресурсе), но он слеп к содержимому. Ему всё равно, privileged там контейнер или нет, latest у тебя тег или конкретная версия, есть ли limits. Вот этот пробел - "запрос разрешён, но его содержимое опасно" - и закрывает admission control. Это последний рубеж между API-запросом и записью в etcd, где можно сказать "нет" или тихо подправить манифест. В этом уроке разберём admission controller kubernetes по слоям: как устроена цепочка, чем mutating отличается от validating, как работает pod security admission на namespace-метках вместо удалённого PodSecurityPolicy, и почему в 2026 связка validatingadmissionpolicy + CEL начинает вытеснять вебхуки. Это фундамент темы kubernetes security, без которого policy-as-code дальше не построить.

Изображение

Механика: где живёт admission в пути запроса

Любой запрос к kube-apiserver (kubectl apply, контроллер, оператор - неважно) проходит конвейер строго по порядку:
  • Authentication - кто ты (сертификат, токен, OIDC).
  • Authorization - что тебе можно (RBAC, Node, Webhook).
  • Admission - можно ли пропустить именно ЭТОТ объект, и не надо ли его подправить.
  • Schema validation и запись в etcd.
Admission - не один контроллер, а длинная встроенная цепочка плюс точки расширения. Запускается она в две фазы, и порядок тут критичен:
  • Mutating фаза - контроллеры, которые МЕНЯЮТ объект. Проставляют дефолты, инжектят sidecar, добавляют лейблы, подставляют ServiceAccount-токен. Сюда же относится MutatingAdmissionWebhook.
  • Validating фаза - контроллеры, которые только ПРОВЕРЯЮТ и либо пропускают, либо отклоняют запрос с ошибкой. Менять объект они уже не могут. Сюда относятся ValidatingAdmissionWebhook, ValidatingAdmissionPolicy и сам Pod Security Admission.
Почему сначала mutating, потом validating? Логика простая: сначала дай всем дописать и поправить объект до финального вида, и только потом проверяй то, что реально уйдёт в etcd. Иначе валидатор проверял бы недописанный манифест, а mutating-вебхук потом подсунул бы туда что-то новое уже после проверки - дыра.

Важная тонкость про идемпотентность mutating-вебхуков. Apiserver гарантирует, что в одной фазе вебхуки вызываются, но при ре-инвокации (reinvocationPolicy: IfNeeded) твой вебхук могут позвать повторно, если другой вебхук после него поменял объект. Поэтому мутация обязана быть идемпотентной: добавляешь sidecar - сначала проверь, что его ещё нет. Классические грабли - оператор service mesh, который при двойном вызове инжектит два sidecar-контейнера в один Pod.

Pod Security Admission: профили на namespace вместо PodSecurityPolicy

PodSecurityPolicy (PSP) удалили в Kubernetes 1.25 - он был сложным, легко ломался на порядке привязок и плохо масштабировался. На замену пришёл встроенный контроллер Pod Security Admission, который реализует три уровня Pod Security Standards:
  • privileged - не ограничивает ничего. Для системных нагрузок (CNI, CSI, агенты мониторинга), которым реально нужен доступ к ноде.
  • baseline - запрещает явно опасное: privileged-контейнеры, hostNetwork, hostPID, hostIPC, hostPath, добавление опасных capabilities. Это разумный минимум для прикладных namespace.
  • restricted - жёсткий профиль по best practices: runAsNonRoot, запрет allowPrivilegeEscalation, seccompProfile: RuntimeDefault, drop ALL capabilities, только проектные тома. Для security-sensitive приложений.
Главная фишка - конфигурация через ЛЕЙБЛЫ namespace, без отдельных объектов политик. Для каждого уровня есть три режима:
  • enforce - нарушающий Pod отклоняется. Жёстко.
  • audit - Pod создаётся, но в audit-лог пишется нарушение.
  • warn - Pod создаётся, но клиенту (тебе в kubectl) возвращается warning.

Код: Выделить всё

apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.31
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
Тут payments принудительно держит restricted. Поле enforce-version пиннит версию стандарта - без него правила "плывут" при апгрейде кластера, и Pod, который вчера проходил, завтра внезапно отклоняется. Всегда пинни версию.

Теперь практика. Создадим namespace и попробуем запустить заведомо плохой Pod:

Код: Выделить всё

$ kubectl label ns payments pod-security.kubernetes.io/enforce=restricted

$ kubectl -n payments run bad --image=nginx
Error from server (Forbidden): pods "bad" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "bad" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "bad" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "bad" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "bad" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
Разбор вывода: контроллер вернул HTTP 403 Forbidden и перечислил ВСЕ нарушения сразу - удобно, не надо чинить по одному. Каждая строка говорит конкретное поле, которое нарушает restricted. Профиль restricted:latest в кавычках - это уровень и версия стандарта. Чтобы Pod прошёл, securityContext должен выглядеть так:

Код: Выделить всё

spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx:1.27
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
Стратегия выката на живой кластер: НИКОГДА не вешай enforce сразу. Сначала включи warn и audit на нужный уровень, посмотри неделю-две, что именно у тебя сломается (особенно прилетит от чужих чартов и операторов), почини манифесты - и только потом enforce. Иначе ты положишь половину рабочих нагрузок одним лейблом.

Грабли PSA: контроллер работает на уровне namespace и проверяет именно Pod-шаблон. Если разработчик создаёт Deployment с плохим шаблоном, PSA на этапе создания Deployment выдаст только warning (контроллер реплик создаст Pod уже от имени системного сервис-аккаунта), а реальное отклонение случится при попытке поднять Pod - и ты увидишь это в событиях ReplicaSet, а не в выводе kubectl apply. Поэтому warn так ценен: он подсвечивает проблему сразу человеку.

Где PSA не справляется и нужен policy-as-code

Pod Security Admission намеренно простой: три фиксированных уровня, и всё. А что если тебе нужно "запретить тег latest", "разрешить образы только из registry.company.ru", "требовать лейбл team на каждом Deployment", "запретить external LoadBalancer Service"? PSA это не умеет в принципе - он про securityContext Pod, и точка. Тут два пути: ValidatingAdmissionPolicy (нативный, на CEL) или внешние движки policy-as-code (Kyverno, OPA Gatekeeper) поверх admission-вебхуков.

ValidatingAdmissionPolicy: политики на CEL без вебхуков

До недавнего времени любая кастомная политика означала свой admission-вебхук: отдельный под, TLS-сертификат, сетевой хоп на каждый запрос к apiserver, точка отказа. Упал вебхук с failurePolicy: Fail - и apiserver не может создавать поды вообще. В Kubernetes 1.30 в стабильную версию (GA) вышел ValidatingAdmissionPolicy - механизм, который выполняет проверки прямо ВНУТРИ apiserver на языке выражений CEL (Common Expression Language). Никакого пода, никакого TLS, никакого сетевого хопа. apiVersion - admissionregistration.k8s.io/v1.

Связка из двух объектов. Сама политика описывает ЧТО проверять:

Код: Выделить всё

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: disallow-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
  - expression: >-
      object.spec.template.spec.containers.all(c, !c.image.endsWith(':latest') && c.image.contains(':'))
    message: "образ обязан иметь явный тег, latest и образы без тега запрещены"
А Binding говорит, ГДЕ её применять - к каким namespace:

Код: Выделить всё

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: disallow-latest-tag-binding
spec:
  policyName: disallow-latest-tag
  validationActions: ["Deny"]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: production
Разбор. В выражении CEL object - это проверяемый объект (Deployment). Функция all(c, ...) - это квантор "для всех контейнеров истинно". Мы требуем, чтобы у каждого контейнера image не заканчивался на :latest и при этом содержал двоеточие (то есть тег вообще указан явно). validationActions поддерживает Deny (отклонить), Warn (предупредить), Audit (записать в лог) - можно комбинировать, например ["Warn", "Audit"] для мягкого внедрения. namespaceSelector ограничивает политику только продакшеном, не трогая dev. Проверим:

Код: Выделить всё

$ kubectl apply -f deploy-latest.yaml
The deployments "web" is invalid: ValidatingAdmissionPolicy 'disallow-latest-tag'
with binding 'disallow-latest-tag-binding' denied request:
образ обязан иметь явный тег, latest и образы без тега запрещены
Видишь, в ошибке прямо указаны имя политики и имя биндинга - отлаживать намного проще, чем безымянный отказ вебхука. CEL умеет переменные (variables), параметры (paramKind для вынесения списков разрешённых реестров в отдельный ConfigMap/CRD) и messageExpression для динамических сообщений. Когда VAP хватает - бери его: меньше движущихся частей, ниже задержка, нечему падать. Когда логика сложнее (генерация ресурсов, мутация, проверки с внешним контекстом) - тогда Kyverno или OPA Gatekeeper.

Admission webhook: когда нужна логика снаружи

Webhook admission - это твой HTTP-сервер, который apiserver дёргает на каждый подходящий запрос. Регистрируется через ValidatingWebhookConfiguration или MutatingWebhookConfiguration. Apiserver шлёт AdmissionReview с объектом, сервер возвращает allowed: true/false (и patch для mutating). Именно так работают Kyverno и Gatekeeper - это готовые вебхук-движки, где политики ты пишешь декларативно (Kyverno - YAML, Gatekeeper - Rego), а не кодишь сервер руками.

Два параметра, на которых горят все:
  • failurePolicy. Fail - если вебхук недоступен, запрос ОТКЛОНЯЕТСЯ. Ignore - запрос ПРОПУСКАЕТСЯ. Поставишь Fail на критичный namespace и уронишь вебхук - перестанут создаваться все поды, включая сам вебхук, если он там же. Классический deadlock.
  • namespaceSelector / objectSelector. ОБЯЗАТЕЛЬНО исключай kube-system и namespace самого вебхука, иначе при рестарте кластера ничего не поднимется.
Типичные грабли и антипаттерны
  • enforce restricted сразу на боевой namespace без фазы warn/audit. Результат - массовое падение подов. Сначала наблюдай, потом запрещай.
  • Не запиннена enforce-version у PSA. После апгрейда кластера стандарт ужесточается, вчерашние рабочие поды отклоняются. Всегда указывай версию.
  • failurePolicy: Fail на вебхуке, который сам крутится в проверяемом namespace - circular dependency, кластер не стартует с нуля.
  • Неидемпотентный mutating-вебхук - двойная инъекция sidecar при ре-инвокации.
  • Расчёт, что PSA отклонит плохой Deployment в kubectl apply. Нет - отклонение случится на Pod внутри ReplicaSet, ищи в событиях, не в выводе apply.
  • Один гигантский вебхук на все ресурсы вместо узких matchConstraints. Лишняя нагрузка на каждый запрос и риск общего отказа.
  • Дублирование: PSA уже закрывает privileged/hostNetwork - не пиши то же самое ещё раз в Kyverno. Используй слои по назначению.
Мини-лаба: собери контроль на входе руками

На любом кластере (подойдёт kind, k3s, Minikube или Yandex Managed Kubernetes):
  • Создай namespace lab и повесь pod-security.kubernetes.io/warn=restricted и audit=restricted (пока без enforce).
  • Запусти kubectl -n lab run test --image=nginx и поймай warning в выводе - прочитай, какие поля нарушены.
  • Перепиши Pod в YAML с корректным securityContext (runAsNonRoot, drop ALL, allowPrivilegeEscalation: false, seccompProfile RuntimeDefault) и убедись, что warning исчез.
  • Переключи warn на enforce и проверь, что плохой Pod теперь отклоняется с 403.
  • Создай ValidatingAdmissionPolicy disallow-latest-tag и Binding к namespace lab. Попробуй задеплоить Deployment с image: nginx:latest - получи Deny.
  • Поменяй в Binding validationActions с ["Deny"] на ["Warn"] и убедись, что теперь только предупреждение - так выглядит мягкое внедрение политики.
Контрольные вопросы
  • Почему mutating-фаза admission идёт строго ДО validating, и что сломается при обратном порядке?
  • Чем pod security admission на namespace-лейблах принципиально отличается от удалённого PodSecurityPolicy, и зачем нужно поле enforce-version?
  • В каких случаях достаточно ValidatingAdmissionPolicy на CEL, а когда придётся брать admission webhook (Kyverno/Gatekeeper)?
  • Почему failurePolicy: Fail на вебхуке в kube-system может полностью заблокировать запуск кластера?
Итог

Admission - последний шлюз перед etcd, где кластер решает, пускать объект и в каком виде. Mutating правит (дефолты, sidecar), validating отклоняет. Pod Security Admission даёт три готовых профиля (privileged/baseline/restricted) прямо на лейблах namespace - это замена PodSecurityPolicy и базовый уровень kubernetes security. Для кастомных правил в 2026 первым делом смотри на validatingadmissionpolicy с CEL - он работает внутри apiserver без вебхуков и точек отказа. А когда нужна сложная логика, мутация или генерация - переходишь к policy-as-code: Kyverno и OPA Gatekeeper. В следующем уроке как раз погрузимся в Kyverno и напишем боевой набор политик.
👍6 ❤️3 🔥2 😄 🤔1
Аватара пользователя
derrik
Сообщения: 1
Зарегистрирован: 14 май 2026, 02:04

Re: Admission и Pod Security: контроль на входе

Сообщение derrik »

Долго не мог понять, почему мой Deployment с latest применяется без ошибки, а поды не поднимаются. Оказалось ровно то, что тут написано - PSA ругается на этапе ReplicaSet, в событиях, а не в apply. Спасибо, сэкономили мне вечер.
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
reggaerat
Сообщения: 1
Зарегистрирован: 15 май 2026, 04:42

Re: Admission и Pod Security: контроль на входе

Сообщение reggaerat »

А правильно понимаю, что VAP полностью заменит Kyverno со временем? Или всё-таки для мутации и генерации ресурсов вебхук-движки никуда не денутся?
👍3 ❤️ 🔥 😄 🤔1
Ответить
← Предыдущая глава
RBAC и аутентификация глубже
Следующая глава →
Policy as code: Kyverno и OPA Gatekeeper

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: kubernetes networkpolicy и securitycontext как настроить

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость