School

← To the program

Зачем Kubernetes и когда он не нужен

Lesson from the course “Курс подготовки DevOps-инженеров”

Хронометраж: занятие 60 минут — теория 30 минут, практика 30 минут. Домашняя работа: 1–2 часа.

Кратко о чём урок: Какие задачи решает оркестратор, цена его внедрения и эксплуатации, честное сравнение с Compose/systemd на нескольких серверах, критерии осознанного выбора.


1. Что изучаем

После урока вы сможете:

  • называть числом, во что обходится сам кластер — память, процессы, время развёртывания — и учитывать это при выборе;
  • показывать на опыте, какие отказы Compose переживает не хуже оркестратора, а какие не переживает вовсе;
  • объяснять, что Kubernetes делает с упавшим узлом и за какое время — то есть чем «самолечение» отличается от мгновенного восстановления;
  • формулировать условия, при которых оркестратор в задаче не нужен, и защищать это решение доводами, а не вкусом;
  • различать цену внедрения и цену эксплуатации: это разные счета, и второй больше.

2. Зачем это нужно и где применяется

К этому месту курса вы умеете многое из того, ради чего люди обычно берут Kubernetes. Блок 07 дал контейнеры и Compose, блок 09 — доставку изменений пайплайном, блок 10 — приведение серверов к описанному состоянию. Приложение из блока 07 уже разворачивается одной командой и переживает перезапуск.

Поэтому вопрос урока не «как устроен Kubernetes» — это блоки 13.02 и дальше. Вопрос практический: что именно добавляет оркестратор к тому, что у вас уже есть, и сколько это стоит. Отвечать на него приходится в рабочих ситуациях:

  • «нам нужен Kubernetes, у всех он есть» — а сервисов три, и все живут на одной машине;
  • «переезжаем в кластер, чтобы было отказоустойчиво» — при том что база остаётся единственной на отдельном сервере, и именно она упадёт;
  • «у нас Compose на пяти серверах, и мы устали раскладывать их руками» — вот здесь оркестратор действительно отвечает на боль;
  • «команда из двух человек, дежурства нет, а кластер надо кому-то обновлять».

Неверный ответ дорого стоит в обе стороны. Взяли кластер там, где хватало одной машины, — получили лишний слой, который сам требует эксплуатации, и команду, которая теперь чинит не приложение, а инфраструктуру. Не взяли там, где нужен, — получили ручную раскладку по серверам, простои при отказе узла и выкаты, которые боятся делать в пятницу.

Весь разбор в этом уроке — измеренный. Ни одно сравнение не взято из статьи: и Compose, и кластер подняты на одном стенде, на одном и том же приложении, и проверены одинаковыми опытами. Стенд — kind на Docker с пределом 4 ГБ памяти, то есть ровно ноутбук, а не «облачный кластер из статьи».

3. Теория (30 минут)

Файлы для работы: blocks/13-kubernetes-basics/examples/13.01-why-kubernetes/

3.1. Что такое оркестратор и какую задачу он решает

Контейнер решает задачу «как запустить приложение одинаково везде». Compose решает задачу «как запустить несколько связанных контейнеров на одной машине». Оба ничего не знают о втором сервере.

Оркестратор решает третью задачу: есть несколько машин и есть описание желаемого состояния — приведи машины к этому состоянию и удерживай их в нём. Слово «удерживай» здесь главное. Вы не говорите «запусти контейнер на сервере 3»; вы говорите «этого приложения должно быть четыре экземпляра», а куда их поставить и что делать, когда сервер 3 исчез, решает система.

Это прямое продолжение линии блока 10: там мы перешли от описания действий к описанию состояния для конфигурации сервера. Kubernetes делает то же самое для запущенных приложений, и добавляет непрерывность — цикл согласования работает всё время, а не в момент запуска плейбука.

Отсюда честная формулировка, к которой сведётся весь урок: оркестратор нужен тогда, когда машин больше одной и состояние должно удерживаться без человека. Если любое из двух условий не выполняется, вы платите за механизм, которым не пользуетесь.

