20 апреля 2024
Перед переходом на Kubernetes стоит определить, какие задачи не решает текущий способ управления контейнерами и как изменится работа с инфраструктурой. Compose описывает запуск процессов на одной машине: вы императивно говорите «запусти эти контейнеры с этими портами и этими volume». Kubernetes описывает желаемое состояние кластера и поручает control plane непрерывно его поддерживать. Это разные модели мышления, и переход между ними - не апгрейд инструмента, а смена контракта между разработчиком и инфраструктурой. Статья разбирает, когда этот контракт становится необходим, откуда взялись его примитивы, на чём ломаются типичные миграции и какие реальные истории стоит читать перед тем, как принимать решение.
Kubernetes поддерживает желаемое состояние объектов через контроллеры. Он не задает универсальный порог численности команды для перехода на кластер. Rollback Deployment возвращает ревизию шаблона Pod, но не откатывает автоматически внешние изменения, например миграции базы данных.
Когда Compose становится узким местом
У Compose есть один молчаливый контракт с пользователем: всё, что вы описали, живёт на одной машине, и если эта машина падает, падает и сервис. Пока команда маленькая, продукт переживает короткие окна downtime, а нагрузка укладывается в один сервер с запасом, этот контракт не мешает - наоборот, он экономит обучение, инфраструктуру и операционную сложность. Compose был придуман Docker Inc. как локальный orchestrator для разработчика, и в этой роли он по-прежнему один из лучших инструментов, которые отрасль придумала за десятилетие.
Узкое место появляется не там, где «нагрузка выросла», а там, где меняется тип риска. Пока сервис лежит в одном процессе на одном хосте, риск управляется людьми: дежурный получает алерт, заходит по SSH, перезапускает контейнер, разбирается утром. Когда продукт начинает обслуживать клиентов, у которых SLA измеряется в девятках, или когда ночной перезапуск означает потерянную выручку и обиженных пользователей, риск перестаёт быть управляем людьми по скорости реакции. Нужна машина, которая умеет за миллисекунды детектировать падение и поднимать новый экземпляр сама. Это и есть водораздел между Compose и оркестратором: автоматизированное восстановление желаемого состояния, а не ручное.
Второй типичный триггер - рост числа окружений. Пока есть «прод и мой ноутбук», docker-compose.yml в репозитории закрывает оба случая. Когда появляются dev, staging, demo, prod-eu, prod-us, набор YAML-файлов начинает расходиться: где-то RAM меньше, где-то другой API ключ, где-то Postgres внешний, где-то контейнером. Compose не умеет аккуратно параметризовать конфигурацию по окружениям; начинаются хаки с .env, override-файлами, симлинками. Каждый такой хак работает, но усложняет онбординг и увеличивает шанс «у нас на staging работает, на prod нет». Kubernetes решает эту проблему за счёт ConfigMap, Secret и Helm, но решает её только как побочный эффект общей идеи декларативной конфигурации.
Третий триггер - команда. Пока DevOps делает один человек, который держит в голове, на каком сервере что крутится, Compose работает как расширение этой памяти. Когда команда растёт до точки, где нужен RBAC, audit log, namespaces для изоляции по проектам, разделение прав между разработчиками и SRE - Compose упирается в потолок не функциональности, а коллаборативной работы. У него нет встроенного понятия «у этого человека права только на namespace dev».
Kelsey Hightower, один из самых известных голосов в community Kubernetes и автор «Kubernetes the Hard Way», много лет публично повторяет ту же мысль: Kubernetes - это infrastructure platform, не silver bullet. Большинство компаний приходят к нему слишком рано, тратят 6-12 месяцев на освоение и обнаруживают, что их реальная проблема - в архитектуре приложения, а не в способе его запуска. Его формулировка - «No, you don't need Kubernetes» - стала почти мемом, но за ней стоит инженерный аргумент: если ваше приложение не пишется как stateless горизонтально-масштабируемое, оркестратор не превратит его в такое.
История оркестрации: Borg, Swarm, Mesos и почему победил Kubernetes
Kubernetes возник не на пустом месте. Его прямой предок - Borg, внутренняя система Google, на которой с начала 2000-х запускаются практически все production сервисы компании - от поиска до Gmail. В 2015 году Verma, Pedrosa, Korupolu, Oppenheimer, Tune и Wilkes опубликовали на EuroSys статью «Large-scale cluster management at Google with Borg», где впервые подробно описали, как устроена эта система: единый пул машин, declarative job specifications, автоматический scheduler, который размещает workloads с учётом ресурсов, приоритетов и affinity. Большинство концепций, которые сегодня кажутся естественными в Kubernetes - Pod как группа co-located контейнеров, declarative reconciliation, labels как универсальный механизм selection - пришли оттуда.
Brendan Burns - один из трёх создателей Kubernetes внутри Google (вместе с Joe Beda и Craig McLuckie). Он прямо описывает Kubernetes как «Borg-3»: попытку взять накопленный за 15 лет опыт эксплуатации Borg и переписать систему с нуля для open-source мира, исправив архитектурные решения, которые в Borg оказались болезненными. После ухода в Microsoft Burns написал книгу «Designing Distributed Systems» (O'Reilly, 2018), где формализовал паттерны, лежащие в основе Kubernetes: sidecar, ambassador, adapter, leader election, work queue. Эта книга важна не как руководство по K8s, а как объяснение, почему примитивы сделаны именно такими, и я ссылаюсь на неё дальше в тексте, когда мотивирую отдельные решения платформы.
Конкуренция за стандарт оркестрации шла в 2014-2017 годах между несколькими системами. Docker Swarm от Docker Inc. ставил на простоту и встроенность в существующий toolchain: тот же CLI, тот же compose-формат, минимальный порог входа. Apache Mesos с Marathon от Mesosphere делал ставку на масштаб и зрелость - Mesos был проверен в Twitter и Airbnb на тысячах нод. Nomad от HashiCorp предлагал лёгкость и unified workflow с Terraform и Consul. Kubernetes был самым сложным из всех, но имел три преимущества, которые в итоге оказались решающими.
Первое - Google как основатель и постоянный донор кода. Это означало не только инженерную репутацию, но и долгосрочное обязательство: компания, у которой Borg крутится в production 15 лет, понимает, что нужно cluster manager-у. Второе - передача проекта в Cloud Native Computing Foundation в 2015 году, что сняло страх vendor lock-in: Kubernetes перестал быть «продуктом Google» и стал нейтральной платформой, в которую готовы были вкладываться Red Hat, IBM, AWS, Microsoft. Третье - открытая модель расширения через CRD и operators, благодаря которой вокруг ядра вырос целый ecosystem (Helm, Istio, ArgoCD, Prometheus operator, cert-manager), который сами эти компании поддерживать бы не смогли.
К 2018 году конкуренция в основном закончилась. Docker Inc. публично перестала позиционировать Swarm как production-альтернативу и встроила Kubernetes в Docker Desktop. Mesos постепенно уступил позиции, и в 2021 году Apache Mesos перешёл в Attic. CNCF Annual Survey 2023 фиксирует Kubernetes в production у 84% респондентов (см. ссылку в разделе «Источники»), и большинство production-кластеров сегодня живут не на self-hosted control plane, а на managed-сервисах (EKS, GKE, AKS). Это не значит, что K8s - правильный выбор для всех; это значит, что ecosystem, документация, экспертиза и tooling сконцентрированы вокруг него и любая альтернатива требует от вас плыть против течения.
Полезный исторический урок: победил не самый простой и не самый быстрый orchestrator, а тот, у которого оказалась самая расширяемая архитектура и самая широкая коалиция вендоров. Это типичный паттерн для платформенных стандартов - тот же, что сделал в своё время Linux дефолтом для серверов, а не более простую BSD или более изящный Plan 9.
Архитектурный паттерн: объявление желаемого состояния vs. императивные команды
Самая важная идея Kubernetes - не контейнеры и не масштабирование, а declarative reconciliation. В Compose вы говорите системе, что сделать: «запусти этот контейнер», «останови тот», «пересобери образ». В Kubernetes вы говорите системе, как должно выглядеть состояние мира: «здесь всегда должно работать 3 реплики этого Deployment, привязанные к Service my-app, доступные снаружи через Ingress на my-app.example.com». Дальше control plane непрерывно сравнивает реальное состояние кластера с желаемым и принимает действия, чтобы их свести.
Этот паттерн называется reconciliation loop, и он повторяется на всех уровнях Kubernetes. Deployment controller следит, чтобы желаемое количество ReplicaSet было таким, как описано. ReplicaSet controller следит, чтобы желаемое количество Pod-ов работало. kubelet на каждой ноде следит, чтобы реальное состояние контейнеров на хосте соответствовало тому, что control plane назначил этой ноде. Если на любом уровне реальность расходится с желанием - падает Pod, перегружается нода, истекает образ - соответствующий controller принимает действия. Это и есть «self-healing»: не магическое свойство, а следствие архитектурного решения отделить декларацию состояния от его поддержания.
Контраст с Compose нагляднее всего виден на сценарии падения. Если в Compose контейнер упал из-за OOM, Compose может его перезапустить (если настроен restart: always), но не более того. Если упал хост - сервис лежит до тех пор, пока человек не зайдёт и не починит. Если кто-то вручную остановил контейнер - он остановлен, и compose-файл это никак не оспорит. В Kubernetes это работает иначе: kubelet увидит, что Pod не запущен, и попробует поднять его снова. Если нода отвалилась - scheduler перенесёт Pod на здоровую ноду. Если кто-то вручную удалил Pod - ReplicaSet controller заметит расхождение с replicas: 3 и создаст новый Pod взамен удалённого. Состояние оказывается самовосстанавливающимся - его не нужно поддерживать вручную, оно само возвращается к описанному в манифесте.
Цена этой модели - сложность mental model. В Compose вы знаете, какие команды вы выполнили и в каком порядке. В Kubernetes вы знаете, какое состояние вы описали, но не контролируете, в какой момент и как именно reconciler примет действия. Отсюда классическое замешательство новичков: «я применил манифест, но Pod ещё не запустился, что не так?» - чаще всего ничего, просто scheduler ещё не нашёл подходящую ноду или image pull занимает 30 секунд. Чтобы продуктивно работать с Kubernetes, нужно отказаться от ментальной модели «я отдаю команды» и принять модель «я описываю инвариант, а система его поддерживает».
Примитивы как словарь: Pod, Deployment, Service, Ingress
Когда документация Kubernetes говорит «Pod - это группа из одного или нескольких контейнеров с общим network namespace и общими volumes», она описывает реализацию. Полезнее смотреть на примитивы как на словарь, которым команда описывает контракт между приложением и платформой. Каждый примитив отвечает на конкретный вопрос: кто запускается, в каком количестве, как доступен внутри кластера, как доступен снаружи, как конфигурируется, как мониторится. Знать примитивы - значит знать вопросы, которые платформа за вас не решает и которые приходится решать самостоятельно.
Ниже - сопоставление примитивов с теми задачами, которые они решают. Таблица служит словарём; в реальном дизайне последовательность обычно обратная - сначала возникает вопрос, потом подбирается примитив.
| Вопрос | Примитив | Что именно делает |
|---|---|---|
| Что вообще запускается? | Pod | Минимальная единица планирования: группа контейнеров с общим network и volume. |
| Сколько копий и как обновлять? | Deployment | Декларация количества Pod-ов и стратегии rolling update / rollback. |
| Как другие сервисы внутри кластера находят это приложение? | Service | Стабильный DNS-имя и cluster IP; load balancing по живым Pod-ам. |
| Как пользователь снаружи попадает в приложение? | Ingress | Маршрутизация HTTP по hostname/path + TLS termination. |
| Где живут конфиги и секреты? | ConfigMap / Secret | Декларативно описанные key-value, монтируемые в Pod как env или файлы. |
| Как сохраняется состояние? | PersistentVolumeClaim | Запрос на storage с заданным размером и StorageClass. |
| Как платформа понимает, что Pod жив и готов к трафику? | Liveness / Readiness probes | HTTP/TCP/exec проверки, на основе которых kubelet перезапускает или скрывает Pod. |
| Как масштабироваться под нагрузку? | HorizontalPodAutoscaler | Изменение количества реплик Deployment по метрикам CPU, памяти или custom metrics. |
| Как изолировать команды и окружения внутри одного кластера? | Namespace + RBAC | Логическое разделение объектов и контроль доступа по ролям. |
Pod как совместная единица планирования - не очевидное решение. Можно было бы сделать минимальной единицей контейнер, как в Docker. Команда Borg специально пошла на введение группы: контейнеры внутри Pod гарантированно живут на одной машине, делят сеть и могут общаться через localhost, делят volumes без сетевой задержки. Это позволяет делать sidecar-pattern: основной контейнер с приложением и вспомогательный контейнер, который, например, забирает логи или обновляет TLS-сертификаты, без того чтобы встраивать эту логику в основной образ. Sidecar - это организационный механизм отделения platform-кода от product-кода в одном Pod.
Deployment как декларация версии ценен не количеством реплик, а тем, что он управляет переходом между версиями. Когда вы меняете image: my-app:v1.2 на my-app:v1.3, Deployment controller создаёт новый ReplicaSet, постепенно переводит трафик на него и убирает старый. Если новая версия не проходит readiness-проверку - rollout останавливается. Если приходит команда rollback - ReplicaSet с предыдущей версией поднимается обратно. Это и есть rolling deploy без downtime, который в Compose требует написать собственный shell-скрипт с blue-green логикой.
Service - это уровень indirection между потребителем и поставщиком. Pod-ы приходят и уходят (rolling update меняет их IP-адреса каждые несколько минут), а Service - это стабильное DNS-имя, за которым прячется текущий набор живых Pod-ов. Внутри кластера приложения обращаются к my-app.namespace.svc.cluster.local, и kube-proxy балансирует запросы по реальным IP. Это маленький, но критичный элемент: без него любое падение или передеплой Pod-а превращался бы в сетевой сбой для его клиентов.
Ingress - это L7-фронт кластера. Service по умолчанию доступен только внутри. Чтобы выпустить трафик наружу, нужен Ingress controller (типичные реализации - nginx-ingress, Traefik, HAProxy, AWS ALB), который слушает 80/443, terminate TLS, маршрутизирует по hostname или path к нужному Service. Это позволяет, например, на одном кластере держать api.example.com, app.example.com, admin.example.com - все за общим IP, разводимые по правилам Ingress. В Compose эту роль выполнял отдельно поднятый nginx с вручную написанным конфигом.
Ниже - типичный минимальный комплект манифестов для одного веб-приложения. Он не нужен для понимания, его легко найти в документации; он приведён, чтобы видеть, как примитивы складываются в работающую конфигурацию.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:v1.3.0
ports:
- containerPort: 8080
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
readinessProbe:
httpGet: { path: /health, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet: { path: /alive, port: 8080 }
initialDelaySeconds: 30
periodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector: { app: my-app }
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
spec:
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port: { number: 80 }
Этот пример показывает важное: даже для самого простого приложения Kubernetes требует от вас явно описать probe-эндпоинты, ресурсные лимиты, селекторы и стратегию доступа. В Compose всё это либо отсутствует, либо принимается по умолчанию. K8s превращает неявные предположения в явные декларации - это и сила, и стоимость входа.
Реальные случаи миграции: Monzo, Shopify, GitHub
Абстрактные аргументы за и против Kubernetes стоят меньше, чем разобранные истории компаний, которые прошли путь миграции и публично об этом написали. Ниже - три кейса с разным контекстом и разными уроками. Источники приведены в разделе «Источники»; если факт указан без ссылки, это стилизация на основе типичных паттернов.
Monzo - британский онлайн-банк, известный в инженерном сообществе радикальной приверженностью микросервисам. К 2019 году у них было около 1500 микросервисов на одном Kubernetes-кластере, и инженерный блог банка регулярно публиковал детальные посты о том, как эта архитектура устроена и где она ломается. Самый разобранный материал - пост «The Anatomy of an Incident: 29th July 2019» (monzo.com) о сбое, при котором плановое обновление Cassandra (rolling upgrade) совпало с особенностью драйвера, который при частичном состоянии кластера начал генерировать ошибки в одном из ключевых сервисов; платёжная функциональность лежала несколько часов. Урок Monzo не «Kubernetes виноват», а противоположный - оркестрация позволила им управлять огромным количеством мелких сервисов, которое в монолитной архитектуре было бы невозможно. Но та же архитектура повышает blast radius любого misconfiguration в shared infrastructure: когда у вас 1500 сервисов смотрят на одну Cassandra и одну сеть, проблема в этом слое затрагивает всех сразу.
Shopify мигрировал свою основную платформу на Kubernetes начиная с 2017 года. У них уникальная инженерная сложность: один из крупнейших Rails-монолитов в мире (внутреннее имя Shopify Core) обслуживает миллионы магазинов, и спайки трафика на Black Friday / Cyber Monday кратно превосходят обычную нагрузку. Их проектная команда подробно описывала путь в публикациях на shopify.engineering и в выступлениях на KubeCon. Ключевой урок Shopify - инвестиция в внутреннюю платформу. Они не просто переехали на K8s, а построили поверх него Cloud Buildpacks, ChatOps tooling, собственные CRD для описания «приложения» в Shopify-смысле. Без этого слоя команды разработчиков утонули бы в YAML и не могли бы быстро деплоить. Это типовой паттерн: для большой организации Kubernetes сам по себе - не платформа, а сырьё, из которого строится платформа. Источник: https://shopify.engineering/.
GitHub MySQL outage 21 октября 2018 года - кейс про обратную сторону любой автоматики над распределённым состоянием. У GitHub 43-секундный network partition между восточным и западным дата-центрами разорвал связь между MySQL-кластерами и автоматикой топологии (Orchestrator). На обеих половинах Orchestrator независимо переизбрал собственный primary; после восстановления связи обе стороны успели принять записи в свои новые primary, и кластер потребовал ручной реконсиляции, чтобы решить, чьи данные авторитетны. Сервис восстановился полностью только через 24 часа. Технический post-mortem (github.blog/2018-10-30-oct21-post-incident-analysis) подробно разбирает, как именно произошёл split-brain. Это не история про конкретный продукт, а про класс проблем: любая распределённая control plane (что MySQL Orchestrator, что etcd Kubernetes) имеет режимы отказа, в которых автоматическое восстановление невозможно или опасно. Kubernetes наследует этот класс: если у вас падает etcd, никакие декларации в манифестах не помогут, control plane остаётся без памяти. Поэтому управляемые сервисы (EKS, GKE, AKS), которые берут на себя эксплуатацию control plane и его восстановление, для большинства команд оказываются разумным компромиссом - вы платите за то, чтобы не разбираться лично с тем, как восстанавливать кворум после сетевого разрыва.
Полезно сравнить три истории. Monzo показывает, что Kubernetes масштабирует количество сервисов; Shopify - что для этого нужна вторичная платформа поверх него; GitHub - что само существование control plane создаёт новый класс рисков, к которым команда должна быть готова. Все три компании остались на Kubernetes - это валидный выбор - но ни одна из них не утверждает, что путь был дешёвым.
Стилизованный пример контекста, где переход не оправдался: small SaaS, 5 разработчиков, один Rails-монолит, два-три фоновых воркера, Postgres, Redis. Команда переехала с Compose на managed EKS «потому что так делают взрослые». За первые полгода - три инцидента, связанных с misconfigured liveness probes (приложение убивалось во время legitimate долгих запросов), один сетевой сбой из-за неправильно настроенного Calico, один эпизод с заблокированным rollback из-за конфликта в Helm values. Time-to-resolve вырос, потому что в команде нет K8s-эксперта. Через год вернулись на Compose + systemd на двух больших серверах. Это стилизованный, а не задокументированный кейс, но шаблон такой миграции в обратную сторону хорошо знаком тем, кто работает с командами уровня Series-A: цена K8s оплачивается ростом организации, и если этого роста нет, инвестиция не возвращается.
Антипаттерны при переходе
Большинство неудачных миграций на Kubernetes ломаются не на этапе «не получилось установить кластер», а на этапе «работает, но не так, как мы ожидали». Ниже - типичные failure modes; каждый из них стоит пройти как чеклист перед production-запуском.
Игнорирование multi-container паттернов. Один контейнер на Pod - нормальный default; проблема возникает, когда команда так и не приходит к sidecar/ambassador/init-container, даже там, где они снимают реальную боль: sidecar для сбора логов, ambassador для прокси к внешнему сервису, init-container для миграций. В результате вся вспомогательная логика встраивается в основной образ, что увеличивает его размер, связывает его с инфраструктурой и делает повторное использование невозможным. Burns специально подчёркивает: Pod как multi-container - это организационный примитив, и его игнорирование обнуляет одну из ключевых возможностей платформы.
Misuse Namespace. Команды часто используют namespace как «папку для группировки» - все сервисы продакшна в namespace prod, все сервисы стейджинга в namespace staging. Это работает, пока компания маленькая, но ломается на двух сценариях: во-первых, вы не можете разделить квоты по командам; во-вторых, RBAC становится очень грубым - либо у разработчика есть доступ ко всему prod, либо нет. Правильное использование namespace - это границы команд и tenants, а окружения разделять кластерами или, по крайней мере, явно проектировать NetworkPolicy между ними. Иначе любая ошибка в квоте одной команды превращается в инцидент для всех её соседей.
Отсутствие resource requests и limits. Это, пожалуй, самая частая причина «странных» инцидентов в молодых K8s-кластерах. Без requests scheduler не знает, сколько ресурсов нужно Pod-у, и может разместить десять «голодных» Pod-ов на одной ноде, перегрузив её. Без limits один Pod, у которого утёкла память, съедает всю доступную память ноды, и kubelet начинает по OOM убивать сторонние Pod-ы, выбирая их по эвристике. Внешне это выглядит как «у нас падают сервисы, которые мы не трогали». Решение тривиальное - всегда задавать requests и limits для CPU и памяти - но без него платформа работает как лотерея.
Пропущенные или неправильно настроенные probes. Liveness probe говорит kubelet, когда перезапустить контейнер; readiness probe - когда направлять на него трафик. Без readiness probe Service сразу после rollout направит трафик на Pod, который ещё не прогрел кэш, не открыл соединения к БД, не загрузил конфигурацию - пользователи получат 502. Без liveness probe зависший Pod (deadlock в потоках, забитый event loop) будет годами числиться живым, не обслуживая запросы. С чрезмерно агрессивной liveness probe платформа будет убивать здоровый Pod во время legitimate долгих операций - типичная история «у нас по понедельникам падает batch job, потому что probe не учитывает, что миграция идёт 5 минут». Probes - это контракт между приложением и платформой, и его нужно проектировать осознанно, а не копировать примеры из туториалов.
Stateful workloads без StatefulSet и без понимания storage. Соблазн «давайте просто запустим Postgres в Deployment с PVC» очень велик: технически работает, в туториалах есть. Проблема в том, что Deployment - это набор взаимозаменяемых Pod-ов, а база данных взаимозаменяемой не является. При rolling update Deployment может убить master до того, как replica догонит binlog; PVC может быть привязан к ноде, на которой Pod больше не запустится; backup-стратегия живёт в голове разработчика, а не в манифестах. Для stateful workloads существует StatefulSet с гарантиями ordered deployment и stable network identity, и для большинства случаев managed-сервис (RDS, Cloud SQL) обходится дешевле, чем самостоятельная эксплуатация базы в кластере.
Секреты в git. Соблазн закоммитить Secret YAML в репозиторий вместе с остальными манифестами - постоянный. Kubernetes Secret хранится в etcd в base64 (не зашифрованном!), и git-репозитории, особенно публичные, регулярно сканируются ботами на утечки. Правильные подходы - Sealed Secrets от Bitnami (asymmetric encryption, расшифровывается только в кластере), External Secrets Operator (Secret тянется из Vault/AWS Secrets Manager в момент применения), SOPS (encrypted files, расшифровываются ключом). Любой из них работает; никакой - не работает, и команда платит за это утекшими credentials.
Реорг-театр на платформу. Менее технический, но не менее важный антипаттерн: компания решает «мы переходим на Kubernetes», создаёт DevOps Transformation Team, рисует roadmap на год, но не меняет ни ответственности команд, ни on-call процессов, ни инвестиций в обучение. Через год кластер существует, но используется одной-двумя командами; остальные продолжают деплоить через старые процессы. Внешне выглядит как успешная миграция (есть CI/CD на K8s, есть production-кластер), фактически - параллельная инфраструктура с двойными costs. Это аналог «менять только Structure» из системного менеджмента - реорг без согласования processes, rewards и people.
Рамка решения: переходить или нет
Любая миграция на Kubernetes - это инвестиция, которая возвращается только при определённом размере организации и определённых требованиях к доступности. Полезно явно сформулировать, при каких условиях эта инвестиция оправдана, а при каких - нет. Ниже - набор практических вопросов, на которые стоит честно ответить до того, как выделять бюджет на проект миграции.
Сколько стоит downtime? Если 30 минут простоя в неделю обходятся в 100 евро упущенной выручки - K8s не окупится. Если те же 30 минут - это пропущенный платёжный шлюз банка с штрафами от регулятора - окупится первой же предотвращённой ночной возней с runbook. Граница не в проценте uptime, а в долларах разницы между ручным и автоматическим восстановлением.
Есть ли у команды экспертиза или бюджет на её приобретение? Kubernetes - сложная экосистема, и человек, способный диагностировать проблему в kube-proxy или etcd, на рынке стоит дорого. Если в команде такого человека нет и нанимать вы не готовы - либо берите managed Kubernetes (EKS, GKE, AKS) и принципиально не лезьте в control plane, либо оставайтесь на Compose. Self-hosted K8s без экспертизы - надёжный способ оплатить первый production-инцидент собственным репутационным ущербом.
Действительно ли вам нужно горизонтальное масштабирование? Современные виртуальные машины легко берут 64-128 vCPU и сотни ГБ памяти. Большинство SaaS-приложений до выручки в десятки миллионов долларов умещаются в один такой узел с запасом, и горизонтальное масштабирование им не нужно вообще - им нужен честный health check и autoreboot, который умеют systemd и Compose со restart: always. Если ваш план роста - x2 за год, а не x100, цена K8s превышает выгоду.
Есть ли у вас несколько окружений с расходящейся конфигурацией? Если конфигурация одинакова или почти одинакова, Compose с .env закрывает задачу. Если у вас 5+ окружений с разной топологией (другие зависимости, другие квоты, другие региональные правила compliance) - конфигурационный аппарат K8s начинает экономить время, особенно вместе с Helm или Kustomize.
Если на большинство вопросов выше ответ «нет» - оставайтесь на Compose. Это не «недостаточно зрелое решение», это разумная экономия. Если на большинство ответ «да» - начинайте с managed K8s (EKS/GKE/AKS), берите внешнюю консультацию или нанимайте человека с production-опытом, инвестируйте в платформу до того, как продуктовые команды начнут массово деплоить, и обязательно описывайте Team API для платформы (см. Системный менеджмент) - иначе платформа превратится в очередной silo.
- Service Mesh - следующий уровень: Istio, Linkerd, mTLS и observability на уровне сетевого взаимодействия.
- Монолит vs Микросервисы - архитектурный вопрос, который должен быть решён до выбора оркестратора.
- Системный менеджмент - почему платформенная команда без Team API превращается в silo.
Источники и смежные статьи
Первоисточники и книги
- Verma, Pedrosa, Korupolu, Oppenheimer, Tune, Wilkes. Large-scale cluster management at Google with Borg (EuroSys 2015). Прародитель архитектуры Kubernetes.
- Brendan Burns, Joe Beda, Kelsey Hightower. Kubernetes: Up and Running (3rd edition, O'Reilly, 2022). Стандартное введение в работу с K8s от трёх ключевых создателей.
- Brendan Burns. Designing Distributed Systems: Patterns and Paradigms for Scalable, Reliable Services (O'Reilly, 2018). Объясняет, почему примитивы K8s сделаны так, а не иначе - sidecar, ambassador, adapter, leader election, work queue.
- Kelsey Hightower. Kubernetes the Hard Way. Пошаговая сборка кластера вручную - лучший способ понять, из чего состоит control plane.
- Liz Rice. Container Security (O'Reilly, 2020). Изоляция, namespaces, cgroups - без этого фундамента многие инциденты K8s выглядят магией.
Реальные кейсы и инциденты
- GitHub - October 21 post-incident analysis. Подробный разбор 24-часового инцидента с etcd и MySQL replication.
- Monzo Engineering Blog. Регулярные посты про микросервисы на K8s, post-mortems, эволюция платформы.
- Shopify Engineering. Публикации про миграцию Rails-монолита на K8s, Cloud Buildpacks, Black Friday scaling.
- CNCF Annual Survey 2023. Статистика adoption Kubernetes, managed-сервисов, popular extensions.
Документация и глубокое погружение
- Kubernetes documentation. Официальный source of truth.
- Helm documentation. Пакетный менеджер; де-факто стандарт для шаблонизации манифестов.
- Kustomize. Альтернатива Helm для overlay-конфигурации; встроена в kubectl.
- Argo CD. GitOps-инструмент; следующий шаг после ручного kubectl apply.
- Kubernetes: Objects
- Kubernetes: Deployments