Kubernetes production usage patterns from "Kubernetes Patterns" (Ibryam, Huss): Sidecar (Istio Envoy mTLS), Ambassador (proxy для external services), Init container (iptables setup, db migrate), Adapter (translate /stats -> /metrics), Operator pattern (CRD + reconcile loop with Postgres operator example, Leader election для HA), Self-healing (probes), Predictable demands (resources/limits) и Elastic Scale (HPA reactive scaling). Three scenarios: sidecar mTLS via Istio Envoy, Postgres operator reconciles cluster state with failover, HPA scales deployment on traffic spike. ADRs cover sidecar vs library vs node-agent trade-offs and operator vs Helm vs raw manifests for stateful workloads.
Kubernetes — это не про «запустить контейнер», а про набор паттернов, которые превращают сырой kernel + cgroups + namespaces в production-grade platform. Если знаешь только Deployment и Service — ты используешь ~10% возможностей и регулярно изобретаешь велосипеды, которые уже решены патентованными паттернами в книге Ibryam/Huss.
Без понимания паттернов нельзя осмысленно ответить:
OOMKilled, хотя top показывает 70% memory usage?Паттерны Kubernetes — это словарь архитектора. Без них любая дискуссия про k8s превращается в YAML-инженерию: «давай добавим securityContext: runAsNonRoot» вместо «нам нужен Sidecar для mTLS, потому что polyglot теam не хочет ставить SDK в каждый язык».
Kubernetes patterns = набор повторяющихся решений для распределённых систем, реализованных через декларативный API + control loops. Pod — атомарная единица; всё остальное (Deployment, StatefulSet, Operator) — controllers, которые reconcile observed state -> desired state.
Три уровня абстракции, которые надо чётко разделять:
Foundational patterns (Predictable Demands, Health Probe, Managed Lifecycle) — фундамент. Без правильных resource requests/limits и probes ничего сверху не работает корректно. HPA без requests = бесполезен. Service без readinessProbe = 503 errors при rolling update.
Structural patterns (Init Container, Sidecar, Adapter, Ambassador) — композиция контейнеров в pod. Pod = atomic unit of scheduling, контейнеры в нём делят network namespace (localhost!) и volumes. Это даёт богатый toolkit для разделения concerns без переписывания app.
Behavioural / Advanced patterns (Singleton, StatefulSet, Operator, Elastic Scale) — паттерны на уровне всего workload, часто с поддержкой stateful logic. Operator — это «программируемый k8s»: ты пишешь controller, который extends API новыми custom resources (CRD).
Ключевая истина: Kubernetes — это control loop framework. Каждый паттерн (Deployment, HPA, Operator) — это reconcile loop: смотри текущее состояние, сравни с желаемым, applый разницу, повтори. Когда ты понимаешь reconcile loop, ты понимаешь весь k8s.
Диаграмма показывает четыре блока структурных и поведенческих паттернов одновременно, чтобы видеть, как они комбинируются в реальном кластере.
Верх-лево — Pod с Sidecar + Init Container (Istio Envoy mTLS). Внутри одного pod: два init containers (istio-init настраивает iptables, db-migrate накатывает миграции), главный app container, два sidecar containers (istio-proxy = Envoy для mTLS, fluent-bit для логов), adapter container (prometheus-exporter переводит legacy /stats в /metrics), probes (liveness/readiness), и resources limit. Это production-grade pod в современном service mesh окружении. Стрелки через localhost показывают ключевой принцип: контейнеры в pod делят network namespace, поэтому app говорит с sidecar через 127.0.0.1.
Верх-право — Control Plane. kube-apiserver, etcd, scheduler, controller-manager, kubelet. Это сердце k8s: всё состояние живёт в etcd, apiserver — единственный путь записи/чтения, контроллеры подписываются на watch events и реконсилируют. Кубeлет на каждой ноде следит за pods и выполняет probes.
Низ-лево — Operator pattern (Zalando Postgres Operator). CRD (postgresqls.acid.zalan.do) расширяет API. CR (billing-db) — экземпляр. Operator pod (с leader election через Lease) watch-ит CR-ы и manage-ит StatefulSet с Postgres master + 2 replicas, плюс два Service-а (для writes и read-only). Это canonical example того, как domain knowledge (failover, streaming replication) кодируется в k8s controller.
Низ-право — HPA reactive scaling. metrics-server scrape-ит kubelet каждые 15s, HPA controller polls metrics-server, считает desired replicas по формуле, patch-ит Deployment.spec.replicas. Новый pod проходит probes -> попадает в Endpoints -> Service шлёт трафик. Показано вместе с Predictable Demands: без realistic requests формула HPA даёт мусор.
Верх-крайний-право — Ambassador pattern. Маленький pod где app говорит с localhost:6379 (думая что это Redis), а ambassador-proxy sidecar делает TLS + retries + sharding к managed Redis. Контраст с Sidecar: Ambassador — это proxy наружу (к external service), Sidecar — это co-located helper для самого app.
Sidecar mTLS через Istio Envoy. Init container istio-init запускается ПЕРВЫМ (это особенность init containers — они выполняются строго последовательно, до старта app containers, каждый должен exit 0 иначе pod в Init:Error). istio-init выполняет iptables -t nat -A PREROUTING -p tcp -j REDIRECT --to-port 15001 — kernel правила iptables в network namespace pod остаются после exit init container. Дальше db-migrate накатывает alembic upgrade head ДО старта app — это правильное место для миграций (а не в entrypoint app, где race condition между replicas).
После всех init containers стартует app + sidecar containers параллельно. App слушает :8080, не знает ни о каком mTLS. Когда app делает curl http://billing-service:8080, kernel iptables redirect-ит соединение на Envoy :15001. Envoy смотрит destination, проверяет SPIFFE identity, делает TLS handshake с использованием сертификата из Istio Citadel, прокидывает запрос. Zero-code mTLS для polyglot team — Java/Go/Python/Node разработчикам не нужны разные mTLS-libs.
Платится цена: ~50-100MB RAM на pod (Envoy footprint) + 0.5-2ms latency на каждый hop. Дебаг сложнее: kubectl logs my-pod показывает только app, для Envoy нужен kubectl logs my-pod -c istio-proxy. Graceful shutdown требует preStop hook: sleep 5 -> app exit -> Envoy ждёт connection drain — иначе in-flight requests рвутся.
Operator реконсилит Postgres cluster. kubectl apply -f postgres-crd.yaml регистрирует CRD — теперь kubectl get postgresql работает как kubectl get pods. Пользователь kubectl apply -f billing-db.yaml (kind: postgresql, replicas: 3, version: 15.4, S3 backup). apiserver валидирует против CRD schema, сохраняет в etcd.
Operator deployment имеет 3 replicas для HA, но активен только один — leader election через coordination.k8s.io/v1 Lease object. Активный pod пишет lease каждые 5 секунд (renewDeadlineSeconds=10). Если умирает — через ~15s другой захватывает lease. Без leader election 3 pods одновременно реконсилили бы один CR -> race condition: 3 попытки создать StatefulSet, конфликт в etcd, бесконечный retry loop.
Leader делает watch на postgresqls.acid.zalan.do (long-poll, получает event при любом change). На event «billing-db ADDED»: observed=ничего, desired=3-node cluster -> action plan. Создаёт StatefulSet pod-0 (initdb), pod-1 (pg_basebackup из pod-0, streaming replication), pod-2 (то же). Создаёт два Service-а: billing-db (selector primary=true) и billing-db-repl (selector primary=false).
Когда master падает (node crash): kubelet перестаёт обновлять pod heartbeat. Operator видит pod-0 NotReady > 30s -> failover protocol -> выбирает реплику с минимальным lag -> pg_promote() на pod-1 -> меняет label на pod-1: primary=true -> Service billing-db мгновенно route writes на pod-1 -> pod-2 переключает streaming на нового master. Self-healing за 30-60s без человека. Это и есть operator pattern: domain knowledge (Postgres failover) закодирован как k8s controller. Helm chart этого не умеет — он template engine, не controller.
HPA reactive scaling при traffic spike. Baseline: 3 pods, CPU ~250m / requests 500m = 50% utilization. HPA target 70%. Формула: desiredReplicas = ceil(currentReplicas * (currentMetric / targetMetric)) = ceil(3 * (250/350)) = 3 -> no action.
metrics-server каждые 15s делает GET /metrics/resource у kubelet (тот собирает данные из cgroups через cAdvisor — это raw kernel метрики, не нужен Prometheus в scale loop). HPA controller каждые 15s polls metrics-server.
Traffic spike: CPU усреднённо 847m. HPA recompute: desired = ceil(3 * 169/70) = 8 replicas. Но HPA имеет behavior.scaleUp.policies — например max 100% increase per 60s -> можем добавить max 3 pods (3 -> 6), не сразу 8. Это защита от over-shoot.
PATCH /apis/apps/v1/deployments/my-service/scale {replicas: 6} -> Deployment controller -> ReplicaSet -> создаёт 3 новых pods. kube-scheduler смотрит requests=500m/256Mi -> ищет ноду с таким свободным capacity (вот зачем нужны realistic requests — Predictable Demands). Новый pod: startupProbe (max 60s) -> readinessProbe -> попадает в Service Endpoints -> начинает получать трафик. Без readinessProbe Service слал бы трафик до того как app готов = 503 errors.
Scale-down: HPA ждёт stabilizationWindowSeconds=300 (5 min) перед уменьшением, чтобы избежать flapping (scale up/down/up/down при колеблющемся трафике).
ADR-001: Sidecar vs library vs node-agent для cross-cutting concerns. Cross-cutting concerns (mTLS, retries, tracing, rate limiting, observability) реализуются тремя способами: (1) Library — встроить SDK в каждый сервис (Hystrix, OpenTelemetry SDK, gRPC interceptors); (2) Sidecar — отдельный container в pod, перехватывает трафик через iptables (Istio Envoy, Linkerd-proxy); (3) Node-agent / DaemonSet — один процесс на ноду обслуживает все pods (Cilium eBPF service mesh, fluentd для logs).
Trade-offs: Library = zero network overhead, но нужен SDK на каждый язык, обновление = redeploy всех сервисов. Sidecar = polyglot-friendly, обновление independent от app, но +50-100MB RAM/pod, +0.5-2ms latency на каждый hop, debugging сложнее. Node-agent = низкий overhead (один процесс на ноду), но shared failure domain (упал agent — все pods на ноде affected), eBPF требует kernel 5.x+.
Решение: Default = sidecar для multi-team polyglot environments — это priceless для совместимости. Library — когда latency-critical hot path (HFT, real-time bidding) или embedded-style сервис (один язык, узкая команда). Node-agent (Cilium eBPF) — когда есть ops-team готовый поддерживать eBPF + kernel >=5.10 + density критична (1000+ pods/node). Не смешивать паттерны для одного concern — debugging превращается в ад.
ADR-002: Operator vs Helm chart vs raw manifests для stateful workloads. Развернуть Postgres в k8s можно тремя способами: (1) Raw YAML — StatefulSet + PVC + Service + ConfigMap; (2) Helm chart (bitnami/postgresql) — параметризованные шаблоны; (3) Operator (Zalando, CNPG, Crunchy) — собственный controller с domain knowledge (failover, switchover, backup, point-in-time recovery, version upgrades).
Raw YAML = full control, но Day-2 операции (failover при падении master, добавить standby, minor version upgrade) — manual ops nightmare. Helm chart = быстрый bootstrap, но это template engine, не controller — не реагирует на изменения состояния. Operator = полный lifecycle через kubectl apply.
Решение: Stateful workloads (Postgres, Kafka, Elasticsearch, Redis cluster) — operator практически обязателен. Stateless workloads (REST API, worker pool) — Deployment + HPA достаточно. Для Postgres в 2026: CNPG (CloudNativePG) — best-in-class. Zalando — battle-tested на тысячах clusters. Crunchy — enterprise с support contracts. Не писать собственный operator если cloud provider предлагает managed (RDS/Cloud SQL/Aiven) — TCO managed обычно ниже с учётом on-call burden. Свой operator — только когда domain logic уникальна (healthcare compliance, multi-tenant изоляция).
Prometheus, ServiceMonitor, PodMonitor, AlertmanagerConfig — конфигурация мониторинга как k8s resources.:latest tags, обязательные labels).resources.requests сильно меньше реального usage. HPA думает 100% = 10m -> любая нагрузка триггерит scale-up до hundreds pods, scheduler bin-packs pods слишком плотно -> noisy neighbor, OOM kills. Always set requests = realistic baseline.resources.limits на memory. Pod с memory leak забивает host, kernel OOM killer убивает случайные pods на ноде. Always set memory limit (CPU limit — спорный, throttling часто хуже чем burstable).coordination.k8s.io/v1 Lease.livenessProbe без readinessProbe. Liveness рестартит pod если probe fails. Readiness исключает из Service Endpoints. Без readiness — Service шлёт трафик в неготовый pod (503 errors). Без liveness — мёртвый pod не рестартится.livenessProbe слишком агрессивная. initialDelaySeconds=5 + slow Java startup -> kubelet убивает pod до того как JVM прогрелся -> CrashLoopBackOff навсегда. Использовать startupProbe для slow-starting apps.behavior.scaleDown.stabilizationWindowSeconds: 300.shareProcessNamespace: true когда нужен. Если sidecar должен видеть процессы app (для tracing, profiling) — нужен shared PID namespace. Иначе sidecar видит только свой PID 1.