3.2. Сколько стоит сам кластер

Начнём с цены, потому что её обычно не называют. Поднимем пустой кластер — без единого своего приложения:

kind create cluster --name lab1201 --image kindest/node:v1.37.0

Замеры на стенде (Docker с пределом 4 ГБ, 4 ядра):

Что Однонодовый кластер Трёхузловой
время создания 45–95 с 94,1 с
память в покое 629–651 МиБ 1021 МиБ (770 + 126 + 125)
служебных подов 9 13
процессов в узле 32 —
загрузка ЦП в покое 22–85 % —

О числах этого блока. Все замеры сняты на ноутбуке: MacBook Pro 2017, двухъядерный Intel Core i5-7267U, 8 ГБ памяти, из них Docker выделено 4 ГБ и 4 логических ядра. На сервере с большим количеством вычислительных ресурсов временные показатели будут заметно меньше. Смотрите на порядок величины и на отношение между вариантами, а не на абсолютные секунды: один и тот же кластер на этой же машине создавался за 45 и за 95 секунд, в зависимости от того, чем она была занята. Что воспроизведётся у вас точно — состав компонентов, число служебных подов, последовательность событий при отказе и сообщения об ошибках. Подробнее — в README стенда блока.

Девять подов на пустом кластере — это etcd, kube-apiserver, kube-controller-manager, kube-scheduler, kube-proxy, сетевой модуль, два экземпляра coredns и поставщик хранилища. Каждый из них вам придётся эксплуатировать: обновлять, чинить, читать его журналы при разборе аварии. Устройство этих компонентов — тема урока 13.02.

Обратите внимание на последнюю строку. Пустой кластер не простаивает: загрузка ЦП в покое скачет от 22 до 85 % одного ядра, хотя в кластере нет ни одного вашего контейнера. Это и есть цикл согласования: контроллеры непрерывно сравнивают желаемое состояние с фактическим. Свойство, за которое вы платите, работает всегда — в том числе когда менять нечего.

Для сравнения: то же приложение под Compose на той же машине занимает столько, сколько занимает сам процесс, — около 20 МиБ. Разница не в разы, а на порядки, и она вся уходит на управление.

3.3. Что Compose умеет не хуже

Теперь честный опыт. Одно и то же приложение — файлы compose/compose.yaml и k8s/deployment.yaml — и одинаковая авария: приложение падает само (у учебного сервиса для этого есть маршрут /crash, см. app/app.py).

Среда Сервис вернулся через (первое падение)
Compose, restart: unless-stopped 2,0–3,3 с (четыре прогона)
Kubernetes, Deployment 1,2–2,9 с (три прогона)

Числа сопоставимы, и это важный результат: на падении процесса оркестратор не даёт ничего, чего не даёт политика перезапуска Docker. Если ваш довод в пользу Kubernetes звучит как «он перезапустит упавшее приложение» — довода нет, перезапустит и Compose, причём дешевле.

А на повторных падениях результат меняется на противоположный, и это стоит знать заранее:

Падение подряд Compose Kubernetes
первое 3,3 с 2,5 с
второе 2,1 с 14,7 с
третье 2,3 с 26,2 с
четвёртое 3,0 с —

Docker задержку не наращивает, Kubernetes наращивает намеренно. kubelet увеличивает паузу перед повторным запуском контейнера, который падает снова и снова, — то самое состояние, которое в выводе kubectl get pods называется CrashLoopBackOff. Логика понятна: бесконечно перезапускать заведомо сломанное приложение значит жечь ресурсы кластера, — но выглядит это как «Kubernetes восстанавливается медленнее».

Что задержка привязана к контейнеру, а не к приложению вообще, проверено контрольным опытом: у пода с тремя перезапусками окно недоступности 26,2 с, а у только что созданного пода первое падение — 2,9 с. Отсюда практический приём отладки, к которому вернёмся в 13.14: под, застрявший в CrashLoopBackOff, имеет смысл пересоздать, а не ждать.

