Зачем 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. - Ожидаемый результат: в режиме
lostCompose не восстанавливает удалённый контейнер сам (ноль контейнеров ещё какое-то время спустя), Kubernetes создаёт новый под за секунды. В режимеscaleCompose возвращает код 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
Режим поднимает трёхузловой кластер, раскладывает четыре реплики, выключает один узел и печатает таблицу по секундам. Из этой таблицы надо выписать три разных момента и не перепутать их:
- когда мёртвые реплики перестали получать трафик (упал столбец «готовых реплик»);
- когда узел помечен
NotReady; - когда появились новые реплики на живом узле (вырос столбец «подов всего»).
Затем ответьте письменно: сколько времени сервис работал с половиной ёмкости и что произошло бы, если бы оставшиеся реплики были загружены на 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. Проверка усвоения
- Приложение падает раз в сутки и перезапускается. Что изменится, если перевести его из Compose в Kubernetes, и почему это не довод за переезд?
- Узел с двумя из четырёх реплик выключился. Через какое время прекратится трафик на мёртвые реплики и через какое восстановится ёмкость? Почему это два разных числа?
- Приложение упало трижды подряд, и каждый раз оно возвращалось дольше предыдущего. Что происходит и почему это сделано намеренно?
- Вы удалили контейнер командой
docker rm -f. Почему политикаrestart: unless-stoppedего не вернула, хотя при падении приложения возвращала? - Назовите два условия, при которых Kubernetes задаче не нужен, и объясните каждое через то, что оркестратор делает.
- У сервиса пять машин с Compose, раскладка описана в Ansible, выкаты раз в месяц, простой в пять минут допустим. Нужен ли здесь кластер? Что должно измениться, чтобы ответ стал другим?
- Почему сравнивать «Kubernetes против Compose» по потреблению памяти приложения бессмысленно, а по потреблению стенда — осмысленно?
- Вы пересобрали образ, выкатили его в кластер
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.
-
Lesson materials 13.01 «Зачем Kubernetes и когда он не нужен».zip