Лучшие практики и антипаттерны Kubernetes

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

Лучшие практики и антипаттерны Kubernetes

Сообщение 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
Почему этот урок - не очередной список из десяти советов

Ты дошел до продвинутой части курса, и теперь у тебя в руках опасный инструмент. Kubernetes с радостью задеплоит то, что в три часа ночи положит прод, выжрет всю память узла и не отдаст ни одного полезного лога. Большинство инцидентов в кластерах - это не хитрые атаки и не баги самого Kubernetes. Это голый Pod без owner-а, который удалили вместе с узлом. Это контейнер без limits, который сожрал RAM соседей. Это образ с тегом latest, который ночью молча подтянул сломанную версию. Все это - известные грабли, на которые наступают годами.

Этот урок - свод правил, к которым пришла индустрия. Не догма, а дистиллят боли. Мы разберем kubernetes best practices не как заклинания (всегда ставь requests), а с механикой: ЧТО конкретно ломается, если правило нарушить, и КАК планировщик, kubelet и API-сервер ведут себя в этих краевых случаях. Параллельно соберем зеркальный список - kubernetes антипаттерны, то есть конструкции, которые работают на демо и взрываются в продакшене. В конце - чеклист ревью манифеста, который можно повесить рядом с монитором.

Держи в голове простую рамку. Любая kubernetes лучшая практика отвечает на один из трех вопросов: переживет ли это рестарт узла (надежность), не уронит ли это соседей (изоляция ресурсов), сможет ли это кто-то воспроизвести и откатить (управляемость). Если правило не закрывает ни один из них - это карго-культ, и его можно игнорировать.

Изображение

Надежность: requests, limits и пробы - физика, а не формальность

Главное kubernetes правило, которое нарушают чаще всего: каждый контейнер обязан объявить, сколько ресурсов ему нужно. Без requests планировщик считает, что Pod-у нужно ноль CPU и ноль памяти, и пихает его на любой узел, где формально есть место. По факту места нет - и узел уходит в memory pressure. Дальше включается kubelet и начинает выселять Pod-ы по QoS-классам.

QoS - это не абстракция, а буквальный приговор при нехватке памяти. Pod без requests/limits получает класс BestEffort и выселяется первым. Pod, где requests меньше limits, - это Burstable, выселяется во вторую очередь по превышению request-а. И только Pod, где requests равны limits по обоим ресурсам, получает Guaranteed и трогается последним. Вот валидный кусок, который переводит контейнер в Burstable с жесткой защитой по памяти:

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

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    memory: "256Mi"
Заметь тонкость: limit по памяти ставим почти всегда (память неэластична - превысил, получил OOMKilled, код 137), а вот жесткий CPU-limit - предмет спора. CPU эластичен: при limit процесс не убивают, а троттлят через CFS-квоты ядра. На практике агрессивный CPU-limit дает рваную латентность даже при свободном CPU на узле, поэтому многие команды ставят только requests по CPU, а limit опускают, полагаясь на честный шер по запросам. Память - наоборот, всегда с limit.

Пробы - вторая половина надежности. Их три, и путать их - классическая ошибка. livenessProbe решает, жив ли контейнер; провал - рестарт. Если повесить туда тяжелую проверку зависимостей, при лагах БД kubelet начнет крутить рестарты живого приложения и устроит каскад. readinessProbe решает, готов ли Pod принимать трафик; провал - Pod просто убирают из EndpointSlice сервиса, но не убивают. startupProbe прикрывает медленный старт: пока она не прошла, liveness и readiness молчат, и тяжелое JVM-приложение не перезапускают в бесконечном цикле до прогрева. Разделяй их осознанно.

И последнее по надежности - graceful shutdown. Когда Pod удаляют, происходит две вещи параллельно: его убирают из эндпойнтов и шлют процессу SIGTERM. Гонка в том, что kube-proxy и DNS обновляются не мгновенно, и трафик еще секунду-две может литься в умирающий Pod. Лечится preStop-хуком с короткой паузой плюс корректной обработкой SIGTERM в коде (дослать ответы, закрыть соединения), и обязательно terminationGracePeriodSeconds с запасом.