Две ловушки, на которые легко наступить, проверяя это самостоятельно:

  • docker kill не приводит к перезапуску, хотя политика задана. Измерено: RestartPolicy: unless-stopped, RestartCount: 0, Exited (137) — контейнер остался лежать. Docker считает остановку через API намеренной, и политика на неё не распространяется. Проверять самолечение надо падением приложения, а не внешним убийством;
  • kill -9 1 изнутри контейнера тоже ничего не убивает. Ядро не доставляет процессу с номером 1 сигналы без обработчика, отправленные из того же пространства имён (родня находки 07.05 о том, что pid 1 не получает SIGTERM по умолчанию). Первый вариант этого опыта показал «перезапусков 0» при исправно отвечающем сервисе — то есть не проверял ничего.

3.4. Где Compose заканчивается

Разница начинается там, где исчезает не процесс, а контейнер:

Авария Compose Kubernetes
процесс упал вернулся за 2,4 с вернулся за 1,2 с
контейнер/под удалён целиком не вернулся (0 контейнеров через 12 с) новый под за 1,4 с
четыре экземпляра на фиксированном порту код 1, port is already allocated, поднялся 1 из 4 4 реплики за 2,4–3,4 с, один адрес на все

Второй случай — точная модель «сервер перезагрузили, а docker compose up никто не выполнил». Политика перезапуска здесь бессильна: она свойство контейнера, а удалённого контейнера нет. В Kubernetes желаемое состояние живёт в кластере, а не в удалённом объекте, поэтому контроллер создаёт реплику заново.

Третий случай — ловушка масштабирования из урока 08.07: docker compose up --scale web=4 при фиксированном порте хозяина падает и оставляет стенд в полусостоянии — один контейнер работает, отката нет. В Kubernetes четыре реплики получают один адрес сервиса, и запросы расходятся по ним: в двух замерах 40 запросов легли как 11 / 12 / 12 / 5 и 11 / 10 / 11 / 8. Распределение неровное (причины — в уроке 13.06), но все четыре экземпляра в работе.

3.5. Отказ узла: то, чего Compose не умеет вовсе

Это главный довод в пользу оркестратора, и он же чаще всего описывается неверно. Опыт: трёхузловой кластер (конфигурация k8s/kind-3nodes.yaml), четыре реплики, разложенные по два на каждый рабочий узел; узел выключается (docker stop lab1201-worker). Замер по секундам:

Время от отказа Что произошло
45–55 с две реплики на мёртвом узле исключены из балансировки (готовых адресов стало 2 из 4)
51–55 с узел помечен NotReady
347–358 с созданы две новые реплики на живом узле, ёмкость восстановлена

(Два независимых прогона; расхождение — шаг опроса в 10 секунд.)

Отсюда точная формулировка вместо привычной «Kubernetes мгновенно перенесёт приложение на живой узел»:

  • трафик защищается быстро — примерно за минуту мёртвые экземпляры перестают получать запросы. Это делает не планировщик, а обновление списка готовых адресов сервиса;
  • ёмкость восстанавливается медленно — около шести минут сервис работал половиной реплик. Задержка не случайна и точно объясняется двумя умолчаниями: kube-controller-manager ждёт --node-monitor-grace-period, по умолчанию 50 с, прежде чем счесть узел неработоспособным, а затем поды держатся на нём ещё 300 с — Kubernetes сам добавляет каждому поду терпимость к меткам node.kubernetes.io/not-ready и unreachable с tolerationSeconds=300. В сумме 350 с — замеры дали 352 и 358 с. Оба значения настраиваются, но по умолчанию они именно такие;
  • запас ёмкости обязателен. Если четыре реплики нагружены под завязку, то шесть минут на двух — это отказ обслуживания, просто растянутый. Оркестратор даёт время пережить отказ, а не отменяет его последствия.

