Меняем неизменяемое поле 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 увидит разницу в хеше шаблона и перекатит всех участников — такое делают в окно обслуживания, а не во вторник после обеда.
Последовательность:
- Сделать бэкап спецификации StatefulSet и убедиться, что
updateStrategy—OnDelete. Записатьcontroller-revision-hashи возраст каждого пода. - Удалить StatefulSet с
--cascade=orphan. Поды продолжают обслуживать запросы. - Оператор StatefulSet не пересоздал, поэтому я применил его сам из бэкапа: размер поднят до 20 GiB, служебные поля вычищены (status, uid, resourceVersion, managedFields и подобные).
- Проверить: в шаблоне 20 GiB, ready 3/3, у каждого пода прежний хеш ревизии и время создания — ничего не перезапускалось.
После этого я закоммитил то же изменение в Git, чтобы следующая пересборка оператором снова не разошлась.
Рискованное окно
Между orphan-удалением и пересозданием поды ничем не управляются. Если в это окно один из них умрёт, его никто не поднимет. Держите окно коротким, а манифест готовьте до того, как что-то удалять.
Выводы
Syncedозначает, что Git совпадает с объектами, которыми управляет Argo CD. Про поля, которые применяются только при создании, это ничего не говорит.- Неизменяемые или однократно рендеримые поля могут тихо дрейфовать вечно. Знайте их для каждого оператора, который у вас работает.
--cascade=orphanвместе сupdateStrategy: OnDeleteпозволяют пересобрать шаблон StatefulSet, не трогая data plane.- При
RollingUpdateтот же приём перезапустит всех участников. Проверяйте стратегию до начала, а не после.