Почему в Kubernetes всё «зависает» в Terminating
Статус Terminating — это не «удаление сломалось», а «удаление началось, но ещё не завершилось». Kubernetes удаляет объекты асинхронно: объект получает metadata.deletionTimestamp, после чего контроллеры должны выполнить завершающие действия и убрать блокировки.
Главный механизм таких блокировок — finalizers. Это список строк в metadata.finalizers, который говорит API-серверу: «не удаляй объект физически, пока конкретная логика не отработает cleanup».
В норме это полезно: корректно отсоединить том, удалить внешний балансировщик, почистить записи в сторонней системе. Но если контроллер удалили, он упал, потерял RBAC-права или завис, финалайзер остаётся навсегда, и вы получаете классический «залипший» ресурс.
Как выглядит проблема на практике
- Namespace stuck terminating: namespace не удаляется часами или днями.
- CRD зависает в
Terminating, потому что остались custom resources или финалайзеры оператора. - PV/PVC не удаляются из-за финалайзеров CSI-драйвера или защитных финалайзеров Kubernetes.
Финалайзер — это не ошибка, а контракт между объектом и контроллером. Снимать
finalizersвручную стоит только после понимания, какой внешний cleanup вы пропускаете.
Быстрая диагностика: кто держит finalizer и что именно блокирует удаление
Универсальный алгоритм: (1) убедиться, что удаление стартовало (deletionTimestamp), (2) посмотреть finalizers, (3) понять, какой контроллер должен их снять, (4) по возможности восстановить контроллер, и только потом (5) точечно снимать финалайзер вручную.
Шаг 1. Посмотреть YAML и события
Начните с YAML проблемного объекта:
kubectl get namespace my-ns -o yaml
kubectl get crd widgets.example.com -o yaml
kubectl get pv pvc-123456 -o yaml
Ищите:
metadata.deletionTimestamp— удаление действительно началось;metadata.finalizers— что именно блокирует;status.conditions(особенно у namespace) — подсказки, какие типы ресурсов не удаляются;- события (
Events) — часто прямо говорят, где ошибка (RBAC, таймаут, недоступный API, проблемы хранилища).
kubectl describe namespace my-ns
kubectl describe pv pvc-123456
Шаг 2. Понять, «чей» это финалайзер
Типовые источники finalizers:
- встроенные механизмы Kubernetes (например, финализация namespace);
- CSI/Storage-контроллеры (PV/PVC, VolumeAttachment и связанные сущности);
- операторы (CRD/CR): часто добавляют собственные финалайзеры, чтобы убрать внешние зависимости;
- реже — policy/GitOps-контроллеры и сервисы, которые создают внешние ресурсы.
Если контроллер жив, но финалайзер не снимается, очень часто причина банальна: у контроллера нет прав (RBAC), он не видит нужный объект, или внешняя система (storage/cloud API) возвращает ошибку.
Шаг 3. Проверить здоровье контроллеров
Перед ручным remove finalizer проверьте, что проблема не лечится восстановлением контроллера:
kubectl get pods -A
kubectl get deploy -A
kubectl get events -A --sort-by=.lastTimestamp
Для CSI почти всегда стоит отдельно смотреть kube-system и namespace драйвера (если он не в kube-system).
Если у вас небольшой кластер и вы запускаете Kubernetes на отдельном сервере, удобнее держать контрольную ноду на предсказуемых ресурсах. В таком сценарии часто выбирают VDS, чтобы не бороться с внезапными ограничениями по CPU/RAM и диску.
Namespace stuck Terminating: причины и безопасный план разморозки
Удаление namespace — особый случай: Kubernetes обязан удалить все объекты внутри. Если хоть один объект не удаляется (в том числе из-за финалайзера), namespace останется в Terminating.
1) Посмотреть conditions у namespace
kubectl get namespace my-ns -o jsonpath='{.status.conditions}'
В conditions обычно видно одно из типовых препятствий: остались ресурсы конкретных типов, не получается перечислить ресурсы из API-группы (часто при проблемных CRD), или найдены объекты с финалайзерами.
2) Найти «залипшие» объекты внутри namespace
Быстрый старт:
kubectl get all -n my-ns
Но проблема часто прячется в «не all»-ресурсах: ingress, networkpolicy, pvc, rolebinding, admission-ресурсы и custom resources. Практичный подход — узнать все namespaced типы, которые вообще существуют в кластере:
kubectl api-resources --verbs=list --namespaced -o name
Дальше проверяйте типы, которые реально используются в этом namespace. Для выборочного поиска объектов с deletionTimestamp или finalizers удобно подключать jq (если он доступен на вашей админ-машине):
kubectl get pods -n my-ns -o json | jq -r '.items[] | select(.metadata.deletionTimestamp!=null or (.metadata.finalizers|length>0)) | .metadata.name + "\t" + ((.metadata.finalizers//[])|join(","))'
Эту же идею повторяйте для pvc, ingress и нужных CR.
3) Частый корень зла: CRD/CR внутри namespace
Если namespace удаляется, а оператор уже удалён или сломан, custom resources могут оставаться «торчать» с финалайзерами и удерживать удаление. Варианты лечения: временно вернуть оператор (лучший вариант) или точечно снять финалайзеры с конкретных CR и удалить их.
Если вы работаете с лёгкими кластерами, где часть функциональности (ingress, балансировка, политики) выбирается руками, полезно иметь под рукой сравнение подходов и типовых ловушек: выбор ingress-контроллера в k3s.
4) Снять финалайзер у namespace (крайняя мера)
Если вы уверены, что внутри не осталось важных внешних ресурсов, и восстановить контроллеры невозможно, можно убрать финалайзеры у namespace. Тогда API «выкинет» namespace даже если не все объекты были корректно доубраны.
Сначала проверьте текущие финалайзеры:
kubectl get namespace my-ns -o jsonpath='{.metadata.finalizers}'
Дальше используйте patch. Вариант 1: удалить поле целиком (JSON Patch):
kubectl patch namespace my-ns --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]'
Вариант 2: заменить массив на пустой (merge patch):
kubectl patch namespace my-ns --type=merge -p='{"metadata":{"finalizers":[]}}'
После снятия финалайзеров namespace может исчезнуть мгновенно, но внешние зависимости (тома, балансировщики, записи в сторонних системах) уже никто автоматически не почистит.