Две сопутствующие находки, которые удивляют на практике:

  • поды на упавшем узле не исчезают. Они остаются с отметкой на удаление и висят в этом состоянии, пока узел мёртв: подтвердить удаление контейнера должен kubelet, а он недоступен. Отсюда картина «подов шесть вместо четырёх», сбивающая с толку при разборе аварии. После возвращения узла лишние поды убираются сами — проверено, их стало ровно 4;
  • обратно реплики не переезжают. После возвращения узла все четыре пода остались на живом узле: Kubernetes не перераспределяет уже работающие поды ради равномерности. Балансировка нагрузки по узлам — отдельная тема (14.08).

Compose в этом опыте участвовать не может в принципе: у него нет второго узла и нет понятия «узел отказал». Восстановление здесь — это человек, который зашёл на второй сервер и поднял сервис руками.

3.6. Когда оркестратор не нужен

Из измеренного выше складывается проверяемый список. Kubernetes не нужен, если:

  • машина одна. Всё, что даёт кластер сверх Compose, — это работа с несколькими узлами. На одной машине вы получаете цену управления без предмета управления;
  • состояние удерживать не требуется: внутренний сервис, который поднимают на время, задача по расписанию, стенд разработчика;
  • некому эксплуатировать. Кластер — это ещё одна система с обновлениями, сертификатами и авариями. Если в команде нет человека, который будет её обновлять, вы получите необслуживаемый кластер на устаревшей версии — это хуже, чем его отсутствие;
  • главный риск лежит не там. Единственная база данных на отдельном сервере делает бессмысленной отказоустойчивость приложения: упадёт она.

И нужен, если совпадает хотя бы часть условий: серверов несколько и раскладка по ним делается руками; требуется пережить отказ узла без участия человека; выкаты частые и должны идти без простоя; сервисов много, и им нужны общие механизмы — конфигурация, секреты, обнаружение друг друга, ограничение ресурсов.

Промежуточный ответ существует и часто оказывается верным: несколько машин с Compose плюс Ansible из блока 10. Раскладка описана кодом, состояние воспроизводимо, оркестратора нет. Это решение перестаёт работать ровно тогда, когда нужно, чтобы приложение переезжало с упавшего узла само.

3.7. Цена внедрения и цена эксплуатации — разные счета

Внедрение видно и заканчивается: поднять кластер, переписать манифесты, настроить доставку. Эксплуатация не заканчивается никогда, и именно она обычно не учитывается:

  • обновления. Kubernetes выпускает минорную версию примерно раз в четыре месяца, и поддерживаются последние три (уточняется в 14.09). Это значит обновление кластера примерно раз в год минимум, с проверкой устаревших API;
  • ваши манифесты стареют вместе с API: ресурс, объявленный устаревшим, через несколько версий удаляется, и выкат ломается на ровном месте;
  • отладка идёт на новом уровне. К вопросу «почему приложение не отвечает» добавляются вопросы «почему под не планируется», «почему его вытеснили», «почему сервис не находит реплики» — этому посвящён отдельный урок 13.14;
  • дежурство. Кластер, который держит продуктивную нагрузку, требует кого-то, кто разберётся в три часа ночи.

Управляемый кластер у облачного провайдера снимает часть этого счёта — управляющий слой обслуживает провайдер, — но не весь: рабочие узлы, манифесты, нагрузка и отладка остаются вашими. Где взять кластер и чем варианты отличаются, разбирается в уроке 13.13.

3.8. Примеры и варианты реализации

Вопрос «нужен ли оркестратор» решается одинаково везде, а вот цена и способ получить кластер зависят от среды.

Локальный стенд (этот урок). kind поднимает кластер в контейнерах Docker: однонодовый за 45 с, трёхузловой за 94 с. Годится для обучения и разработки, не годится для нагрузки: все «узлы» живут на одной машине и падают вместе с ней. Альтернативы того же класса — minikube (умеет несколько драйверов, в том числе виртуальную машину) и k3d (кластер на k3s). Что выбрать и чем они отличаются — урок 13.13.

