В werf 1.2 для обновления ресурсов в Kubernetes мы использовали механизм под названием 3-way merge. Он достался нам от Helm 3, который мы использовали как подсистему развертывания. Хотя 3-way merge и решил часть проблем, существовавших в 2-way merge, многие проблемы, приводящие к некорректным обновлениям ресурсов, так и остались нерешёнными.
В werf 2.0 и Nelm мы пошли дальше и заменили 3-way merge на более современный механизм обновления ресурсов Kubernetes — Server-Side Apply. Он решает все проблемы 3-way merge и гарантирует корректные обновления ресурсов в кластере при развёртывании.
Подробнее о 3-way merge и 2-way merge читайте в нашей статье от 2019 года.
В этой статье мы расскажем, какие проблемы испытывают пользователи Helm 3 и как Server-Side Apply помогает их преодолеть.
Некорректные обновления ресурсов в Helm 3
Helm 3 и werf 1.2 используют 3-way merge (далее — 3WM) для обновления ресурсов в кластере. Но с 3WM можно нередко обнаружить, что развёрнутые ресурсы не соответствуют тому, что описано в Helm-чарте. Давайте попробуем воспроизвести подобный сценарий.
Допустим, у нас есть Helm-чарт с Deployment:
$ cat chart/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: main image: nginx
…и хуком Job:
$ cat chart/templates/job.yaml apiVersion: batch/v1 kind: Job metadata: name: myjob annotations: helm.sh/hook: "post-install,post-upgrade" spec: backoffLimit: 0 template: spec: restartPolicy: Never containers: - name: main image: alpine command: ["echo", "succeeded"]
Сделаем первый релиз, используя последнюю версию Helm 3:
$ helm upgrade --install myapp chart Release "myapp" has been upgraded. Happy Helming!
Теперь в Deployment чарта заменим один контейнер main на два — backend
и frontend
:
$ cat chart/templates/deployment.yaml ... containers: - name: backend image: nginx - name: frontend image: nginx
…и одновременно с этим «случайно» сломаем хук Job:
$ cat chart/templates/job.yaml ... containers: - name: main image: alpine command: ["fail"]
Делаем второй релиз, в этот раз ожидаемо неудачный:
$ helm upgrade --install myapp chart Error: UPGRADE FAILED: post-upgrade hooks failed: 1 error occurred: * job myjob failed: BackoffLimitExceeded
Поняв, что допустили ошибку в Job в чарте, исправим её:
$ cat chart/templates/job.yaml ... containers: - name: main image: alpine command: ["echo", "succeeded"]
…а заодно, решив, что у новых контейнеров в Deployment не очень удачные названия, переименуем их из backend
и frontend
в app
и proxy
:
$ cat chart/templates/deployment.yaml ... containers: - name: app image: nginx - name: proxy image: nginx
Делаем третий, успешный релиз:
$ helm upgrade --install myapp chart Release "myapp" has been upgraded. Happy Helming!
А теперь проверим, совпадают ли Deployment в чарте и Helm-релизе с Deployment в кластере:
$ cat chart/templates/deployment.yaml ... containers: # корректно - name: app - name: proxy $ helm get manifest myapp ... containers: # корректно - name: app - name: proxy $ kubectl get deploy myapp -oyaml ... containers: # некорректно - name: app - name: proxy - name: backend - name: frontend
…и обнаружим, что Deployment в чарте/релизе имеет два контейнера, а Deployment в кластере — почему-то четыре: два правильных контейнера app
и proxy
и два старых контейнера frontend
и backend
.
Новый релиз не поможет избавиться от ненужных контейнеров frontend
и backend
:
$ helm upgrade --install myapp chart $ kubectl get deploy myapp -oyaml ... containers: - name: app - name: proxy - name: backend - name: frontend
Rollback до самой первой ревизии тоже не поможет:
$ helm rollback myapp 1 $ kubectl get deploy myapp -oyaml ... containers: - name: main - name: backend - name: frontend
На этом этапе самый простой способ избавиться от ненужных контейнеров — это удалить их в кластере вручную через kubectl edit.
И этот случай не уникален — примерно то же самое регулярно происходит с большинством полей большинства ресурсов при развертывании с Helm 3. А триггером может выступать не только неудачный, но и отменённый релиз (когда Helm получил сигнал INT
, TERM
или KILL
).
Почему это происходит и что делать
Корень проблемы в том, что если каких-то полей ресурса нет в чарте, но они есть в кластере, то не так-то просто понять, должен Helm удалять эти поля или нет.
Но почему бы тогда просто не удалять всё, чего нет в манифесте ресурса в чарте? Да потому что Kubernetes или Kubernetes-операторы могут вносить в ресурс в кластере такие изменения, которые Helm никогда не должен удалять. Например, если Istio добавляет istio-proxy sidecar-контейнер в Deployment, Helm этот sidecar-контейнер удалять не должен, хотя его и нет в чарте.
Чтобы понять, что делать, Helm должен разделить «лишние» поля (те, которые есть только в ресурсе в кластере, но не в чарте) на «свои» и «чужие». «Свои» поля ему удалять можно, а «чужие» — нельзя. При использовании helm upgrade
«своими» полями считаются все поля, присутствующие в ресурсе нового релиза и предыдущего удачного релиза. Здесь обычно и возникает проблема: что будет, если предыдущий релиз был неудачным или отменённым, то есть был задеплоен не полностью, и при этом в нём произошло что-то важное, например появились новые «свои» поля? Это приведет к тому, что Helm некоторые «свои» поля начнёт расценивать как «чужие», и никогда не будет их удалять.
В итоге, чем чаще релизы заканчиваются неудачно или отменяются, тем больше «осиротевших» полей можно обнаружить на ресурсах в кластере. Иногда это что-то безобидное, а иногда это может быть что-то, приводящее к отказу в обслуживании или даже к порче/потере данных.
В рамках Helm простого решения для этой проблемы нет. Одним из способов могла бы быть новая схема Helm-релизов, где мы для каждого отдельного ресурса фиксировали бы его последнее применённое состояние. Но есть способ лучше — использование Server-Side Apply вместо 3WM.
Что такое Server-Side Apply
В Kubernetes 1.22 включена поддержка нового способа обновления ресурсов в кластере, который называется Server-Side Apply (далее — SSA). Давайте сравним обновление ресурсов через 3WM с обновлением через SSA.
Чтобы обновить ресурс через 3WM, нужно выполнить следующие шаги:
-
Взять манифест ресурса из последнего удачного релиза.
-
Взять манифест ресурса из чарта.
-
Взять манифест ресурса из кластера.
-
На основе этих трёх манифестов составить 3WM-патч.
-
Отправить в Kubernetes HTTP PATCH-запрос с 3WM-патчем.
Чтобы обновить ресурс через SSA, необходимо выполнить всего лишь две операции:
-
Взять манифест ресурса из чарта.
-
Отправить в Kubernetes HTTP PATCH-запрос с манифестом ресурса.
Преимущества SSA:
-
Простота использования.
-
Не нужен манифест ресурса из последнего релиза, который иногда может быть недостоверным.
-
Не надо вычислять, какие поля ресурса «свои», а какие нет — Kubernetes сам отслеживает это в поле
managedFields
ресурса. -
Обновление ресурса и сохранение информации о «своих» полях — это одна атомарная операция.
Если бы мы сумели заменить 3WM на SSA в Helm, нам бы больше не понадобилось обращаться к манифестам предыдущих релизов — за исключением случаев, когда нам необходимо понять, какие ресурсы надо удалить целиком, если они уже были удалены из чарта. Это бы полностью решило проблемы с «осиротевшими» полями в ресурсах в кластере.
Поддержка Server-Side Apply в Helm, werf и других инструментах
Flux, ArgoCD, kubectl/kustomize уже имеют поддержку SSA, хотя по умолчанию пока она включена только во Flux. За внедрение SSA в Helm 3 так никто и не взялся, даже несмотря на то, что поддержка SSA была уже в Kubernetes 1.16 (включалась через feature gate
), а в Kubernetes 1.22 она и вовсе была включена по умолчанию.
В werf 2.0 мы разработали и внедрили новый движок развёртывания Nelm, который пришёл на смену Helm 3. В Nelm, помимо всего прочего, мы полностью заменили 3WM на SSA. Внедрение SSA позволило решить ещё целый ряд проблем вроде этой (до сих пор существует в Helm, а появилась ещё в версии 2). Также SSA позволил реализовать несколько фич вроде автоматического сброса при новом релизе тех изменений ресурсов, которые были внесены вручную через kubectl edit
.
SSA в экспериментальном режиме был внедрён уже в werf 1.2 и эксплуатировался (в том числе в production) более года. Все пользователи werf 2.0 уже используют SSA по умолчанию, все проблемы 3WM исчезли, и на этом этапе мы рекомендуем werf 2.0 и SSA к использованию в production.
Для пользователей werf 1.2: миграция на werf 2.0 очень проста, и если не учитывать более строгую валидацию Helm-чартов, то в них почти ничего менять не нужно.
P. S.
Читайте также в нашем блоге:
ссылка на оригинал статьи https://habr.com/ru/articles/821665/
Добавить комментарий