Желаемое состояние веб-приложения в Kubernetes
Веб-разработчик обычно начинает с одного процесса: запускает сервер, открывает страницу и видит ответ API. Когда экземпляров становится несколько, появляются другие вопросы. Кто восстановит процесс после остановки, как направить запрос к готовой копии, где хранить настройки и как заменить версию без случайного исчезновения всего сайта? Kubernetes даёт средства описать такие отношения. Однако сначала полезно отделить его задачи от устройства самого приложения.
В серии рассмотрим самостоятельный проект «Площадка»: маленький каталог уроков, собственный HTTP API и собственный frontend. Вам нужны основы HTTP, JavaScript и понимание образа контейнера. Код лежит в examples/kubernetes-web-lab репозитория. Это локальная инструкция: автор не собирал образы, не создавал кластер и не выполнял команды. Все наблюдения далее обозначают ожидаемое поведение будущей лаборатории.
Приложение, образ и экземпляр
API будет возвращать две учебные записи и имя обслужившего запрос экземпляра. Frontend покажет записи в браузере, а также версию ответа. Эти признаки выбраны не ради оформления. Имя экземпляра поможет увидеть, что HTTP-запросы обслуживает не абстрактное «приложение», а конкретный процесс. Версия позволит отличить замену кода от обычного перезапуска той же программы.
Исходный код описывает поведение сервера. Образ упаковывает код и среду исполнения. Контейнер запускает процесс из образа. Образ можно использовать многократно: две копии API не требуют двух наборов исходников. При этом память запущенных процессов раздельная. Если первый процесс изменит массив в своей памяти, второй об этом не узнает. В нашей «Площадке» каталог неизменяемый, поэтому такое разделение пока не создаёт конфликта.
Kubernetes группирует контейнеры в Pod. Для первого примера один Pod содержит один контейнер API. Pod — единица размещения и жизненного цикла, а не адрес приложения навсегда. Когда понадобится новая копия, у неё будут другие имя, идентификатор и сетевой адрес. Поэтому браузер и соседний сервер не должны запоминать конкретный Pod как постоянную точку доступа. Общая модель описана в документации Pods.
Представим сбой: процесс API завершился. Перезапуск контейнера внутри существующего Pod и создание нового Pod — разные события. В первом случае объект ещё существует; во втором Kubernetes нужен управляющий объект, который объясняет, сколько копий следует иметь. Одиночный Pod удобен для знакомства с механизмом, но не выражает требование «всегда поддерживать две копии». Для этого далее введём Deployment.
Декларация вместо списка ручных действий
Желаемое состояние — описание того, к чему система должна стремиться. Запись «две копии API версии 1» не означает «выполнить запуск ровно два раза и забыть». Контроллер сравнивает наблюдаемую ситуацию с декларацией. Если копий меньше, он создаёт недостающую. Если больше, уменьшает количество. Разработчик задаёт намерение; управление жизненным циклом продолжается после завершения команды отправки манифеста.
Упрощённое описание нашей будущей системы выглядит так:
Deployment api: две копии образа ploshchadka-api:1
Deployment web: одна копия образа ploshchadka-web:1
Service api: стабильное имя для готовых копий API
Service web: точка доступа к frontend внутри кластера
Браузер: localhost -> временный port-forward -> web -> api
Это схема, а не готовый манифест. Здесь ещё нет полей API, меток, портов и namespace. Зато видны отношения: web обращается к Service api, а Service находит подходящие Pod. Если конкретный Pod заменился, исходный код web не переписывается. Такая связь особенно полезна при обновлении, когда одновременно существуют старые и новые экземпляры.
Контроллер не проверяет смысл ответа приложения. Сервер может успешно слушать порт и возвращать неверные данные. Kubernetes также не исправит неправильный SQL, отсутствующую бизнес-проверку или неудачно спроектированную транзакцию. Поэтому здоровье процесса, готовность принимать трафик и корректность пользовательского результата мы будем обсуждать раздельно. Декларативное управление сокращает ручные операции, но не заменяет проектирование веб-системы.
Обратите внимание на время. Отправка желаемого состояния и достижение этого состояния не происходят одновременно. Образ нужно получить, Pod разместить, контейнер запустить. Веб-приложению может понадобиться инициализация. Сообщение об успешном создании объекта означает, что API принял декларацию, а не что страница уже отвечает. В будущих уроках будем смотреть условия и события, чтобы отличать эти этапы.
Граница локальной лаборатории
Для практики используем kind: Kubernetes-узел работает внутри Docker-контейнера на вашем компьютере. В официальном релизе kind 0.33.0 опубликован образ узла Kubernetes 1.37.0; в kind.yaml он закреплён digest. Это подтверждённая комбинация учебного окружения, а не утверждение о самом новом patch-релизе. Список образов находится в релизе kind.
Один локальный узел подходит для изучения деклараций, DNS, проб и смены версии. Он не демонстрирует устойчивость к потере физического компьютера. Даже если запустить три Pod, все они останутся зависимы от одного ноутбука, Docker и локального диска. Когда говорим «копия восстановилась», речь будет о заданной учебной ситуации, а не о доказанной доступности производственного сервиса.
Внешний домен и платный балансировщик не нужны. Первую страницу откроем через kubectl port-forward, который временно связывает локальный порт с приложением в кластере. Такой доступ существует, пока работает команда. Он удобен для обучения и диагностики, но не является способом публикации сайта. Для реального внешнего трафика потребуется отдельный контроллер и договорённости о DNS, TLS и доступе.
Контекст команд назовём kind-ploshchadka, namespace — ploshchadka. Контекст выбирает кластер и учётные данные, namespace определяет область имён некоторых объектов. Эти признаки помогают адресовать действия, но сами по себе не являются полной системой изоляции. Мы будем указывать их явно, чтобы примеры не зависели от случайного текущего окружения терминала.
Как оценивать результат первого этапа
После этого урока ещё не требуется работающий кластер. Результат — согласованная модель: API и web собираются из собственного кода, копии API взаимозаменяемы для чтения каталога, постоянный доступ создаётся Service, а хранение пользовательских данных пока отсутствует. Такая модель позволяет выбирать манифесты по задаче, а не копировать неизвестный набор YAML.
Попробуйте мысленно заменить одну копию API. Какая часть должна измениться? Меняются конкретный Pod и процесс; имя Service api сохраняется. Теперь замените содержимое каталога в исходниках. Потребуется новый образ и обновление Deployment, потому что изменение файла на компьютере автора не переписывает уже упакованный код. Эти два случая относятся к разным слоям системы.
Ещё один полезный вопрос касается ответственности. API отвечает за HTTP-контракт, web — за интерфейс и проксирование, Deployment — за количество экземпляров, Service — за обнаружение адресатов. Если страница отдаётся, но каталог недоступен, бессмысленно сразу увеличивать число web. Сначала нужно установить, на каком переходе цепочки исчезает ожидаемый результат. Эта привычка пригодится во всех следующих уроках.
Продолжим с полными исходниками приложения и сборкой собственных образов. Там появится конкретный HTTP-контракт, на который затем будут опираться порты, маршруты и проверки готовности.