Управляемость: правильные объекты, метки и образы

Никогда не создавай голый Pod в проде. Голый Pod не имеет контроллера-владельца: умер узел - Pod исчез навсегда, никто его не пересоздаст. Всегда оборачивай в контроллер. Stateless-сервис - Deployment. Что-то со стабильной идентичностью и личным диском (БД, очередь, etcd) - StatefulSet, он дает предсказуемые имена pod-0, pod-1 и приклеенные PVC. Узловой агент на каждой ноде (CNI, лог-шиппер) - DaemonSet. Разовая задача - Job, по расписанию - CronJob. Голый Pod допустим только для одноразового дебага через kubectl run --rm -it.

Метки - это не косметика, а способ, которым Kubernetes склеивает объекты. Service находит Pod-ы по selector-у меток, а не по именам. Промахнулся в одной букве лейбла - сервис указывает в пустоту, эндпойнтов ноль, и снаружи это выглядит как полностью рабочий Service без единого бэкенда. Используй стандартный набор app.kubernetes.io: name, instance, version, component, part-of, managed-by. Это не каприз - на эти ключи опираются Helm, дашборды, операторы и автоматика. Минимальный осмысленный набор:

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

metadata:
  labels:
    app.kubernetes.io/name: payments-api
    app.kubernetes.io/instance: payments-api-prod
    app.kubernetes.io/version: "1.8.3"
    app.kubernetes.io/component: backend
    app.kubernetes.io/part-of: billing
    app.kubernetes.io/managed-by: argocd
Образы - отдельная больная тема. Тег latest в проде - это бомба замедленного действия: реестр в любой момент может перепривязать его к другому слою, и два Pod-а одного Deployment-а окажутся на разном коде. Хуже того, при imagePullPolicy: Always (которая по умолчанию включается именно для latest) рестарт узла может подтянуть новый образ, о котором ты не просил. Правило простое: пинуй по immutable-тегу версии, а для критичного - по digest. Digest - это sha256-хеш контента, его невозможно подменить:

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

image: registry.example.ru/payments-api@sha256:9b2c... 
imagePullPolicy: IfNotPresent
Проверить, что реально приехало на узел, можно так:

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

kubectl get pod payments-api-7d9f-abc -o jsonpath='{.status.containerStatuses[0].imageID}'
# registry.example.ru/payments-api@sha256:9b2c...e1
Поле imageID показывает именно digest запущенного контейнера, а не то, что написано в манифесте. Если у тебя в спеке тег, а в imageID digest, который ты не узнаешь, - значит на узле версия, которую ты не контролируешь.

Изоляция и безопасность: default-deny, не от root, секреты снаружи

По умолчанию в Kubernetes сеть плоская: любой Pod достучится до любого другого Pod-а во всем кластере. Для прода это неприемлемо. Базовая практика безопасности - default-deny: в каждом namespace создаешь NetworkPolicy, которая запрещает весь входящий (а лучше и исходящий) трафик, а дальше точечно открываешь только нужное. Это zero-trust на уровне сети.

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

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: billing
spec:
  podSelector: {}
  policyTypes:
    - Ingress
Пустой podSelector означает все Pod-ы namespace-а, а отсутствие правил ingress - не пропускать ничего. Важная тонкость: NetworkPolicy исполняет CNI. Если у тебя стоит CNI без поддержки политик, манифест применится, но не будет иметь эффекта. Cilium на eBPF, Calico, а в Yandex Managed Kubernetes - встроенная поддержка - все это умеет политики. Проверяй, что твой CNI их реально энфорсит, иначе у тебя ложное чувство защищенности.

Дальше - не запускай от root. Pod Security Standards - современная замена устаревших PodSecurityPolicy. Уровень restricted требует non-root, запрещает привилегии, эскалацию и опасные capabilities. Включается лейблом на namespace, и API-сервер сам начинает резать нарушителей:

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

apiVersion: v1
kind: Namespace
metadata:
  name: billing
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/warn: restricted
А сам Pod должен соответствовать. Минимальный securityContext для restricted:

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

