Привет, Хабр! Сегодня мы рассмотрим механизмы garbage collection в Kubernetes: как удалять orphaned pods, утилизировать устаревшие данные и управлять томами.
Garbage Collection в Kubernetes — это автоматизированный процесс очистки неиспользуемых ресурсов, который предотвращает засорение кластера «мусором». Без GC кластер может превратиться в лабиринт забытых подов, устаревших ConfigMaps и ненужных томов, что очевидно приведет к снижению производительности и увеличению затрат.
Основные компоненты Garbage Collection в Kubernetes
Kubernetes GC заботится о трех основных вещах:
-
Orphaned Pods: поды без контроллеров.
-
Stale Data: устаревшие данные, которые больше не нужны.
-
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/
Добавить комментарий