Миграция PersistentVolumeClaim в Kubernetes на ходу: метод in-place перепривязки без простоя
Миграция постоянных дисков в Kubernetes — одна из самых сложных и рискованных задач для платформ автоматизации. Переход со старых встроенных драйверов (in-tree provisioners) на современные интерфейсы CSI (Container Storage Interface) стандартными методами требует остановки приложений. Однако для систем, поддерживающих ClickHouse, CockroachDB, Kafka или Prometheus, даже кратковременный простой (downtime) недопустим.
Существует практичное решение: метод «in-place» перепривязки постоянных томов (PersistentVolumeClaim, PVC), который позволяет заменить базовый томик (PersistentVolume, PV) без остановки дисков. Этот подход требует лишь одного перезапуска пода на каждый том и выполняется исключительно штатными средствами Kubernetes API, без использования кастомного ПО.
Архитектурные барьеры и неизменяемость данных
Чтобы понять сложность миграции, необходимо рассмотреть модель хранения данных в Kubernetes. Физический диск представляется объектом PersistentVolume (PV). Приложение запрашивает диск через PersistentVolumeClaim (PVC), а класс хранилища (StorageClass) определяет тип диска и провайдер (provisioner), который его создает. Связка между PV и PVC фиксируется параметром claimRef на PV.
Главная проблема при миграции заключается в том, что почти все критические поля этих ресурсов неизменяемы (immutable):
- Поле провайдера (provisioner) в StorageClass нельзя обновить после создания.
- В спецификации PV поле источника (spec.persistentVolumeSource) заблокировано для изменений. Попытка его обновить возвращает ошибку API-сервера.
- Шаблон томов в StatefulSet (spec.volumeClaimTemplates) жестко зафиксирован. Нельзя просто поменять имя StorageClass для работающих подов.
- Поля томов в спецификации самого пода (spec.volumes) также защищены от редактирования.
Эти ограничения созданы для защиты стабильности данных: поды могут перезапускаться, но диски должны оставаться неизменными. Единственным официальным способом изменить эти ресурсы является их удаление и повторное создание, что означает остановку сервисов.
Системные особенности Kubernetes как инструмент решения
Для реализации миграции без простоя можно использовать несколько особенностей жизненного цикла ресурсов Kubernetes:
- Пассивность класса хранилища (StorageClass). Этот объект используется только в момент динамического выделения PV для нового PVC. После завершения связывания StorageClass больше не участвует в работе диска. Это означает, что старый класс хранилища можно безболезненно удалить и сразу же пересоздать с тем же именем, но указывающим на новый CSI-драйвер (например,
disk.csi.azure.comвместоkubernetes.io/azure-disk). - Свободное связывание (claimRef). Когда PV привязан к PVC, его поле spec.claimRef содержит уникальный идентификатор (UID) этого PVC. Но если вручную создать новый PV, указав в claimRef только имя и пространство имен целевого PVC, и опустить UID, контроллер томов посчитает этот PV свободным кандидатом для связывания. Такой томик становится «приманкой» (honeypot volume) для вновь создаваемых PVC.
- Защитный механизм pvc-protection. Kubernetes автоматически вешает финализатор
kubernetes.io/pvc-protectionна все PVC, которые активно используются запущенными подами. Если отправить запрос на удаление такого PVC, он не сотрется сразу, а перейдет в состояние Terminating. Физическое удаление произойдет только после того, как остановится использующий его под. Это защищает систему от гонки условий: StatefulSet не успеет создать новый пустой диск до того, как сработает механизм перепривязки к нашей «приманке».
Пошаговый алгоритм миграции дисков
Процесс миграции делится на глобальную подготовку инфраструктуры и точечный перенос каждого отдельного тома.
Подготовительный этап: подмена класса хранилища
Сначала необходимо перенаправить запросы на создание дисков на новый CSI-драйвер. Для этого создается резервная копия старого StorageClass, после чего он удаляется, и на его месте разворачивается новый с тем же именем, но новым провайдером.
# Сохраняем конфигурацию старого класса
kubectl get sc managed-premium -o yaml > managed-premium-legacy.yaml
Удаляем старый StorageClass и применяем новый, в котором в качестве provisioner указан disk.csi.azure.com, а reclaimPolicy установлен в Delete. Теперь при создании любого нового тома с этим классом Kubernetes будет использовать CSI.
Перенос конкретного диска (Checklist)
Для каждого тома в кластере последовательно выполняются следующие шаги:
- Сбор параметров. Определяем имя целевого пода, его PVC, PV и уникальный URI физического диска в облаке (Azure Disk URI).
- Изменение политики утилизации. Переводим reclaimPolicy старого PV в режим Retain. Это критически важно: иначе при удалении объекта PV из Kubernetes физический диск в облаке будет стерт.
kubectl patch pv $PV_NAME -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' - Создание PV-приманки (Honeypot PV). Пишем манифест для нового PV, работающего через CSI, который указывает на тот же самый физический диск.
Пример манифестаpv-csi.yaml:apiVersion: v1 kind: PersistentVolume metadata: name: pv-migrated-csi spec: capacity: storage: 100Gi # Указываем точный объем исходного диска accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: managed-premium claimRef: name: my-claim-pvc # Имя оригинального PVC namespace: default # Пространство имен csi: driver: disk.csi.azure.com volumeHandle: /subscriptions/.../resourceGroups/.../providers/Microsoft.Compute/disks/my-physical-disk volumeAttributes: fsType: ext4 # Файловая система исходного диска
Применяем манифест:kubectl apply -f pv-csi.yaml. - Запуск перепривязки. Инициируем одновременное удаление старого PVC и пода:
kubectl delete pvc my-claim-pvc kubectl delete pod my-pod-0
В этот момент запускается цепочка событий: под начинает завершать работу, PVC переходит в статус Terminating. Как только под полностью останавливается, финализатор pvc-protection исчезает, и PVC удаляется. Контроллер StatefulSet мгновенно замечает отсутствие пода и PVC и генерирует их заново. Поскольку класс хранилища указывает на CSI, создается новый запрос на привязку тома. Но вместо создания нового пустого диска Kubernetes находит наш PV-приманку с подходящим claimRef и связывает его с новым PVC. - Верификация. Убеждаемся, что новый под успешно запустился, статус PVC изменился на Bound, а старый PV перешел в статус Released (его теперь можно безопасно удалить из системы).
Альтернативные стратегии миграции томов
Существует несколько традиционных способов решения этой задачи:
- Ручной импорт (Microsoft Static Volume). Метод предусматривает ручное создание PV и PVC для CSI с обновлением конфигурации приложения. Это надежный способ сохранить данные, но он требует перенастройки приложения и полного простоя сервиса.
- Отсоединение и усыновление (Orphan and Adopt). StatefulSet удаляется с флагом
--cascade=orphan, оставляя поды работать без контроллера. Затем создается новый StatefulSet с измененным шаблоном томов. Метод исключает downtime, но несет риски потери данных в случае сбоя узла, пока поды остаются без управления. - Холодное резервное копирование (Velero). Снимок данных разворачивается с измененным StorageClass после удаления приложения. Это безопасный стандартный подход, однако он требует полной остановки записи в базу данных, что увеличивает время простоя.
- Модификация API-сервера (Datadog). Форканье кода Kubernetes и патчинг логики API-сервера для обхода валидации неизменяемых полей «на лету». Это дает полный контроль над объектами, но не подходит для управляемых облачных сервисов (GKE, EKS, AKS), где панель управления закрыта.
- Кастомные операторы данных (ATOM). Развертывание оператора для автоматического копирования данных между томами с использованием webhook-мутаций. Это мощное, но избыточное решение для простой смены драйвера.
Практические выводы и рекомендации для продакшена
Описанный метод in-place перепривязки был успешно применен на инфраструктуре из сотен Kubernetes-кластеров. При реализации важно учитывать:
- Обрабатывайте пограничные случаи. Ошибки Multi-Attach возникают, когда старый узел еще не отпустил диск, а новый уже пытается его смонтировать. Скрипты автоматизации должны учитывать время на отмонтирование дисков.
- Держите контроль. Не проводите автоматическую миграцию всех дисков разом. Оптимальный вариант — автоматизировать подготовку PV и удаление ресурсов, но сам перезапуск пода завязать на ручное подтверждение.
- Глубокие системные знания эффективнее автоматизации «в лоб». Понимание внутренней логики работы claimRef и финализаторов позволило провести миграцию сотен продуктовых томов силами небольшой платформенной команды всего за два месяца, не вызвав ни одного сбоя в работе приложений.