VPS / bare-metal. Кластер ставится kubeadm на подготовленные машины: вы сами отвечаете за управляющий слой, etcd и его резервные копии, обновления, сеть и балансировщик перед кластером. Минимально осмысленная отказоустойчивая конфигурация — три управляющих узла (кворум etcd) плюс рабочие, то есть счёт начинается не с одной машины. Для небольших установок распространён облегчённый дистрибутив k3s. Именно здесь цена эксплуатации из раздела 3.7 видна целиком.

Yandex Cloud. Managed Service for Kubernetes берёт на себя управляющий слой: его обновление и доступность — зона ответственности провайдера, вы платите за это и за рабочие узлы. Остаются вашими: группы узлов и их размер, обновление рабочих узлов, манифесты, сетевые политики и наблюдаемость. Проверка запуском в этом блоке не делалась (стенд — локальный), поэтому конкретные команды и ограничения даны в уроке 13.13 по документации сервиса и помечены там прямо.

Общее правило выбора среды одно: на локальном стенде проверяются манифесты и механика, отказоустойчивость — не проверяется. Кластер из трёх контейнеров на одном ноутбуке не переживёт отказа ноутбука.

4. Практика на занятии (30 минут)

Курс проходят самостоятельно, поэтому каждый шаг говорит, что ввести, зачем, что должно получиться и что делать при неудаче.

Все опыты собраны в одном скрипте lab-why-k8s.sh. Он сам поднимает стенд (kind), сам убирает за собой и кладёт kubeconfig во временный каталог — ваш ~/.kube/config он не трогает. Если запускаете несколько режимов подряд, ставьте KEEP=1, иначе каждый режим будет создавать кластер заново. После пересборки образа приложения кластер не увидит новую версию сам — нужен kind load docker-image; в лабораторной это уже сделано за вас, но помнить об этом стоит уже сейчас, потому что это частая ошибка всего блока (раздел 6).

Шаг 1 (3 минуты). Подготовка.

  • Что ввести:
docker build -t course-k8s-app:1 blocks/13-kubernetes-basics/examples/13.01-why-kubernetes/app
  • Зачем: этот образ понадобится всему блоку 13, а не только этому уроку. В его Dockerfile уже собраны три решения из блока 08, которые в кластере станут требованиями, а не рекомендациями, — непривилегированный пользователь, небуферизованный вывод и форма списка в CMD.
  • Ожидаемый результат: сборка заканчивается без ошибок; docker images course-k8s-app:1 показывает свежий образ.
  • Если не так: ошибка вида «no such file or directory» на пути app почти всегда значит, что команда запущена не из корня репозитория, — путь к контексту сборки в команде задан от корня.
  • Ответьте: откройте Dockerfile и найдите в нём все три решения из блока 08 — дальше по блоку кластер начнёт требовать их соблюдения, а не просто одобрять.

Шаг 2 (5 минут). Цена кластера.

  • Что ввести:
cd blocks/13-kubernetes-basics/examples/13.01-why-kubernetes
bash lab-why-k8s.sh cost
  • Зачем: увидеть цену пустого кластера раньше, чем в нём появится хоть один ваш под, — это и есть счёт из раздела 3.2, который обычно никто не называет.
  • Ожидаемый результат: скрипт поднимает однонодовый кластер и печатает память узла в покое, число служебных подов и число процессов — на этом стенде это около 629–651 МиБ, 9 подов и 32 процесса (у вас цифры будут плавать в зависимости от загрузки машины, смотрите на порядок и состав, а не на секунды — см. врезку в разделе 3.2). Пока скрипт работает, посмотрите на сами поды: kubectl get pods -A.
  • Если не так: если числа заметно выше указанных — на машине не хватает свободной памяти для Docker, закройте лишние приложения и повторите; если kind create cluster не укладывается в разумное время (пара минут) — Docker Desktop мог деградировать после серии прогонов, помогает его перезапуск.
  • Ответьте: сколько ваших приложений среди девяти служебных подов? (Правильный ответ — ноль: это etcd, управляющий слой, сеть, DNS и поставщик хранилища.)

