Привет! Меня зовут Роман Христевич, я DevOps-инженер в Cloud.ru, занимаюсь плагинами и безопасностью для кластеров Kubernetes. Сегодня расскажу, как я сражался с ветряными мельницами — мусором в кластерах Kubernetes после тестирования релизов, рандомных Helm Chart и просто в автотестах. И как я эти мельницы все-таки одолел.

Если вы DevOps, SRE или системный администратор, то вам не раз и не два приходилось вычищать кластеры от отработанных Jobs в статусе Complete или Configmaps с Secrets, которые создали руками для уже раскатанного Helm Chart. Разовая акция не вызывает особых забот, а вот если это надо делать несколько раз в неделю, это уже проблема или даже боль.

Под катом я расскажу, как прошел путь, усеянный граблями, от самописных решений до K8s-cleaner, и почему я теперь доволен. Если ты еще не прошел мой путь, приглашаю под кат!

Пара слов о мусорных кластерах ↓

Что это вообще за мусорные кластеры? Откуда они берутся и почему, как постоянно больное колено, не дают спокойно жить? Расскажу в нескольких словах.

Представьте себе кластер Kubernetes, где проводят тесты с релизами в состоянии разработки, или же QA все проверяют вдоль и поперек. Вот уже сбилдили образ с новой фичей, Helm Chart подготовлен, приложение раскатано. Но ему для работы почему-то нужен Configmaps или Secrets, которого нет.

Прогонять весь CI — это долго и муторно. Проще быстро написать yaml, применить его и посмотреть результат. Дальше Helm Chart релиз удаляется, а вот ресурсы, которые добавлялись, минуя Helm Chart при помощи kubectl apply, никуда не денутся, так как к самому релизу отношения не имеют. И при следующим деплое из Helm Chart мы получим закономерную ошибку, что такой ресурс уже такой есть.

Или же QA написал Job для автотестов, которые не удаляются после выполнения и через некоторое время мы увидим орду с лозунгом Complete.

Все это явно не приносит радости ни команде, ни инженеру, который обслуживает этот кластер. Проблему надо решать.

Что такое K8s-cleaner и как я к нему пришел

Я не стал исключением, и данная проблема настигла меня на «коммунальных» и QA-кластерах. После непродолжительных раздумий я написал несколько CronJob, которые решали типовые проблемы. Как показала практика, эта идея была обречена на провал. Менялись требования и условия, появлялись новые «осиротевшие» ресурсы, и уже очень скоро несколько CronJobs превратились в небольшую орду и стали копиться как снежный ком. Управлять ими оказалось проблематично, вернее, столь же трудозатратно, как и просто вычищать кластер от мусора руками.

Лечение оказалось чуть лучше болезни. Нужно было искать другое решение, желательно готовое, которое будет поддерживать комьюнити. Через неделю поиска, проб и ошибок я наткнулся на K8s-cleaner. После его тестирования я понял: это то, что мне нужно. А может, и не только мне.

Что это за зверь? Описание в репозитории говорит, что K8s-cleaner — это контроллер Kubernetes, который выявляет неиспользуемые или неисправные ресурсы, помогая поддерживать оптимизированный и эффективный кластер Kubernetes. Он обеспечивает гибкое планирование, фильтрацию по меткам, критерии выбора на основе Lua, удаление или обновление ресурсов, а также уведомления через Slack, Webex и Discord. Также он может автоматизировать работу кластеров.

Если простыми словами, то K8s-cleaner — движок, который позволит создавать CRD, чтобы они подчищали весь мусор в вашем кластере. Под капотом у них есть Lua-скрипты, которые и будут выполнять всю рутину за нас.

Сейчас вас, как и меня в момент тестирования этого решения, может посетить вопрос: «А есть ли готовые CRD и где их взять»? Да, готовый джентельменский набор есть. Вот он:

  1. https://github.com/gianlucam76/k8s-cleaner/tree/main/examples-automated-operations

  2. https://github.com/gianlucam76/k8s-cleaner/tree/main/examples-unhealthy-resources

  3. https://github.com/gianlucam76/k8s-cleaner/tree/main/examples-unused-resources

А что делать, если подходящего CRD нет? Придется выбрать один из трех путей: попросить коллегу, который знает Lua; научиться самому; обратиться за помощью к бездушному AI. Но мы обойдемся без этого и возьмем CRD из 2-го и 3-го пунктов. В самом начале этого будет достаточно.

Набор CRD, чтобы не сломать меч

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

CRD

Зачем нужно

pods-with-expired-certificates.yaml