securityContext:
  runAsNonRoot: true
  seccompProfile:
    type: RuntimeDefault
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
Секреты - снаружи. Объект Secret в Kubernetes по умолчанию хранится в etcd всего лишь в base64, то есть открытым текстом для любого, у кого есть доступ к etcd или права на чтение секретов. Не коммить секреты в git и не держи пароли в обычных манифестах. Стандарт 2026 - External Secrets Operator: реальные секреты живут в Yandex Lockbox, HashiCorp Vault или аналоге, а в кластер через объект ExternalSecret подтягивается только ссылка, и оператор синхронизирует значение. В git едет описание, где взять секрет, а не сам секрет.

Высокая доступность: PodDisruptionBudget и anti-affinity

Допустим, у тебя три реплики - кажется, что HA есть. Приходит админ, делает kubectl drain ноды на обслуживание, и если все три реплики стояли на ней, сервис лег целиком. От этого защищают два механизма в паре.

PodDisruptionBudget (apiVersion policy/v1) ограничивает добровольные выселения - drain, апгрейд узлов, работу autoscaler-а. Он говорит планировщику выселений: не уводи разом больше, чем можно. PDB защищает только от добровольных нарушений; от падения узла он не спасет - это уже работа репликации и anti-affinity.

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

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payments-api-pdb
  namespace: billing
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: payments-api
При drain-е Eviction API не выселит Pod, если это нарушит minAvailable. Команда повиснет в ожидании - это правильное поведение, а не баг. Частая грабля: minAvailable: 2 при двух репликах. Тогда нельзя выселить ни одного Pod-а, drain зависнет навсегда, а cluster-autoscaler не сможет убрать пустой узел. Держи запас: реплик больше, чем minAvailable.

Второй механизм - podAntiAffinity. Он размазывает реплики по разным узлам и зонам, чтобы падение одного узла не унесло все сразу. Современный и более гибкий способ - topologySpreadConstraints:

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

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: payments-api
maxSkew: 1 означает, что разница в числе Pod-ов между любыми двумя узлами не больше одного. PDB плюс spread по зонам - это и есть рабочая основа HA, а не просто число replicas: 3 в манифесте.

GitOps вместо kubectl-в-прод

Самая коварная категория - ручные изменения мимо git. kubectl edit на проде кажется быстрым, но создает дрейф: реальное состояние кластера расходится с тем, что в репозитории, и никто этого не видит. Через месяц никто не помнит, почему у Pod-а вдруг другой лимит, откатить не к чему, воспроизвести в стейджинге невозможно.

Практика 2026 - GitOps: git является единственным источником правды, а Argo CD или Flux непрерывно приводят кластер к описанному в репозитории состоянию. Любое изменение - это коммит и pull request с ревью. Откат - это git revert. Argo CD к тому же подсвечивает drift: если кто-то руками поменял ресурс, ты увидишь OutOfSync и сможешь засинкать обратно. Доступ на запись в прод у людей при этом стоит закрыть - пусть пишет только контроллер.

Манифесты не копипасть между окружениями - используй Kustomize: базовый набор плюс оверлеи для dev/stage/prod, которые патчат только различия (реплики, лимиты, домены). Helm - для упаковки и параметризации переиспользуемых чартов. А enforcement правил из этого урока автоматизируй политиками: Kyverno или нативная ValidatingAdmissionPolicy на CEL (стабильна, работает прямо в API-сервере без внешнего вебхука) могут блокировать на входе Pod без лимитов, образ с latest или запуск от root.

Сводный список антипаттернов
  • cluster-admin всем подряд вместо узких ролей RBAC на namespace - один скомпрометированный токен открывает весь кластер.
  • Один namespace на все - нет границ для квот, политик, RBAC и сетевой изоляции; все сервисы в общей куче.
  • privileged: true и hostNetwork без крайней нужды - контейнер фактически равен руту на узле.
  • Pod-ы без requests/limits - BestEffort, выселяются первыми, роняют соседей при нехватке памяти.
  • БД, развернутая голым StatefulSet без оператора - бэкапы, failover и апгрейды придется делать руками, и в инцидент это всплывет.
  • Состояние в emptyDir или в локальной ФС контейнера - данные исчезают при рестарте; состояние - в PVC или внешнем хранилище.
  • Ручные правки kubectl edit в обход git - дрейф, который невозможно воспроизвести и откатить.
  • latest вместо пина по версии или digest - неконтролируемая подмена кода под нагрузкой.