Шаг 3 (8 минут). Самолечение.

  • Что ввести:
KEEP=1 bash lab-why-k8s.sh selfheal

Запустите команду ещё два раза подряд, не убирая стенд между запусками.

  • Зачем: сравнить, как каждая среда ведёт себя при первом падении приложения — и как меняется поведение при повторных падениях подряд. Это ровно опыт из раздела 3.3.
  • Ожидаемый результат: при первом запуске обе половины вывода сопоставимы — Compose и Kubernetes возвращают сервис за похожее время (единицы секунд). При втором и третьем запуске окно недоступности у Kubernetes растёт (в уроке измерено 2,5 → 14,7 → 26,2 с), а у Compose остаётся на том же уровне.
  • Если не так: если окно у Kubernetes не растёт от запуска к запуску — проверьте, что вы не пересоздали стенд между запусками (KEEP=1 должен стоять на каждом вызове); задержка привязана к счётчику перезапусков конкретного пода, а не к приложению вообще, — счётчик виден в колонке RESTARTS вывода kubectl get pods.
  • Ответьте: почему окно недоступности растёт именно у Kubernetes и не растёт у Docker Compose? Ответ виден в kubectl get pods — в колонке перезапусков и в состоянии CrashLoopBackOff (раздел 3.3).

Шаг 4 (8 минут). Потеря контейнера и масштабирование.

  • Что ввести:
KEEP=1 bash lab-why-k8s.sh lost
KEEP=1 bash lab-why-k8s.sh scale
  • Зачем: здесь впервые появляется настоящая разница между средами — там, где исчезает не процесс, а контейнер целиком (режим lost), и там, где нужно поднять несколько экземпляров на одном порту (режим scale). Это опыт из раздела 3.4.
  • Ожидаемый результат: в режиме lost Compose не восстанавливает удалённый контейнер сам (ноль контейнеров ещё какое-то время спустя), Kubernetes создаёт новый под за секунды. В режиме scale Compose возвращает код 1 с сообщением port is already allocated и поднимает только 1 контейнер из 4, а Kubernetes поднимает все 4 реплики за несколько секунд на одном адресе сервиса, и запросы расходятся по ним неровно.
  • Если не так: если у вас Compose поднял все 4 экземпляра без ошибки — проверьте compose/compose.yaml: опыт показателен только при фиксированном порте хозяина, без него ловушки нет.
  • Ответьте: какой код возврата у Compose в режиме scale и сколько контейнеров работает после его отказа?

Шаг 5 (1 минута). Уборка.

  • Что ввести:
bash lab-why-k8s.sh clean
  • Зачем: убрать временный кластер и контейнеры лабораторной, не трогая образ узла — он нужен следующим урокам блока, и качать его заново около 12 минут.
  • Ожидаемый результат: kind get clusters и docker ps -a | grep lab1201 пусты; образ kindest/node:v1.37.0 (1,34 ГБ) на месте — docker images | grep kindest.
  • Если не так: если kind get clusters всё ещё показывает lab1201 — выполните kind delete cluster --name lab1201 вручную и проверьте docker ps -a на зависшие контейнеры лабораторной.

Режим nodefail в занятие не помещается: он идёт около семи минут, потому что столько занимает выселение подов с отказавшего узла. Он вынесен в домашнюю работу.

Что обсудить в конце занятия. Возьмите любой сервис, который вы эксплуатируете или знаете, и ответьте на два вопроса: сколько у него машин и нужно ли удерживать его состояние без человека. Если ответ «одна» и «нет» — оркестратор этому сервису не нужен, и это нормальный инженерный вывод.

5. Домашняя работа (1–2 часа)

Задание состоит из двух частей: измерить отказ узла и принять решение по своему сервису.

Часть 1. Отказ узла (около 20 минут, из них 7 — ожидание).

cd blocks/13-kubernetes-basics/examples/13.01-why-kubernetes
bash lab-why-k8s.sh nodefail

Режим поднимает трёхузловой кластер, раскладывает четыре реплики, выключает один узел и печатает таблицу по секундам. Из этой таблицы надо выписать три разных момента и не перепутать их:

  1. когда мёртвые реплики перестали получать трафик (упал столбец «готовых реплик»);
  2. когда узел помечен NotReady;
  3. когда появились новые реплики на живом узле (вырос столбец «подов всего»).

Затем ответьте письменно: сколько времени сервис работал с половиной ёмкости и что произошло бы, если бы оставшиеся реплики были загружены на 100 %.

Часть 2. Решение по своему сервису. Заполните карточку homework/decision-card.md для любого сервиса, который вы эксплуатируете, разрабатываете или хорошо знаете (подойдёт и учебный сервис из блока 07). Карточка требует не мнения, а проверяемых ответов: сколько машин, кто дежурит, что происходит сейчас при отказе одной машины, во что обойдётся кластер. Вывод «оркестратор не нужен» — полноценный результат, если он обоснован.

Критерии приёмки.

  • режим nodefail прогнан, таблица по секундам сохранена (вывод целиком) в карточке;
  • три момента из части 1 выписаны с числами и не перепутаны между собой;
  • в карточке заполнены все строки раздела «Факты о сервисе» — без «не знаю»: если число неизвестно, написано, как вы его узнаете;
  • в карточке посчитана цена кластера для вашего случая: минимум машин и кто обновляет;
  • решение сформулировано одной фразой и подкреплено минимум тремя фактами из таблицы, а не общими соображениями;
  • названо хотя бы одно условие, при котором решение следует пересмотреть;
  • стенд убран: bash lab-why-k8s.sh clean, и kind get clusters не содержит lab1201.

Файлы для работы: blocks/13-kubernetes-basics/examples/13.01-why-kubernetes/

6. Частые ошибки и подводные камни

Все перечисленные встретились при подготовке этого урока, и половина из них сначала дала неверный результат опыта.

Проверяют самолечение через docker kill — и делают неверный вывод. Симптом: контейнер с restart: unless-stopped остался лежать, RestartCount: 0, состояние Exited (137). Причина: остановку через API Docker считает намеренной, и политика перезапуска на неё не распространяется. Что делать: проверять падением самого приложения (в учебном сервисе для этого есть /crash), а не убийством снаружи.

Пытаются убить процесс внутри контейнера командой kill -9 1. Симптом: команда выполнена, ошибки нет, приложение продолжает отвечать. Причина: ядро не доставляет процессу с номером 1 сигналы без обработчика, посланные из того же пространства имён. Что делать: ронять приложение изнутри его собственным кодом либо останавливать контейнер снаружи, понимая разницу из предыдущего пункта.

Пересобрали образ, а кластер работает на старом. Симптом: изменение в коде не появилось, хотя сборка прошла и тег тот же. Причина: kind не видит локальное хранилище Docker, у кластера своё. Что делать: после каждой пересборки выполнять kind load docker-image <образ> --name <кластер>. Тег при этом не меняется, поэтому ошибка выглядит как «мистика».

Ждут мгновенного переезда при отказе узла. Симптом: узел выключен, а поды всё ещё числятся на нём и новых нет. Причина: умолчания дают 50 секунд до пометки узла неработоспособным и ещё 5 минут до выселения подов. Что делать: планировать запас ёмкости и понимать, что оркестратор даёт время пережить отказ, а не отменяет его.

Видят «лишние» поды и решают, что кластер сломался. Симптом: реплик четыре, а kubectl get pods показывает шесть, из них два с отметкой на удаление, которая не исчезает. Причина: подтвердить удаление контейнера должен kubelet упавшего узла, а он недоступен. Что делать: при разборе аварии смотреть на узел пода и на состояние узла; после возвращения узла лишние поды убираются сами.