CRD finalizers: почему CRD зависает при удалении и что делать
CRD — это «схема» и точка входа для custom resources. Пока в кластере существуют объекты этого типа, Kubernetes должен их корректно обработать. Плюс сами CR часто имеют финалайзеры от оператора.
Сценарий A: CRD удаляется, но остаются custom resources
Проверьте, остались ли CR этого типа (пример для widgets.example.com):
kubectl get widgets --all-namespaces
Если список не пустой — удаляйте CR штатно. Если они тоже в Terminating, смотрите metadata.finalizers у конкретного CR:
kubectl get widget my-widget -n my-ns -o yaml
Если оператор, который должен снять финалайзер, уже удалён, это и будет причиной «вечного Terminating».
Сценарий B: CRD застрял из-за финалайзера на самом CRD
Иногда финалайзер висит прямо на CRD. Проверьте:
kubectl get crd widgets.example.com -o jsonpath='{.metadata.finalizers}'
Если вы осознанно идёте на ручное снятие, используйте merge patch:
kubectl patch crd widgets.example.com --type=merge -p='{"metadata":{"finalizers":[]}}'
Сценарий C: проблемы discovery/aggregated API и «висячие» группы
Иногда удаление ломается на этапе API discovery: namespace conditions сообщают, что невозможно перечислить ресурсы определённой API-группы. Такое бывает при битых APIService (aggregated API) или при некорректно удалённых расширениях.
Практичные варианты: вернуть CRD и оператор на время, чтобы они сняли финалайзеры с CR и удалились корректно; либо, если восстановление невозможно, точечно снять финалайзеры с CR, а затем с CRD. Если в кластере есть aggregated API, проверьте его состояние отдельно (ошибки обычно видны в events и логах компонентов).
PV finalizers и Storage: почему тома не удаляются
С PV/PVC часто путаются ожидания: «удалил PVC — почему PV или диск остался?» или «PV в Terminating и не уходит». Тут одновременно влияют финалайзеры и политика spec.persistentVolumeReclaimPolicy.
Сначала уточняем, какой результат вам нужен
- Удалить PVC, но сохранить диск для ручного восстановления: обычно используется
Retain, и внешний диск останется. - Удалить PVC и удалить диск: нужен
Delete, а CSI-контроллер обязан быть жив и иметь права на удаление внешнего тома.
Типовые финалайзеры на PV/PVC
kubernetes.io/pv-protectionиkubernetes.io/pvc-protection— защита от удаления, пока есть привязки;- финалайзеры CSI (зависят от драйвера) — пока драйвер не отсоединит или не удалит внешний том.
Проверяем связку PVC → PV и текущие статусы
kubectl get pvc -A -o wide
kubectl get pv -o wide
Дальше смотрим YAML проблемного PV:
kubectl get pv pvc-123456 -o yaml
Полезные поля:
spec.claimRef— какой PVC держит PV;spec.persistentVolumeReclaimPolicy—DeleteилиRetain;metadata.finalizers— кто блокирует удаление;spec.csi— какой драйвер отвечает за том.
Когда допустимо вручную убрать финалайзеры у PV
Если CSI-драйвер удалён или сломан, и вы понимаете последствия (например, внешний диск уже удалён вручную, или он вам не нужен), можно снять финалайзеры с PV. Kubernetes завершит удаление объекта, но внешний ресурс при этом не «магически» очистится.
kubectl patch pv pvc-123456 --type=merge -p='{"metadata":{"finalizers":[]}}'
Если блокирует kubernetes.io/pv-protection, сначала убедитесь, что нет живого PVC, который ссылается на PV, и что PV не используется pod’ами (иначе вы рискуете получить потерю данных или странные ошибки монтирования).
Garbage collection в Kubernetes и почему он не спасает от финалайзеров
Встроенный сборщик мусора (garbage collection) удаляет зависимые объекты по ownerReferences и выбранной стратегии propagation. Но GC не «ломает» финалайзеры: если объект защищён finalizers, GC будет ждать, пока финалайзер снимут.
Из-за этого удаление «по цепочке» иногда выглядит бесконечным: вы удалили верхний объект, GC пытается удалять нижние, но один из них застрял на финалайзере, и весь процесс визуально «завис».
Полезные флаги при удалении (не панацея)
Иногда помогает корректно настроить ожидание, но это не обходит финалайзеры:
kubectl delete ns my-ns --wait=false
kubectl delete crd widgets.example.com --wait=false
--wait=false не ускоряет удаление, а лишь не блокирует терминал ожиданием завершения.
Практика: безопасный чеклист перед тем как снимать finalizers вручную
Проверьте, что контроллер существует: оператор/CSI/контроллер облака запущен и не падает.
Проверьте права: нередко контроллер жив, но после изменений потерял RBAC и не может завершить cleanup.
Посмотрите логи контроллера: там обычно есть прямое объяснение (нет доступа к API, ошибка провайдера, quota, таймаут).
Поймите внешний эффект: что именно финализируется (диск, LB, запись в внешней системе).
Действуйте точечно: сначала финалайзер на конкретном CR/PV, и только потом думайте про namespace целиком.
После ручного вмешательства проверьте «хвосты»: внешние ресурсы могут остаться и потребовать ручной уборки.

Команды-шпаргалки
Показать финалайзеры
kubectl get namespace my-ns -o jsonpath='{.metadata.finalizers}'
kubectl get crd widgets.example.com -o jsonpath='{.metadata.finalizers}'
kubectl get pv pvc-123456 -o jsonpath='{.metadata.finalizers}'
Снять финалайзеры (merge patch)
kubectl patch namespace my-ns --type=merge -p='{"metadata":{"finalizers":[]}}'
kubectl patch crd widgets.example.com --type=merge -p='{"metadata":{"finalizers":[]}}'
kubectl patch pv pvc-123456 --type=merge -p='{"metadata":{"finalizers":[]}}'
Найти ресурсы в namespace, которые ещё существуют
kubectl get all -n my-ns
kubectl get pvc -n my-ns
kubectl get ingress -n my-ns
kubectl get events -n my-ns --sort-by=.lastTimestamp
Итоги
Если вы видите Terminating, это почти всегда означает одно: объект помечен на удаление, но финализация не завершилась. Для namespace stuck terminating причина обычно в «залипших» объектах внутри (часто CR/CRD) или в контроллерах, которые должны снять финалайзеры. Для crd finalizers и pv finalizers ключевой шаг — понять, какой оператор/CSI отвечает за cleanup, и восстановить его работу. Ручное снятие финалайзеров через kubectl patch — инструмент последней линии: полезный, но требующий осознанных действий и проверки внешних последствий.


