Поды зависают в Pending: где ломается Kubernetes StorageClass
Развёртывание выглядит здоровым: манифесты применились, сервис создан, но поды висят в Pending и не двигаются. В событиях пода — FailedScheduling со ссылкой на неудовлетворённую заявку на том, у контроллера — ошибка выделения тома. Приложение при этом не падает: оно просто не запускается, потому что Kubernetes не отдаёт ему хранилище.
Причина почти всегда в конфигурации класса хранения. Если в StorageClass выставлен режим немедленной привязки, том создаётся до того, как планировщик выбрал узел, — и легко попадает в зону, где нужного диска нет или он несовместим с этим узлом. При отложенной привязке том выделяется под конкретный узел, и зависшее развёртывание часто означает, что под некуда поставить, а не что хранилище недоступно.
Отдельная ветка — расхождение заявки и класса: PVC ссылается на имя, которого в кластере нет, на класс без метки класса по умолчанию или просит режим доступа, который драйвер не умеет отдавать. Тогда том не создаётся вообще, а автоскейлер не может добавить ни одной реплики: каждая новая ждёт свой том, и масштабирование встаёт вместе с раскаткой.
Диагностика здесь предсказуема: смотрим события PVC и пода, сверяем имя класса, режим привязки и политику возврата, проверяем, что драйвер зарегистрирован и в выбранной зоне есть свободная ёмкость. Дальше задача не в том, чтобы разбудить один зависший под, а в том, чтобы привести политики хранения в порядок — разбираем, как это закрывается.
Что даёт динамический провижининг томов в кластере
Динамический провижининг томов строится на одной связке: StorageClass описывает класс хранилища и политику его освобождения, а PersistentVolumeClaim в манифесте приложения просто ссылается на этот класс. Дальше контроллер хранилища сам создаёт том, привязывает его к заявке и подключает к поду. Ручной шаг «заказать диск, дождаться выделения, вписать идентификатор в спецификацию и передеплоить» исчезает из процесса целиком — том появляется за секунды, а не за часы согласований.
Для времени развёртывания это меняет больше, чем кажется. Заявка на том едет в том же коммите, что и само приложение, поэтому новая среда в тестовом контуре поднимается одним применением манифестов. Разработчик не ждёт инфраструктурной заявки, а стенд не живёт в состоянии «под уже есть, диска ещё нет».
Масштабирование нагрузки получает тот же эффект: при росте числа реплик StatefulSet каждый новый под автоматически создаёт том из класса и монтирует его, не требуя ручной работы на каждую копию. Класс отказов вида «том не выделен», «заявка висит в ожидании», «диск привязан не к той зоне» уходит из эксплуатации, потому что их закрывает контроллер, а не дежурный инженер.
Существующие статические тома продолжают работать: классы и заявки на них сосуществуют, поэтому переход можно делать постепенно, по одному приложению. Если в кластере уже описаны StorageClass с политиками сохранения данных, новые заявки используют их как есть — совместимость со старыми манифестами сохраняется, менять разметку хранилища не требуется.
Доля ручных операций падает до единичных случаев: ручными остаются только те решения, где нужен человек — размер квоты, срок жизни, политика удаления тома после снятия заявки. Всё остальное — создание, привязка, монтирование — идёт автоматически, а радиус отказа сужается до одной заявки вместо цепочки ручных шагов.
Сравнение режимов выделения и привязки томов
Выбор между статическим и динамическим выделением — это выбор между предсказуемостью и скоростью. Режимы выделения томов определяют, кто создаёт носитель: администратор заранее описывает тома и держит их в пуле, либо контроллер создаёт носитель в момент появления запроса от пода. Статический провижининг даёт полный контроль над тем, куда попадут данные, динамический — убирает ручную работу на каждый новый сервис.
На привязку томов влияет не только режим, но и политика переработки тома: после удаления запроса носитель либо возвращается в пул, либо сохраняется как есть, либо удаляется вместе с томом. От этого зависит и возможность отката, и то, как быстро растёт объём хранилища.
Когда какой режим уместен
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Статическое выделение | Данные переживают кластер: базы, архивы, тома с внешним жизненным циклом | Тома создаются вручную, пул нужно держать с запасом, привязка по имени хрупкая |
| Динамическое через класс по умолчанию | Однотипная нагрузка без требований к профилю носителя | Все запросы получают один профиль; его смена затрагивает весь кластер, а не одну команду |
| Именованные классы под профили | Разные требования: быстрые диски под транзакции, ёмкие — под логи и аналитику | Больше объектов в сопровождении, для каждого класса своя политика переработки |
Отдельно стоит отложенная привязка: том создаётся только тогда, когда под уже размещён на узле. Иначе в кластере с несколькими зонами носитель может оказаться не в той зоне, и под зависнет в ожидании, а диагностика такого состояния занимает часы. Разбираем, как выбрать режим под профиль нагрузки и не потерять данные при откате.
Как мы настраиваем Kubernetes StorageClass: этапы работ
Порядок работ выстраиваем так, чтобы классы хранения появлялись из реальных профилей нагрузки, а не «на глазок». Сначала фиксируем, что уже смонтировано и как тома ведут себя под нагрузкой, затем проектируем схему классов и только после этого меняем кластер. Каждый этап закрывается артефактом, который можно проверить, — до перехода к следующему.
- Аудит томов Kubernetes: собираем PVC и PV по пространствам имён, смотрим драйвер, политику освобождения, режим привязки, фактическую утилизацию и топологию узлов. На выходе — таблица нагрузок с требованиями к задержке, разделяемому доступу и интенсивности ввода-вывода.
- Проектирование схемы классов: выделяем 3–5 профилей — быстрые тома для баз данных, ёмкие для логов, архивные с холодным слоем — и закрепляем за каждым свой StorageClass с явными параметрами.
- Настройка StorageClass: задаём политику освобождения, разрешение на расширение, отложенную привязку для томов в нескольких зонах и предельные размеры, чтобы один PVC не вытянул весь пул.
- Подготовка и перевод нагрузок: назначаем класс по умолчанию, переносим существующие тома сменой класса через пересоздание или снимки — без длительной остановки сервисов.
- Проверка на стенде: прогоняем отказ узла, исчерпание квоты, расширение тома на живой нагрузке и откат на предыдущий класс. Фиксируем, как ведёт себя планировщик в каждом сценарии.
- Внедрение хранилища в продуктив и передача команде: раскатываем по пространствам имён, отдаём схему классов, регламент заявок на тома и инструкции для дежурной смены.
Политики доступа, квоты и объекты в кластере
После внедрения у команды заказчика остаются не только работающие классы хранения, но и артефакты, по которым кластер эксплуатируется без нас. Ниже — то, что передаётся на руки и проверяется на приёмке.
- Описания каждого StorageClass: назначение, параметры (режим привязки тома, политика возврата данных при удалении заявки, тип диска) и класс нагрузки, под который он заведён — разработчик выбирает класс по описанию, а не наугад.
- Политики доступа: разграничение прав по ролям — кто создаёт и правит классы, кто только запрашивает тома, кто читает статус; отдельные роли для сервисных учётных записей.
- Квоты пространств имён: ResourceQuota и LimitRange на каждое пространство — предел по суммарному объёму хранилища, числу заявок и размеру одного тома. При исчерпании квоты команда получает понятную ошибку, а не зависшую задачу.
- Правила жизненного цикла томов: какие классы сохраняют данные после удаления заявки, какие освобождают; как забрать том на разбор инцидента.
- Регламент обслуживания: порядок добавления класса или правки параметров, проверка на стенде, окно применения изменений, точки отката.
- Документация по кластеру: схема размещения, перечень классов, манифесты и их история в системе контроля версий, журнал изменений.
- Мониторинг и оповещения: какие метрики по запросам томов и свободному месту снимаются, при каких порогах приходит уведомление.
- Передача команде: разбор с инженерами заказчика, прогон типовых сценариев, контакты для эскалации на время сопровождения.
Кейс: убрали зависание томов на пиковой нагрузке
До проекта тома в кластере выдавались вручную: по заявке инженер создавал диск и подкладывал его в манифест, а поды ждали своей очереди. На пиковых распродажах очередь росла, часть подов оставалась в состоянии Pending до утра, и раздел каталога был недоступен. Планировщик Kubernetes помочь не мог — без StorageClass ему нечем привязать том, поэтому дежурный разбирал такие инциденты ночью, руками.
Что изменили
Описали StorageClass под профили нагрузки: быстрый класс по умолчанию для баз и очередей, холодный — для логов и выгрузок. Включили динамический провижининг, привязку тома к зоне пода, allowVolumeExpansion для роста тома без пересоздания и reclaimPolicy Retain для критичных данных — том больше не уходит вместе с запросом. Сверху поставили квоты на размер запроса, чтобы один под не выедал пул целиком, и снимки тома перед релизом для быстрого отката.
Метрики до и после
| Показатель | До | После |
|---|---|---|
| Тома в Pending на пике | до 18% запросов | ноль |
| Выдача тома | 4-6 минут, на пике — десятки | секунды |
| Ручные операции дежурного | 10-12 в неделю | 0 |
| Инциденты из-за хранилища | 3-4 в месяц | 0 за полгода |
Ушёл целый класс отказов: нагрузка больше не встаёт в очередь за томом, а запускает автоматическое масштабирование. Ночные часы дежурный тратит на плановые работы, а не на ручную привязку дисков, и релиз больше не сдвигается из-за хранилища.
Что делать, если том уже создан статически
Поле storageClassName у существующего PVC неизменяемо — отредактировать его на живом кластере не получится. Поэтому переход на динамический провижининг делают не правкой манифеста, а миграцией: создаётся StorageClass с нужным драйвером и политикой возврата, готовится новый PVC, данные переносятся, и только затем нагрузка переключается на новый том.
Способ переноса зависит от возможностей драйвера хранилища. Аккуратный путь — снимок тома или клонирование с восстановлением в новый PVC: копия снимается на уровне хранилища, приложение продолжает работать на исходном томе и о копировании не знает. Если снимков нет, поднимается вторая реплика, которая наполняет новый том, пока сервис отвечает на трафик.
Переиспользовать старый том как есть не получится: статический PV привязан к конкретному PVC. StatefulSet не подхватит чужой PVC автоматически — правится volumeClaimTemplates, а поды заменяются по одному с проверкой, что реплика догнала и данные на новом томе консистентны. Старый том остаётся смонтированным для отката, пока новая схема не отработает под нагрузкой полный цикл.
На живом кластере нельзя менять accessModes и привязку к классу у PVC, а размер — только в сторону увеличения и только если расширение включено в StorageClass; часть драйверов делает это на лету, остальным нужен перезапуск пода. Политику возврата ресурсов задают в StorageClass до создания тома: править её на работающем PV рискованно — удаление PVC унесёт данные, на которые ещё ссылается приложение.
Технические сомнения перед стартом работ
Сомнения обычно не про Kubernetes как таковой, а про то, как конкретный класс ляжет на существующее хранилище, тома и права команд. Ниже — вопросы, которые инженерные команды задают перед стартом чаще всего.
Класс сработается с нашим хранилищем?
StorageClass — это профиль, а работу с диском выполняет драйвер хранилища. На разборе смотрим, какие режимы доступа он поддерживает и как ведёт себя при смене узла, и поднимаем тестовый класс на изолированном контуре — до любых правок в продуктивной среде.
Что произойдёт с уже существующими томами?
Созданные заявки на тома остаются привязанными к своему классу — новый класс их не переписывает. Переезд делаем через копию данных и новую заявку, а не правкой живого тома. Отдельно следим за классом по умолчанию: пока метка стоит, все тома без явного указания уходят именно в него.
Почему нельзя обойтись одним классом на всё?
Ограничения задаёт драйвер: один том — один узел, разделяемый доступ, поведение при удалении снятой заявки. Поэтому классы дробят по профилям. Универсальный класс либо не даёт нужного режима, либо удаляет данные там, где их требовалось сохранить.
Какие права доступа нужны командам?
Класс — ресурс уровня кластера, его создаёт и правит платформенная команда, а не каждый разработчик. Прикладным командам выдаём права на заявки о томах внутри своего пространства имён и квоты на объём: классы остаются управляемыми, а доступ к хранилищу не разрастается.
Обсудим задачу: что подготовить к разговору
Чтобы обсудить задачу предметно, полезно иметь на руках текущую картину хранения и требования к ней. Мы не просим оформлять полноценное техническое задание — достаточно перечня ниже, и по нему уже собирается оценка работ и план внедрения.
- Текущая схема классов: какие StorageClass объявлены в кластере, какой помечен как используемый по умолчанию, какие политики возврата тома заданы и куда девается физический диск после удаления заявки.
- Профили нагрузки: какие сервисы пишут последовательно (логи, архивы), каким нужна случайная запись (базы данных), а каким достаточно сетевого диска — от этого зависит тип провижининг и режим привязки тома к узлу.
- Требования к доступу: нужна ли одновременная запись с нескольких узлов, есть ли жёсткая привязка к зоне доступности, какими правами обладают команды и пространства имён.
- Работа с ростом и копиями: допускается ли расширение тома на лету, делаются ли снимки, как устроены резервные копии и восстановление из них.
- Порядок проверки результата: на каком тестовом контуре сверяем поведение, по каким сценариям — создание тома, переезд нагрузки, освобождение диска после удаления, отказ узла.
- Режим эксплуатации: окна работ, требования к отказоустойчивости, кто со стороны заказчика принимает изменения и подписывает переход на новую схему.
Если часть данных отсутствует, мы восстанавливаем её сами на этапе обследования — это отражается в плане внедрения отдельной строкой, а не перекладывается на вашу команду. Присылайте то, что есть: по неполному набору мы всё равно вернёмся с оценкой и понятным порядком работ.