Считают, что Kubernetes всегда перезапускает быстрее. Симптом: приложение, падающее повторно, возвращается всё медленнее — 2,5 с, 14,7 с, 26,2 с. Причина: kubelet намеренно увеличивает паузу перед перезапуском повторно падающего контейнера (CrashLoopBackOff). Docker так не делает. Что делать: чинить причину падения; для проверки исправления пересоздать под, а не ждать — счётчик задержки привязан к контейнеру.

kind записывает конфиг в ~/.kube/config. Симптом: после учебных опытов в рабочем файле контекстов появились чужие записи, а текущий контекст переключился. Причина: так работает kind по умолчанию. Что делать: работать с KUBECONFIG, указывающим на временный файл (лабораторная урока так и делает), либо не забывать kubectl config delete-context после опытов.

7. Проверка усвоения

  1. Приложение падает раз в сутки и перезапускается. Что изменится, если перевести его из Compose в Kubernetes, и почему это не довод за переезд?
  2. Узел с двумя из четырёх реплик выключился. Через какое время прекратится трафик на мёртвые реплики и через какое восстановится ёмкость? Почему это два разных числа?
  3. Приложение упало трижды подряд, и каждый раз оно возвращалось дольше предыдущего. Что происходит и почему это сделано намеренно?
  4. Вы удалили контейнер командой docker rm -f. Почему политика restart: unless-stopped его не вернула, хотя при падении приложения возвращала?
  5. Назовите два условия, при которых Kubernetes задаче не нужен, и объясните каждое через то, что оркестратор делает.
  6. У сервиса пять машин с Compose, раскладка описана в Ansible, выкаты раз в месяц, простой в пять минут допустим. Нужен ли здесь кластер? Что должно измениться, чтобы ответ стал другим?
  7. Почему сравнивать «Kubernetes против Compose» по потреблению памяти приложения бессмысленно, а по потреблению стенда — осмысленно?
  8. Вы пересобрали образ, выкатили его в кластер kind, но изменения не появились. Где искать причину в первую очередь?

8. Материалы

Официальная документация — начать отсюда.

  • Overview: что такое Kubernetes — определение от проекта, список того, что оркестратор делает и, отдельным разделом, чего он не делает. Второй список для этого урока полезнее первого.
  • Taints and Tolerations — здесь дословно сказано про автоматические терпимости с tolerationSeconds=300, то есть откуда берутся пять минут из опыта с отказом узла.
  • Справочник kube-controller-manager — значение по умолчанию --node-monitor-grace-period (50 с). Вместе с предыдущим пунктом объясняет замер полностью.
  • Pod Lifecycle — состояния контейнера и CrashLoopBackOff: почему повторно падающее приложение возвращается всё медленнее.

Про инструменты стенда.

  • kind: Quick Start — создание кластера, конфигурация с несколькими узлами и kind load docker-image, без которого образ в кластер не попадёт.
  • Docker: перезапуск контейнеров автоматически — чем отличаются no, on-failure, always и unless-stopped; нужно, чтобы понимать, с чем именно вы сравниваете оркестратор.

Если решение принято в пользу кластера.

  • Production environment — обзор способов получить кластер, пригодный для нагрузки, и требований к управляющему слою. Хорошо показывает, что «поставить Kubernetes» и «эксплуатировать Kubernetes» — разные задачи.

Для чтения не по документации. Kelsey Hightower, Kubernetes The Hard Way — пошаговая ручная сборка кластера без инструментов автоматизации. Автор прямо пишет, что руководство «optimized for learning», а не для боевой установки, — и именно поэтому оно отвечает на вопрос «из чего состоит кластер» лучше любого описания: вы собираете его по компонентам. Читать имеет смысл после урока 13.02.


← Предыдущий: 12.10. Terraform и Ansible вместе: разделение ответственности · Блок 13 · Следующий: 13.02. Архитектура кластера: control plane, etcd, kubelet, CRI, CNI →

Practice materials

Scripts, cheat sheets and starter files for the lesson tasks. The lesson text is on this page.