Найдет и подскажет, какие Pods используют «протухший» сертификат.

pod-with-error-state.yml

Удалит Pods в статусе Failed. Должны иметь хотя бы один контейнер, завершившийся с Error и кодом выхода (exitCode) ≠ 0.

too-many-restarts.yaml

«Выключит» Deployment, если его Pod перезапускались более 50 раз.

orphaned_secrets.yaml

Обнаруживает и удаляет Secret, которые не используются ни в каких компонентах кластера.

completed_pods.yaml

Обнаруживает и удаляет Pods, которые успешно завершили свою работу.

unused_pod-disruption-budgets.yaml

Найдет и удалит устаревшие Pod (PDB). Устаревшими они считаются, если их селекторы не соответствуют ни одному Deployment/StatefulSet.

unused_persistent-volume-claims.yaml

Нашелся бесхозный PVC? На удаление его.

unbound_persistent-volumes.yaml

Если найдется PV в состоянии Bound, то он будет удален.

long-running-pods.yaml

Найдет и удалит Pods, которые были созданы Jobs и работают дольше одного часа.

Но! Надо быть внимательным: убедитесь, что длительная работа не была задумана изначально, иначе можно убить, например, миграцию.

completed_jobs.yaml

Тут все просто. Jobs отработала – Jobs должна умереть.

orphaned_deployment.yaml

А тут уже чуть сложнее: находятся и удаляются «осиротевшие» Deployments. Deployments считаются таковыми, если нет связанных Pod и Service.

deployment_with_replica_zero.yaml

Предназначен для автоматического поиска и удаления Deployments с нулевым количеством реплик.

orphaned_configmaps.yaml

Находит и удаляет неиспользуемые ConfigMaps.

Как CRD понимает, что они не используются? По нескольким критериям: ConfigMaps не монтируется как volume в Pod, не используется в environment variables контейнеров, не используется в initContainers, не находится в системных namespace (игнорирует namespace с префиксом kube).

statefulset_with_no_replicas.yaml

Тут тоже все просто: нашелся StatefulSet с нулевым количеством реплик — на удаление.

Приводить код этих CRD не буду, просто еще раз напомню, что они лежат на Gihub: тут и тут.

Для полной ясности давайте рассмотрим под микроскопом один CRD: из чего он состоит и как работает. Остальные мало чем отличаются, и если разберетесь в одном, то разберетесь со всеми.

Препарировать будем completed_jobs.yaml.

---
# Общее описание.
# Создается объект типа Cleaner, который будет называться completed-jobs. Он предназначен для периодической очистки завершенных задач (Job) в кластере.
apiVersion: apps.projectsveltos.io/v1alpha1
kind: Cleaner
metadata:
  name: completed-jobs
# Расписание выполнения.
# Поле schedule задает cron-выражение, по которому будет запускаться процесс очистки.
# В данном случае каждый день в 00:00 (полночь).
spec:
  schedule: "* 0 * * *"
# Что и как ищется.
# Здесь определяется, какие ресурсы нужно удалять:
# 1. Ищутся ресурсы типа Job из API-группы batch/v1.
# 2. Для каждого такого ресурса выполняется Lua-функция evaluate(), которая проверяет:
#   a. Есть ли у задачи статус (status).
#   b. Успешно ли она завершена (completionTime != nil и succeeded > 0).
# Если оба условия выполнены, задача считается подходящей для удаления.
resourcePolicySet:
  resourceSelectors:
    - kind: Job
      group: "batch"
      version: v1
      evaluate: |
        function evaluate()
          hs = {}
          hs.matching = false
          if obj.status ~= nil then
            if obj.status.completionTime ~= nil and obj.status.succeeded > 0 then
              hs.matching = true
            end
          end
          return hs
        end
# Действие после выборки.
# Для всех найденных ресурсов (завершенных Job'ов) будет выполнено действие — удаление.
action: Delete
```

Планируем очистку и проверяем корректность

После короткого знакомства с K8s-cleaner перейдем к самому интересному — практике. Для начала нам понадобится сам кластер. Подойдет любой, который есть под рукой. Я же для своего удобства буду использовать облачные кластеры в Cloud.ru, поэтому сначала выполню три простых шага:

  1. Зарегистрируюсь в Cloud.ru Evolution. Если личный кабинет уже есть, достаточно просто зайти.

  2. Закажу себе кластер Kubernetes.

  3. Установлю туда сам K8s-cleaner.

Если у вас есть кластер в другом месте, выполните один из двух шагов ниже:

1. Этот вариант поможет скачать и применить Helm Chart без скачивания оного руками.

helm install k8s-cleaner oci://ghcr.io/gianlucam76/charts/k8s-cleaner \
    --version 0.18.0 \
    --namespace k8s-cleaner \
    --create-namespace

2. А этот позволит самостоятельно скачать Helm Chart с GitHub и при необходимости отредактировать его. Скачайте, перейдите в директорию и просто выполните команду.

helm install k8s-cleaner . -n k8s-cleaner --create-namespace

Любой из этих вариантов позволит получить свежую версию K8s-cleaner, и работать они будут одинаково хорошо. Заодно сразу отвечу на очевидный вопрос: когда я начинал разбираться с K8s-cleaner, его еще не было среди плагинов в Cloud.ru. Он появился только после успешного использования внутри команды.

После установки плагина давайте проверим состояние его Pods.

kubectl get pods -n k8s-cleaner

Если мы увидели аналогичный ответ, то приступаем к добавлению необходимых нам заданий.

NAME                           READY   STATUS    RESTARTS   AGE
k8s-cleaner-5f887cb4dc-vcxh9   1/1     Running   0          8m54s

Следом применяем нужные нам ресурсы Cleaner.

kubectl apply -f pods-with-expired-certificates.yaml pod-with-error-state.yml too-many-restarts.yaml orphaned_secrets.yaml completed_pods.yaml unused_pod-disruption-budgets.yaml unused_persistent-volume-claims.yaml unbound_persistent-volumes.yaml long-running-pods.yaml completed_jobs.yaml orphaned_deployment.yaml deployment_with_replica_zero.yaml orphaned_configmaps.yaml statefulset_with_no_replicas.yaml -n k8s-cleaner

И проверим, что они успешно применены.

kubectl get cleaner -n k8s-cleaner

Ответ мы должны получить примерно такой.

NAME             AGE
completed-jobs   117s
completed-pods   109s
…

А теперь давайте проверим, как это все будет работать. Приводить пример работы каждого CRD я не буду, но один конкретный рассмотрим. Для этого создадим Job, которая после выполнения не удалится и оставит после себя Pod в состоянии Complete, и назовем ее job_for_test.yaml.

---
apiVersion: batch/v1
kind: Job
metadata:
  name: persistent-success-job
spec:
  backoffLimit: 0
  ttlSecondsAfterFinished: null
  completions: 1
  parallelism: 1
  template:
    spec:
      containers:
      - name: job-container
        image: alpine:latest
        command:
        - /bin/sh
        - -c
        - |
          echo "Job started"
          echo "Working..."
          sleep 10
          echo "Work completed successfully"
          exit 0
      restartPolicy: OnFailure  

Применяем Job.

kubectl apply -f job_for_test.yaml

Ждем некоторое время и проверяем состояние.

kubectl get jobs
NAME                     STATUS     COMPLETIONS   DURATION   AGE
persistent-success-job   Complete   1/1           17s        4m55s

Осталось подождать, когда отработает планировщик, и проверить заново.

kubectl get jobs
No resources found in default namespace.

Мы видим, что Job отработала и осталась в состоянии Complete, но через некоторое время была удалена. Что, несомненно, радует.

Что в итоге

В итоге хочу сразу же сказать, что K8s-cleaner — не серебряная пуля, которая после helm install решит все ваши проблемы по щелчку пальцев. Это скорее швейцарский нож, который при должном умении будет вам верным другом. Вторым пунктом снова хочу предупредить, что на продовых кластерах нужно быть очень аккуратным. Если применять CRD, не понимая точно, что он должен делать, то однажды можете лишиться нужных ресурсов. Например, Job, которая применяет миграцию на БД в течение нескольких часов, а вы ее придушили уже через час.

В будущем планирую продолжить исследование возможностей K8s-cleaner и других инструментов, чтобы облегчить сопровождение кластеров Kubernetes. Также буду рад услышать о вашем опыте и проблемах, связанных с поддержанием чистоты и сопровождения в Kubernetes-окружении.

Спасибо за внимание, и надеюсь, кому-то статья спасла несколько вечеров для поиска решения аналогичной проблемы!

Комментарии (2)


  1. sm1ly
    21.11.2025 03:19

    попробуйте argocd или козий стек, там это все под капотом есть


  1. Cib0rg
    21.11.2025 03:19

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

    С ручным созданием манифестов при наличии хелма вообще не ясно - это системная проблема, которую вы вообще не решаете, подпирая тем же костылем.

    Короче, инструмент вроде интересный - для тех, у кого проблемы с освоением основного тулсета.