Garbage Collection в Kubernetes: основы
Привет, Хабр! Сегодня мы рассмотрим механизмы 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:
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:
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, как они взаимодействуют между собой и как настроить их для эффективного управления контейнерными приложениями. Если интересно, записывайтесь по ссылке.