Одновременно с ростом кластера Kubernetes растет количество ресурсов, volume и других API-объектов. Рано или поздно вы упретесь в потолок, будь то etcd
, память или процессор. Зачем подвергать себя ненужной боли и проблемам, если можно установить простые — хотя и довольно изощренные — правила? Вы можете настроить автоматизацию и мониторинг, которые будут содержать кластер в аккуратном состоянии. В статье разберемся, как избавиться от лишних нагрузок, через которые утекают ресурсы, и устаревших накопившихся объектов.
Что будет в статье:
В чем проблема?
Несколько забытых подов, неиспользуемые volume, завершенные задачи или, возможно, старый ConfigMap/Secret — насколько все это важно? Все это просто где-то лежит и ждет, пока не понадобится.
Вообще-то этот хлам не сильно вредит до поры до времени. Но когда эти штуки накапливаются, то начинают влиять на производительность и стабильность кластера. Давайте посмотрим, какие проблемы могут вызвать эти забытые ресурсы.
Ограничение количества подов. Каждый кластер Kubernetes имеет несколько основных ограничений. Первое из них — количество подов на узел, которое по документации не рекомендуется делать больше 110. При этом, если у вас достаточно мощные узлы с большим количеством памяти и CPU, вы можете увеличить это количество — возможно, даже до 500, как было протестировано на OpenShift. Но если вы достигнете этих пределов, не удивляйтесь, если что-то пойдет не так, и не только потому, что не хватает памяти или мощности процессора.
Нехватка хранилища ephemeral storage. Все запущенные поды на узле используют по крайней мере часть этого пространства для логов, кэша, рабочего пространства или emptyDir волюмов. Вы можете достичь предела довольно быстро, что приведет к вытеснению уже существующих подов, или невозможности создания новых. Запуск слишком большого количества подов на узле тоже может способствовать этой проблеме: ephemeral storage используется для образов контейнеров и их записываемых слоев. Если узел достигает предела хранилища, он становится нерабочим (на него будет применен taint), о чем вы узнаете довольно быстро. Если вы хотите узнать текущее состояние диска на узле, запустите на нем df -h /var/lib.
Лишние расходы на PVC. Схожим образом источником проблем могут стать persistent volume, особенно если вы запускаете Kubernetes в облаке и платите за каждый предоставленный PVC. Очевидно, что для экономии денег необходимо чистить неиспользуемые PVC. Содержание используемых PVC чистыми тоже важно, потому что позволяет избежать нехватки места для ваших приложений. Особенно, если вы запускаете базы данных в кластере.
Низкая производительность etcd. Еще один источник проблем — чрезмерное количество объектов, поскольку все они находятся в хранилище etcd
. По мере роста количества данных в etcd
, его производительность может начать снижаться. Этого нужно стараться не допускать всеми силами: etcd
— мозг кластера Kubernetes. Учитывая вышесказанное, чтобы упереться в etcd,
вам понадобится действительно большой кластер, как продемонстрировано в этом посте OpenAI. В то же время нет какой-то единой метрики для замера производительности etcd
, потому что она зависит от количества объектов, их размеров и частоты использования. Так что наилучшим выходом будет профилактика: простое сохранение чистоты и порядка. В противном случае вас могут ждать весьма неприятные сюрпризы.
Нарушение границ безопасности. Наконец, мусор в кластере может стать источником проблем сам по себе. Не забывайте подчищать Role Bindings и учетные записи (Service Account), когда ими никто не пользуется.
Основы
Для решения большинства этих проблем не нужно делать ничего особенно сложного. Лучшим выбором будет совсем не допускать их. Один из вариантов превентивных мер — использование объектных квот, которые вы, как администратор кластера, можете применять в каждом отдельном неймспейсе.
Первое, что можно решить с помощью квот — количество и размер PVC:
apiVersion: v1
kind: LimitRange
metadata:
name: pvc-limit
spec:
limits:
- type: PersistentVolumeClaim
max:
storage: 10Gi
min:
storage: 1Gi
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: pvc-quota
spec:
hard:
persistentvolumeclaims: "10"
requests.storage: "10Gi"
# сумма запрошенного хранилища в bronze storage class не может превышать 5 Гб
bronze.storageclass.storage.k8s.io/requests.storage: "5Gi"
Выше мы имеем два объекта.
LimitRange устанавливает минимальный и максимальный размеры PVC в неймспейсе. Это оградит пользователей от запрашивания слишком больших объемов.
ResourceQuota дополнительно обеспечивает жесткое ограничение на количество PVC и их совокупный размер.
Затем вы можете предотвратить создание кучи объектов и оставление их в качестве мусора после использования. Для этого используйте object count, квоты на количество объектов, которые зададут жесткое ограничение на количество объектов определенного типа в конкретном неймспейсе:
apiVersion: v1
kind: ResourceQuota
metadata:
name: object-count-quota
spec:
hard:
configmaps: "2"
secrets: "10"
services: "5"
count/jobs.batch: "8"
Есть несколько встроенных полей, через которые вы можете задать квоты на количество объектов. Например, configmaps
, secrets
или services
, показанные выше. Для всех прочих ресурсов можно использовать формат count/<resource>.<group>
, как показано в примере с count/jobs.batch
, что может помочь от бесконтрольного создания джоб из-за неправильно настроенного CronJob.
Вероятно, большинству известно о функции установки квот на память и CPU. Но, возможно, для вас станет новостью квота ephemeral storage. Альфа-поддержка квот для эфемерного хранилища была добавлена в v1.18 и дала возможность установить границы ephemeral storage так же, как как для памяти и процессора.
apiVersion: v1
kind: ResourceQuota
metadata:
name: ephemeral-storage-quota
spec:
hard:
requests.ephemeral-storage: 1Gi
limits.ephemeral-storage: 2Gi
Однако будьте осторожны с этой настройкой. Поды могут быть вытеснены из-за превышения лимита, что может быть вызвано слишком большим размером логов контейнера.
Помимо установки квот и границ ресурсов, можно установить ограничение для истории ревизий Deployment — для снижения количества репликасетов, хранящихся в кластере. Для этого используйте .spec.revisionHistoryLimit,
который по умолчанию равен 10.
Наконец, вы можете установить время жизни (TTL) для очистки кластера от объектов, которые существуют там слишком долго. Эта процедура использует TTL-контроллер, который находится в стадии бета-тестирования с версии v1.21, и в настоящее время работает только для задач, использующих поле .spec.ttlSecondsAfterFinished.
В будущем, возможно, он будет расширен на другие ресурсы, например, поды.
Ручная очистка
Если превентивных мер уже недостаточно, потому что у вас накопилось достаточно неиспользуемых ресурсов, вы можете попробовать разовое удаление. Это делается просто с помощью kubectl get
и kubectl delete
. Пара основных примеров того, что вы можете сделать:
kubectl delete all -l some-label=some-value # Delete based on label
kubectl delete pod $(kubectl get pod -o=jsonpath='{.items[?(@.status.phase=="Succeeded")].metadata.name}') # Delete all "Succeeded" Pods
Первая команда выполняет базовое удаление с использованием лейблов ресурсов. Поэтому этот метод потребует от вас предварительно пометить все подлежащие удалению объекты некоторой парой ключ-значение.
Вторая показывает, как вы можете удалить тип ресурсов, основанный на некотором поле, как правило, каком-то поле статуса. В примере выше это будут все завершенные/успешные поды. Этот вариант можно применить и к другим ресурсам, например, завершенным задачам.
Кроме этих двух команд, довольно сложно найти шаблон, который помог бы разом очистить кластер от хлама. Поэтому вам придется специально поискать конкретные неиспользуемые ресурсы.
Могу посоветовать такой инструмент, как k8spurger. Он ищет неиспользуемые объекты вроде RoleBinding, ServiceAccounts, ConfigMaps и создает список ресурсов-кандидатов на удаление. Это поможет сузить круг поиска.
Kube-janitor
В разделах выше мы рассмотрели несколько вариантов простой очистки для конкретных случаев. Но лучшим решением для наведения порядка в любом кластере будет использование kube-janitor. Этот инструмент работает в вашем кластере так же, как любая другая рабочая нагрузка, и использует JSON-запросы для поиска ресурсов, которые можно удалить на основе TTL или истечения срока действия.
Для развертывания kube-janitor на вашем кластере запустите:
git clone https://codeberg.org/hjacobs/kube-janitor.git
cd kube-janitor
kubectl apply -k deploy/
Это разложит kube-janitor в default namespace и запустит его с правилами по умолчанию в пробном режиме с использованием флага --dry-run.
Перед отключением dry-run нужно установить собственные правила. Они лежат в config map kube-janitor, которая выглядит примерно так:
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-janitor
namespace: default
data:
rules.yaml: |-
rules:
...
Конечно, для нас самый интересный раздел здесь — правила, rules
. Вот несколько полезных примеров, которые вы можете использовать для очистки своего кластера:
rules:
# Удаление Jobs в development namespaces после 2 дней.
- id: remove-old-jobs
resources:
- jobs
jmespath: "metadata.namespace == 'development'"
ttl: 2d
# Удаление тех подов в development namespaces, которые не в состоянии выполнения (Failed, Completed).
- id: remove-non-running-pods
resources:
- pods
jmespath: "(status.phase == 'Completed' || status.phase == 'Failed') && metadata.namespace == 'development'"
ttl: 2h
# Удаление всех PVC, которые не использует ни один под
- id: remove-unused-pvcs
resources:
- persistentvolumeclaims
jmespath: "_context.pvc_is_not_mounted"
ttl: 1d
# Удаление всех Deployments, чье имя начинается с 'test-'
- id: remove-test-deployments
resources:
- deployments
jmespath: "starts_with(metadata.name, 'test-')"
ttl: 1d
# Удаление всех ресурсов в playground namespace через неделю
- id: remove-test-deployments
resources:
- "*"
jmespath: "metadata.namespace == 'playground'"
ttl: 7d
Этот пример показывает несколько базовых вариантов настройки для очистки от временных, устаревших или неиспользуемых ресурсов. Помимо правил такого рода, можно установить абсолютные дату/время истечения срока действия для конкретных объектов.
Это можно сделать с помощью аннотаций, например:
apiVersion: v1
kind: Namespace
metadata:
annotations:
# будет удалено 18.6.2021 в полночь
janitor/expires: "2021-06-18"
name: temp
spec: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
# будет удалено 20.6.2021 в 17:30
janitor/expires: "2021-06-20T17:30:00Z"
name: nginx
spec:
replicas: 1
...
Когда закончите устанавливать правила и аннотации, вам стоит дать kube-janitor поработать какое-то время в dry-run режиме с включенными логами отладки. Это мера предосторожности, чтобы инструмент не удалил то, что удалять было не нужно.
Другими словами, не обвиняйте меня, если сотрете production волюмы из-за неправильной конфигурации и отсутствия тестирования.
Наконец, при использовании kube-janitor нужно учитывать его потребности в ресурсах. Если в кластере много объектов, ему может потребоваться больше памяти, чем выделенные по умолчанию 100 Мб. Чтобы его под не застревал в CrashLoopBackOff, я выставляю ему лимит 1 Гб.
Мониторинг ограничений кластера
Не все проблемы можно решить ручной или даже автоматической очисткой. В некоторых случаях мониторинг будет лучшим выбором для обеспечения уверенности в том, что вы не упираетесь в лимиты кластера — будь то количество подов, доступное ephemeral storage или количество объектов в etcd
.
Мониторинг — это огромная тема, которая требует отдельной статьи, а то и нескольких. Поэтому для сегодняшних целей я просто перечислю несколько Prometheus-метрик, которые могут оказаться полезными для поддержания порядка в кластере:
etcd_db_total_size_in_bytes
— размер базы данныхetcd
etcd_object_counts
— количество объектов вetcd
pod:container_cpu_usage:sum
— использование CPU на каждый под в кластереpod:container_fs_usage_bytes:sum
— использование файловой системы на каждый под в кластереpod:container_memory_usage_bytes:sum
— использование памяти на каждый под в кластереnode_memory_MemFree_bytes
— свободная память на каждом узлеnamespace:container_memory_usage_bytes:sum
— использование памяти по неймспейсамnamespace:container_cpu_usage:sum
— загрузка CPU на каждый namespacekubelet_volume_stats_used_bytes
— используемое место по каждому волюмуkubelet_running_pods
— количество запущенных подов на узлеkubelet_container_log_filesystem_used_bytes
— размер логов на каждый контейнер/подkube_node_status_capacity_pods
— рекомендованный максимум подов на узелkube_node_status_capacity
— максимум для всех метрик (CPU, поды, ephemeral storage, память, hugepages)
Это только некоторые метрики из тех, которые вы можете использовать. Какие из них будут доступны, зависит также от ваших инструментов мониторинга, т.е. вы можете использовать какие-то специальные метрики, доступные на вашем сервисе.
Заключение
Мы рассмотрели несколько вариантов очистки кластера Kubernetes — некоторые совсем простые, а некоторые посложнее. Независимо от того, что вы выберете, старайтесь не забывать делать уборку в кластере и сохранять порядок. Это может спасти вас от большой головной боли и, по крайней мере, избавит от ненужного хлама в кластере. В конечном счете такая уборка служит тем же целям, что наведение порядка на рабочем столе.
Также имейте ввиду, что если вы оставляете объекты лежать без использования долгое время, вы просто забудете, зачем они там. Это может сильно осложнить понимание, что должно существовать, а что — нет.
Помимо описанных здесь подходов, вы также можете использовать какое-либо решение GitOps — например, ArgoCD или Flux для создания ресурсов и управления ими, что может значительно упростить их очистку. Обычно потребуется удалить только один кастомный ресурс, что вызовет каскадное удаление всех зависимых ресурсов.
От переводчиков
Если хотите научиться девопсу, посмотрите программу нашего курса «Деплой приложений в Kubernetes».
Почитать подробнее можно тут.
Другие статьи про DevOps для начинающих:
Другие статьи про DevOps для продолжающих: