Контролируем удаление с финализаторами

от автора

image

В Kubernetes не так-то просто что-то удалить — вы уверены, что удалили объект, но оказывается, что он все еще присутствует в кластере. Вы, конечно, можете выполнять команду kubectl delete в повседневных операциях и надеяться на лучшее, но знание принципов работы delete команд в Kubernetes поможет вам понять, почему некоторые объекты остаются после удаления.

В этой статье мы рассмотрим:

  • Какие свойства ресурса влияют на удаление.
  • Как финализаторы и ссылки на родителя-владельца управляют удалением объектов.
  • Как можно использовать propagationPolicy, чтобы изменить порядок удаления.
  • Как работает удаление, с примерами.

Для простоты во всех примерах мы используем ConfigMaps и базовые команды kubectl. Мы увидим, как работают эти команды, и обсудим результаты их использования на практике.

Базовая команда delete

В Kubernetes есть несколько команд для создания, чтения, обновления и удаления объектов. Здесь мы поговорим о четырех командах kubectl: create, get, patch и delete.
Примеры базовой команды kubectl delete:

kubectl create configmap mymap configmap/mymap created

kubectl get configmap/mymap NAME    DATA   AGE mymap   0      12s

kubectl delete configmap/mymap configmap "mymap" deleted

kubectl get configmap/mymap Error from server (NotFound): configmaps "mymap" not found

Перед командой стоит знак $, далее следует ее результат. Как видите, мы начали с kubectl create configmap mymap, чтобы создать пустой configmap mymap. Теперь нужно выполнить get и убедиться, что он существует. Затем мы удалили configmap. Если снова сделать get, получим ошибку HTTP 404, потому что configmap не найден.

У команды deleteочень простая диаграмма состояний:

image
Диаграмма состояний для delete

Операция незамысловатая, но ей могут помешать некоторые факторы, включая финализаторы и ссылки на родителя-владельца.

Как работают финализаторы

Чтобы понять, как удаляются ресурсы в Kubernetes и почему они иногда все-таки не удаляются, полезно разобраться с финализаторами.

Финализаторы — это ключи в манифесте ресурса, отвечающие за операции, которые надо выполнить до удаления объекта. Они контролируют сборку мусора для ресурсов и сообщают контроллерам, какие операции очистки нужно выполнить до удаления ресурса. Они не обязательно обозначают код, который нужно выполнить. По сути, это просто списки ключей, вроде аннотаций. Как и аннотациями, ими также можно управлять.

Скорее всего, вы встречали следующие финализаторы:

  • kubernetes.io/pv-protection
  • kubernetes.io/pvc-protection

Они используются для постоянных томов и не дают случайно удалить их. Точно так же некоторые финализаторы могут использоваться для предотвращения удаления любого ресурса.

Ниже приводится configmap без свойств, но с финализатором:

cat <<EOF | kubectl create -f - apiVersion: v1 kind: ConfigMap metadata:   name: mymap   finalizers:   - kubernetes EOF

Контроллер ресурса configmap не понимает, что делать с ключом финализатора kubernetes. Такие финализаторы для configmap я называю «мертвыми», и обычно они используются с неймспейсами. Вот что происходит при попытке удалить configmap:

kubectl delete configmap/mymap & configmap "mymap" deleted jobs [1]+  Running kubectl delete configmap/mymap

Kubernetes сообщает, что объект удален, но на самом деле, в традиционном понимании, никуда он не удален. Скорее он находится в процессе удаления. Если мы снова выполним get, то узнаем, что объект изменен и теперь содержит временную метку удаления deletionTimestamp.

kubectl get configmap/mymap -o yaml apiVersion: v1 kind: ConfigMap metadata:   creationTimestamp: "2020-10-22T21:30:18Z"   deletionGracePeriodSeconds: 0   deletionTimestamp: "2020-10-22T21:30:34Z"   finalizers:   - kubernetes   name: mymap   namespace: default   resourceVersion: "311456"   selfLink: /api/v1/namespaces/default/configmaps/mymap   uid: 93a37fed-23e3-45e8-b6ee-b2521db81638

Если в двух словах, объект был изменен, но не удален. Все потому, что Kubernetes увидел, что у объекта есть финализаторы, и поместил его в состояние read-only. Временная метка удаления показывает, что объект можно только читать — а еще из него можно удалить апдейты ключа финализатора. Другими словами, удаление будет завершено, только когда мы удалим из объекта финализатор.

Удалить финализаторы можно с помощью команды patch. Фоновое удаление завершится, и объект будет удален. Если мы опять выполним get configmap, то ничего не увидим.

kubectl patch configmap/mymap \     --type json \     --patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]' configmap/mymap patched [1]+  Done  kubectl delete configmap/mymap  kubectl get configmap/mymap -o yaml Error from server (NotFound): configmaps "mymap" not found

Диаграмма состояния для финализации выглядит так:

image
Диаграмма состояний для финализации

Если мы попытаемся удалить объект с финализатором, он останется на этапе финализации, пока контроллер не удалит ключи финализаторов или финализаторы не будут удалены через Kubectl. Когда список финализаторов будет пуст, Kubernetes поместит его в очередь на удаление из реестра.

Ссылки на владельца

Ссылки на владельца (owner references) описывают отношения между группами объектов. Это свойства ресурсов, которые указывают, как они связаны друг с другом, чтобы можно было удалять целые деревья ресурсов.

Правила финализатора обрабатываются, если есть ссылки на владельца. Ссылки на владельца состоят из имени и UID. Они связывают ресурсы в одном неймспейсе и им нужен UID. Ссылки на владельца для подов обычно указывают на replica set, которому они принадлежат. Если удалить deploy или stateful set, дочерние replica set и pod тоже удалятся.

Посмотрим на примере, как работают ссылки на владельца. В первом примере мы сначала создаем родительский объект, а потом дочерний. В результате получаем простейший configmap со ссылками на владельца, то есть родителя:

cat <<EOF | kubectl create -f - apiVersion: v1 kind: ConfigMap metadata:   name: mymap-parent EOF CM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")  cat <<EOF | kubectl create -f - apiVersion: v1 kind: ConfigMap metadata:   name: mymap-child   ownerReferences:   - apiVersion: v1     kind: ConfigMap     name: mymap-parent     uid: $CM_UID EOF

Если мы удалим дочерний объект, в котором есть ссылка на владельца, родитель никуда не денется:

kubectl get configmap NAME           DATA   AGE mymap-child    0      12m4s mymap-parent   0      12m4s  kubectl delete configmap/mymap-child configmap "mymap-child" deleted  kubectl get configmap NAME           DATA   AGE mymap-parent   0      12m10s

В следующем случае мы воссоздали configmap из примера выше. Если мы удалим родителя, а не дочерний объект, в котором есть ссылка на него, а потом сделаем get для configmap, то увидим, что все configmap пропали:

kubectl get configmap NAME           DATA   AGE mymap-child    0      10m2s mymap-parent   0      10m2s  kubectl delete configmap/mymap-parent configmap "mymap-parent" deleted  kubectl get configmap No resources found in default namespace.

Получается, что если в дочернем объекте есть ссылка на родителя, он автоматически удаляется при удалении родителя. Это называется cascade. По умолчанию cascade имеет значение true, но можно указать —cascade=false в kubectl delete, чтобы удалить родительский объект, не трогая дочерние.

В следующем примере у нас есть родительский и дочерний объекты, а еще ссылки на владельца. Если мы удалим родителя, указав —cascade=false, дочерние объекты сохранятся:

kubectl get configmap NAME           DATA   AGE mymap-child    0      13m8s mymap-parent   0      13m8s  kubectl delete --cascade=false configmap/mymap-parent configmap "mymap-parent" deleted  kubectl get configmap NAME          DATA   AGE mymap-child   0      13m21s

Параметр —cascade указывает на политику распространения в API, которая позволяет менять порядок объектов удаления в дереве. В следующем примере используется отправка запроса к API через curl для удаления с фоновой политикой распространения (propagation policy):

kubectl proxy --port=8080 & Starting to serve on 127.0.0.1:8080  curl -X DELETE \   localhost:8080/api/v1/namespaces/default/configmaps/mymap-parent \   -d '{ "kind":"DeleteOptions", "apiVersion":"v1", "propagationPolicy":"Background" }' \   -H "Content-Type: application/json" {   "kind": "Status",   "apiVersion": "v1",   "metadata": {},   "status": "Success",   "details": { ... } }

Политику распространения нельзя указать в командной строке с помощью kubectl — только при прямом вызове API. Просто создайте прокси, чтобы иметь доступ к серверу API со своего компьютера, и выполните команду curl с одним URL, чтобы послать команду delete.

Есть три варианта политики распространения:

  • Foreground: дочерние объекты удаляются до родительского (обратный порядок).
  • Background: родительский объект удаляется до дочерних (прямой порядок).
  • Orphan: ссылки на владельца игнорируются.

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

Принудительное удаление неймспейса

В некоторых случаях для удаления неймспейсов может потребоваться принудительная финализация: если вы удалили неймспейс и очистили все объекты в нем, но неймспейс по-прежнему существует, его можно удалить принудительно, обновив его подресурс, finalize. Так контроллер неймспейса поймет, что нужно удалить финализатор и выполнить очистку:

cat <<EOF | curl -X PUT \   localhost:8080/api/v1/namespaces/test/finalize \   -H "Content-Type: application/json" \   --data-binary @- {   "kind": "Namespace",   "apiVersion": "v1",   "metadata": {     "name": "test"   },   "spec": {     "finalizers": null   } } EOF

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

Итоги

Как показывают примеры, финализаторы могут помешать удалению ресурсов в Kubernetes, особенно если речь о родительских и дочерних объектах. Обычно финализаторы добавляются в код не просто так, поэтому нужно все проверить, прежде чем удалять их вручную. С помощью ссылок на владельца можно указывать и удалять деревья ресурсов, хотя и в этом случае будут учитываться финализаторы. Наконец, можно использовать политику распространения, чтобы указывать порядок удаления в прямых вызовах API и контролировать процесс удаления объектов. Теперь вы чуть больше знаете о том, как работает удаление в Kubernetes, и можете поэкспериментировать самостоятельно в продакшен-кластере.

Видео от автора:

ссылка на оригинал статьи https://habr.com/ru/company/southbridge/blog/563320/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *