← Все статьи

Меняем неизменяемое поле StatefulSet, не перезапустив ни одного пода

В Git — 20 GiB, в живом StatefulSet — шаблоны на 6 GiB, а Argo CD показывает Synced. Как удаление с orphan и OnDelete пересобрали шаблон без простоя.

В Optimacros GitOps-репозиторий объявлял MySQL InnoDB Cluster с шаблоном тома данных на 20 GiB и настроенным конфигом MySQL. Живой StatefulSet работал с шаблонами на 6 GiB и дефолтами binlog, включая хранение 30 дней. Argo CD всё это время показывал Synced и Healthy.

Почему дрейф был невидим

Оба поля действуют только в момент сборки StatefulSet.

  • spec.volumeClaimTemplates у StatefulSet неизменяем. Его нельзя пропатчить.
  • Оператор вписывает конфиг MySQL в конфигурацию подов при создании StatefulSet. Последующий sync его не перерисовывает.

selfHeal давал ложную уверенность. Он согласовывал кастомный ресурс — а в ресурсе действительно было 20 GiB. Но оператор не меняет (и не может менять) шаблон существующего StatefulSet и сам его не пересоздаёт. Существующие PVC тоже сохраняют свой размер. Так что и шаблон, и живые тома устарели, а все индикаторы GitOps выглядели идеально.

Две разные задачи

Помогло разделить это на две части: у них разные решения.

Существующие PVC. Их можно просто расширить вживую. Патч на storage: 20Gi и онлайн-расширение Longhorn сделали остальное (одному из них понадобилось больше работы, но это отдельная история).

Шаблон StatefulSet. Тут нужно пересоздать StatefulSet, чтобы он собрался заново из кастомного ресурса. Вопрос был в том, как сделать это с работающей базой без рестарта.

Orphan, пересоздание, adoption

kubectl delete sts <name> --cascade=orphan удаляет объект StatefulSet, но оставляет его поды работать. Новый StatefulSet с тем же селектором затем их подхватывает.

Ключом к работе без простоя оказалась стратегия обновления. При updateStrategy: OnDelete подхваченный под не перезапускается только из-за того, что шаблон изменился: поды обновляются, лишь когда кто-то их удалит. При RollingUpdate adoption увидит разницу в хеше шаблона и перекатит всех участников — такое делают в окно обслуживания, а не во вторник после обеда.

Последовательность:

  1. Сделать бэкап спецификации StatefulSet и убедиться, что updateStrategy — OnDelete. Записать controller-revision-hash и возраст каждого пода.
  2. Удалить StatefulSet с --cascade=orphan. Поды продолжают обслуживать запросы.
  3. Оператор StatefulSet не пересоздал, поэтому я применил его сам из бэкапа: размер поднят до 20 GiB, служебные поля вычищены (status, uid, resourceVersion, managedFields и подобные).
  4. Проверить: в шаблоне 20 GiB, ready 3/3, у каждого пода прежний хеш ревизии и время создания — ничего не перезапускалось.

После этого я закоммитил то же изменение в Git, чтобы следующая пересборка оператором снова не разошлась.

Рискованное окно

Между orphan-удалением и пересозданием поды ничем не управляются. Если в это окно один из них умрёт, его никто не поднимет. Держите окно коротким, а манифест готовьте до того, как что-то удалять.

Выводы

  • Synced означает, что Git совпадает с объектами, которыми управляет Argo CD. Про поля, которые применяются только при создании, это ничего не говорит.
  • Неизменяемые или однократно рендеримые поля могут тихо дрейфовать вечно. Знайте их для каждого оператора, который у вас работает.
  • --cascade=orphan вместе с updateStrategy: OnDelete позволяют пересобрать шаблон StatefulSet, не трогая data plane.
  • При RollingUpdate тот же приём перезапустит всех участников. Проверяйте стратегию до начала, а не после.