Garbage Collection в Kubernetes: основы

от автора

Привет, Хабр! Сегодня мы рассмотрим механизмы garbage collection в Kubernetes: как удалять orphaned pods, утилизировать устаревшие данные и управлять томами.

Garbage Collection в Kubernetes — это автоматизированный процесс очистки неиспользуемых ресурсов, который предотвращает засорение кластера «мусором». Без GC кластер может превратиться в лабиринт забытых подов, устаревших ConfigMaps и ненужных томов, что очевидно приведет к снижению производительности и увеличению затрат.

Основные компоненты Garbage Collection в Kubernetes

Kubernetes GC заботится о трех основных вещах:

  1. Orphaned Pods: поды без контроллеров.

  2. Stale Data: устаревшие данные, которые больше не нужны.

  3. Volumes: ненужные тома, занимающие место.

Orphaned Pod

Orphaned Pods — это поды, которые утратили своих контроллеров, будь то ReplicaSet, Deployment или другой контроллер. Например, если контроллер внезапно упал или был удален, его поды остаются как «бездомные».

Напишем скрипт, который найдет и удалит orphaned pods. Будем использовать kubectl, jq и немного bash.

#!/bin/bash  # Проверяем, установлен ли jq if ! command -v jq &> /dev/null; then     echo "jq не установлен. Пожалуйста, установите jq для выполнения скрипта."     exit 1 fi  # Получаем все поды в кластере echo "Получаем список всех подов в кластере..." PODS=$(kubectl get pods --all-namespaces -o json)  # Фильтруем поды без ownerReferences echo "Фильтруем orphaned pods..." ORPHANED_PODS=$(echo "$PODS" | jq -r '.items[] | select(.metadata.ownerReferences | length == 0) | "\(.metadata.namespace) \(.metadata.name)"')  # Проверяем, есть ли orphaned pods if [ -z "$ORPHANED_PODS" ]; then     echo "Orphaned pods не найдены. Отличная работа!"     exit 0 fi  # Удаляем каждый orphaned pod echo "Начинаем удаление orphaned pods..." while read -r namespace pod; do     echo "Удаляем orphaned pod: $namespace/$pod"     kubectl delete pod "$pod" -n "$namespace" --grace-period=30 --timeout=30s || {         echo "Не удалось удалить pod: $namespace/$pod"     } done <<< "$ORPHANED_PODS"  echo "Очистка orphaned pods завершена." 

Теперь автоматизируем этот процесс, настроив CronJob, который будет выполнять скрипт каждый час.

apiVersion: batch/v1 kind: CronJob metadata:   name: cleanup-orphaned-pods spec:   schedule: "0 * * * *" # Каждый час   jobTemplate:     spec:       template:         spec:           serviceAccountName: gc-cleanup-sa           containers:           - name: cleanup             image: bitnami/kubectl:latest             command:               - /bin/bash               - -c               - |                 # Ваш скрипт здесь                 PODS=$(kubectl get pods --all-namespaces -o json)                 ORPHANED_PODS=$(echo $PODS | jq -r '.items[] | select(.metadata.ownerReferences | length == 0) | "\(.metadata.namespace) \(.metadata.name)"')                 if [ -z "$ORPHANED_PODS" ]; then                     echo "Orphaned pods не найдены. Отличная работа!"                     exit 0                 fi                 while read -r namespace pod; do                     echo "Удаляем orphaned pod: $namespace/$pod"                     kubectl delete pod "$pod" -n "$namespace" --grace-period=30 --timeout=30s                 done <<< "$ORPHANED_PODS"           restartPolicy: OnFailure

Чтобы CronJob мог удалять поды, необходимо настроить соответствующие права доступа.

--- apiVersion: v1 kind: ServiceAccount metadata:   name: gc-cleanup-sa   namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata:   name: gc-cleanup-role   namespace: default rules: - apiGroups: [""]   resources: ["pods", "configmaps", "persistentvolumes"]   verbs: ["get", "list", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata:   name: gc-cleanup-rb   namespace: default subjects: - kind: ServiceAccount   name: gc-cleanup-sa   namespace: default roleRef:   kind: Role   name: gc-cleanup-role   apiGroup: rbac.authorization.k8s.io

Утилизация Stale Data

Stale Data — это данные, которые больше не используются, но продолжают занимать место. Это могут быть старые ConfigMaps, Secrets, логи или временные файлы.

Создадим скрипт для удаления ConfigMaps старше 30 дней.

#!/bin/bash  # Проверяем, установлен ли jq if ! command -v jq &> /dev/null then     echo "jq не установлен. Пожалуйста, установите jq для выполнения скрипта."     exit 1 fi  # Устанавливаем пороговое время (30 дней назад) THRESHOLD=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ) echo "Удаляем ConfigMaps, созданные до $THRESHOLD..."  # Получаем все ConfigMaps CONFIGMAPS=$(kubectl get configmaps --all-namespaces -o json)  # Фильтруем ConfigMaps, созданные раньше порога OLD_CONFIGMAPS=$(echo $CONFIGMAPS | jq -r --arg THRESHOLD "$THRESHOLD" '.items[] | select(.metadata.creationTimestamp < $THRESHOLD) | "\(.metadata.namespace) \(.metadata.name)"')  # Проверяем, есть ли старые ConfigMaps if [ -z "$OLD_CONFIGMAPS" ]; then     echo "Старые ConfigMaps не найдены. Отличная работа!"     exit 0 fi  # Удаляем старые ConfigMaps с подтверждением while read -r namespace configmap; do     echo "Удаляем ConfigMap: $namespace/$configmap"     kubectl delete configmap "$configmap" -n "$namespace" --grace-period=30 --timeout=30s     if [ $? -ne 0 ]; then         echo "Не удалось удалить ConfigMap: $namespace/$configmap"     else         echo "ConfigMap $namespace/$configmap успешно удален."     fi done <<< "$OLD_CONFIGMAPS"  echo "Очистка старых ConfigMaps завершена."

Настроим CronJob для автоматической очистки ConfigMaps.

apiVersion: batch/v1 kind: CronJob metadata:   name: cleanup-configmaps spec:   schedule: "0 2 * * 0" # Каждое воскресенье в 02:00   jobTemplate:     spec:       template:         spec:           serviceAccountName: gc-cleanup-sa           containers:           - name: cleanup             image: bitnami/kubectl:latest             command:               - /bin/bash               - -c               - |                 THRESHOLD=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)                 CONFIGMAPS=$(kubectl get configmaps --all-namespaces -o json)                 OLD_CONFIGMAPS=$(echo $CONFIGMAPS | jq -r --arg THRESHOLD "$THRESHOLD" '.items[] | select(.metadata.creationTimestamp < $THRESHOLD) | "\(.metadata.namespace) \(.metadata.name)"')                 if [ -z "$OLD_CONFIGMAPS" ]; then                     echo "Старые ConfigMaps не найдены. Отличная работа!"                     exit 0                 fi                 while read -r namespace configmap; do                     echo "Удаляем ConfigMap: $namespace/$configmap"                     kubectl delete configmap "$configmap" -n "$namespace" --grace-period=30 --timeout=30s                 done <<< "$OLD_CONFIGMAPS"           restartPolicy: OnFailure

Управление Volumes

Volumes могут стать настоящей головной болью, если не управлять ими должным образом. Старые PersistentVolumes и PersistentVolumeClaims, которые больше не нужны, продолжают занимать место и ресурсы.

Настроим StorageClass с reclaimPolicy: Delete, чтобы при удалении PVC автоматически удалялся связанный PV и физический том.

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata:   name: standard provisioner: kubernetes.io/aws-ebs parameters:   type: gp2 reclaimPolicy: Delete

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

#!/bin/bash  # Проверяем, установлен ли jq if ! command -v jq &> /dev/null then     echo "jq не установлен. Пожалуйста, установите jq для выполнения скрипта."     exit 1 fi  echo "Получаем список всех PVs..." PVS=$(kubectl get pv -o json)  # Находим PV без связанных PVC UNBOUND_PVS=$(echo $PVS | jq -r '.items[] | select(.spec.claimRef == null) | .metadata.name')  # Проверяем, есть ли незакрепленные PV if [ -z "$UNBOUND_PVS" ]; then     echo "Незакрепленные PV не найдены. Отличная работа!"     exit 0 fi  # Удаляем незакрепленные PV for pv in $UNBOUND_PVS; do     echo "Удаляем незакрепленный PV: $pv"     kubectl delete pv "$pv" --grace-period=30 --timeout=30s     if [ $? -ne 0 ]; then         echo "Не удалось удалить PV: $pv"     else         echo "PV $pv успешно удален."     fi done  echo "Очистка незакрепленных PV завершена."

Настроим CronJob для автоматической очистки PV

apiVersion: batch/v1 kind: CronJob metadata:   name: cleanup-unbound-pvs spec:   schedule: "0 3 * * *" # Каждый день в 03:00   jobTemplate:     spec:       template:         spec:           serviceAccountName: gc-cleanup-sa           containers:           - name: cleanup             image: bitnami/kubectl:latest             command:               - /bin/bash               - -c               - |                 PVS=$(kubectl get pv -o json)                 UNBOUND_PVS=$(echo $PVS | jq -r '.items[] | select(.spec.claimRef == null) | .metadata.name')                 if [ -z "$UNBOUND_PVS" ]; then                     echo "Незакрепленные PV не найдены. Отличная работа!"                     exit 0                 fi                 for pv in $UNBOUND_PVS; do                     echo "Удаляем незакрепленный PV: $pv"                     kubectl delete pv "$pv" --grace-period=30 --timeout=30s                 done           restartPolicy: OnFailure

OwnerReferences и Finalizers

Теперь рассмотрим как Kubernetes решает, что удалять, а что оставлять. Основные механизмы здесь — это ownerReferences(использовали в коде выше) и finalizers.

OwnerReferences

ownerReferences — это механизм, позволяющий Kubernetes отслеживать зависимости между объектами. Например, ReplicaSet владеет подами, а Deployment владеет ReplicaSet. Если объект-родитель удаляется, Kubernetes GC автоматически удалит все объекты-потомки.

Связывание Pod с ReplicaSet через ownerReferences:

apiVersion: apps/v1 kind: ReplicaSet metadata:   name: frontend spec:   replicas: 3   selector:     matchLabels:       app: frontend   template:     metadata:       labels:         app: frontend     spec:       containers:       - name: frontend         image: nginx --- apiVersion: v1 kind: Pod metadata:   name: frontend-pod   ownerReferences:   - apiVersion: apps/v1     kind: ReplicaSet     name: frontend     uid: <replicaset-uid> spec:   containers:   - name: frontend     image: nginx

Когда ReplicaSet frontend удаляется, Kubernetes автоматом удалит все поды, связанные с ним через ownerReferences.

Finalizers

finalizers — это способ задержать удаление объекта до выполнения определённых действий

Предположим, есть Custom Resource, и хочется выполнить некоторые действия перед его удалением:

apiVersion: mydomain.com/v1 kind: MyResource metadata:   name: my-resource   finalizers:   - mydomain.com/finalizer spec:   # спецификация ресурса

Когда вы удаляете этот ресурс, Kubernetes сначала добавляет его в статус deletionTimestamp, но не удаляет до тех пор, пока все finalizers не будут удалены. Контроллер должен отследить это и выполнить необходимые действия:

func (r *MyResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {     var resource myv1.MyResource     if err := r.Get(ctx, req.NamespacedName, &resource); err != nil {         return ctrl.Result{}, client.IgnoreNotFound(err)     }      if !resource.ObjectMeta.DeletionTimestamp.IsZero() {         // Выполняем очистку         if err := cleanup(resource); err != nil {             return ctrl.Result{}, err         }         // Удаляем finalizer         resource.ObjectMeta.Finalizers = remove(resource.ObjectMeta.Finalizers, "mydomain.com/finalizer")         if err := r.Update(ctx, &resource); err != nil {             return ctrl.Result{}, err         }         return ctrl.Result{}, nil     }      // Основная логика контроллера     return ctrl.Result{}, nil }

Как оптимизировать все это дело

Garbage Collection может быть ресурсоемким, если не оптимизировать.

Reclaim Policies

reclaimPolicy определяет, что делать с PV при удалении PVC. Есть два варианта:

  • Delete: автоматически удаляет физический том при удалении PVC.

  • Retain: оставляет физический том, чтобы его можно было повторно использовать.

Настроим Reclaim Policy:

apiVersion: storage.k8s.io/v1 kind: PersistentVolume metadata:   name: pv-example spec:   capacity:     storage: 10Gi   accessModes:     - ReadWriteOnce   persistentVolumeReclaimPolicy: Delete   storageClassName: standard   awsElasticBlockStore:     volumeID: <volume-id>     fsType: ext4 

Юзаем Delete для временных или легко восстанавливаемых данных, и Retain для критически важных данных. Главное — понимать, что именно нужно и как долго хотите хранить данные.

TTL Controllers

TTL Controllers позволяют автоматически удалять объекты через заданное время жизни.

Настроим TTL для Jobs:

apiVersion: batch/v1 kind: Job metadata:   name: example-job   ttlSecondsAfterFinished: 3600 # Удаление через 1 час spec:   template:     spec:       containers:       - name: job         image: busybox         command: ["sleep", "60"]       restartPolicy: Never

Так кластер не будет засоряться старыми Jobs после их выполнения.

Параллельная очистка

Если у вас большой кластер, разбивайте задачи очистки на параллельные части. Например, можно использовать несколько CronJobs для разных типов ресурсов.

Разделение очистки ConfigMaps и PVCs:

# CronJob для ConfigMaps apiVersion: batch/v1 kind: CronJob metadata:   name: cleanup-configmaps spec:   schedule: "0 2 * * 0"   jobTemplate:     spec:       template:         spec:           serviceAccountName: gc-cleanup-sa           containers:           - name: cleanup             image: bitnami/kubectl:latest             command:               - /bin/bash               - -c               - |                 THRESHOLD=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)                 CONFIGMAPS=$(kubectl get configmaps --all-namespaces -o json)                 OLD_CONFIGMAPS=$(echo $CONFIGMAPS | jq -r --arg THRESHOLD "$THRESHOLD" '.items[] | select(.metadata.creationTimestamp < $THRESHOLD) | "\(.metadata.namespace) \(.metadata.name)"')                 if [ -z "$OLD_CONFIGMAPS" ]; then                     echo "Старые ConfigMaps не найдены. Отличная работа!"                     exit 0                 fi                 while read -r namespace configmap; do                     echo "Удаляем ConfigMap: $namespace/$configmap"                     kubectl delete configmap "$configmap" -n "$namespace" --grace-period=30 --timeout=30s                 done <<< "$OLD_CONFIGMAPS"           restartPolicy: OnFailure  # CronJob для PVCs apiVersion: batch/v1 kind: CronJob metadata:   name: cleanup-unbound-pvs spec:   schedule: "0 3 * * *"   jobTemplate:     spec:       template:         spec:           serviceAccountName: gc-cleanup-sa           containers:           - name: cleanup             image: bitnami/kubectl:latest             command:               - /bin/bash               - -c               - |                 PVS=$(kubectl get pv -o json)                 UNBOUND_PVS=$(echo $PVS | jq -r '.items[] | select(.spec.claimRef == null) | .metadata.name')                 if [ -z "$UNBOUND_PVS" ]; then                     echo "Незакрепленные PV не найдены. Отличная работа!"                     exit 0                 fi                 for pv in $UNBOUND_PVS; do                     echo "Удаляем незакрепленный PV: $pv"                     kubectl delete pv "$pv" --grace-period=30 --timeout=30s                 done           restartPolicy: OnFailure

Заключение

Мы узнали, как Kubernetes сам чистит свой «мусор», удаляя забытые поды, старые ConfigMaps и ненужные тома. Благодаря этим настройкам кластер будет работать быстрее и без лишних затрат.

Если хотите углубиться дальше, посмотрите на Kubernetes Operators, освоите Helm Charts или изучите инструменты мониторинга типа Prometheus и Grafana. Все это поможет сделать ваш кластер еще более надежным.


Больше актуальных навыков по IT-инфраструктуре вы можете получить в рамках практических онлайн-курсов от экспертов отрасли.

А 17 декабря пройдет открытый урок, посвящённый внутреннему устройству Kubernetes. Вы узнаете, из каких ключевых компонентов состоит Kubernetes, как они взаимодействуют между собой и как настроить их для эффективного управления контейнерными приложениями. Если интересно, записывайтесь по ссылке.


ссылка на оригинал статьи https://habr.com/ru/articles/860928/