Мини-лаба: превращаем плохой манифест в хороший

Возьми заведомо плохой Pod и пройди по чеклисту руками.
  • Создай голый Pod с образом nginx:latest без ресурсов и проб: kubectl run badpod --image=nginx:latest. Посмотри kubectl describe pod badpod - найди QoS Class: BestEffort и imagePullPolicy: Always.
  • Перепиши его в Deployment с тремя репликами, метками app.kubernetes.io, пином образа по тегу версии, requests/limits и тремя пробами.
  • Добавь securityContext под restricted и навесь на namespace лейбл pod-security.kubernetes.io/enforce=restricted. Применяй, читай предупреждения, чини, пока не пройдет.
  • Добавь PodDisruptionBudget с minAvailable: 2 и topologySpreadConstraints по hostname. Выполни kubectl drain на узле, где сидит реплика, и убедись, что выселяется не больше разрешенного.
  • Сравни kubectl get pod ... -o jsonpath='{.status.qosClass}' до и после - было BestEffort, стало Burstable или Guaranteed.
Чеклист ревью манифеста
  • Это контроллер (Deployment/StatefulSet/DaemonSet/Job), а не голый Pod.
  • Есть requests и memory limit; CPU-limit - осознанно.
  • Три пробы по назначению: startup, readiness, liveness.
  • Образ пинован по версии или digest, imagePullPolicy адекватен.
  • Метки app.kubernetes.io проставлены и совпадают с selector-ом сервиса.
  • securityContext: non-root, drop ALL caps, no privilege escalation; namespace под PSS restricted.
  • Секреты через External Secrets, не в git и не в открытом манифесте.
  • NetworkPolicy default-deny в namespace плюс точечные разрешения.
  • Для HA: PDB с запасом и spread/anti-affinity по узлам и зонам.
  • graceful shutdown: SIGTERM обрабатывается, есть terminationGracePeriodSeconds.
  • Манифест едет через git и Argo CD/Flux, а не через kubectl edit.
Итог

Все эти k8s practices сводятся к трем вопросам: переживет ли рестарт, не уронит ли соседей, можно ли воспроизвести и откатить. Голый Pod, latest, отсутствие лимитов, root и ручные правки - вот пятерка, которая дает большинство ночных инцидентов. Прогоняй каждый манифест через чеклист, а лучше - повесь enforcement политикой, чтобы плохое просто не попадало в кластер. Хорошая практика, которую не проверяет машина, рано или поздно будет нарушена человеком в три часа ночи.
👍2 ❤️ 🔥1 😄 🤔1
Аватара пользователя
deno98
Сообщения: 1
Зарегистрирован: 31 май 2026, 20:25

Re: Лучшие практики и антипаттерны Kubernetes

Сообщение deno98 »

Про CPU-limit прям глаза открыло. У нас как раз латентность скакала при свободном проце, а мы лимиты только закручивали. Сняли жесткий cpu limit на двух сервисах - дерганья пропали.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
jvilma
Сообщения: 1
Зарегистрирован: 02 июн 2026, 22:17

Re: Лучшие практики и антипаттерны Kubernetes

Сообщение jvilma »

Вопрос по PDB и кворумным базам: если minAvailable держать с запасом, а под у меня в StatefulSet ровно 3 реплики etcd, как не словить ситуацию когда drain двух нод подряд убивает кворум? PDB же про добровольные, а ноды иногда падают сами.
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
Следующая глава →
Сквозной проект и путь дальше: от манифеста до прода

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: minikube или kind что выбрать для локального кластера kuberneteskubectl get describe logs основные команды для работы с кластеромчем отличается deployment от pod в kubernetes простыми словамичто такое service в kubernetes и как поды находят друг другаrequests и limits в kubernetes как правильно задать ресурсы подуnamespace в kubernetes зачем нужен и как разделить ресурсы

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

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

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