Миграция Deployment между namespace без даунтайма
Почему namespace нельзя поменять на лету, какие поля чистить в экспорте и в каком порядке переносить объекты, чтобы сервис не лёг.
Почему миграция это не mv
Namespace в Kubernetes хранится прямо в metadata.namespace каждого
объекта. Поменять его на лету нельзя: namespace входит в путь ресурса в
etcd, и для apiserver deployment/web в dev и deployment/web в
staging это два разных объекта.
Отсюда единственный рабочий сценарий: экспорт, чистка, импорт в новый namespace, проверка, удаление старого. Ровно в таком порядке, потому что любая перестановка шагов даёт окно недоступности.
Сначала инвентаризация
kubectl get all вводит в заблуждение: all это не «все объекты
namespace», а короткий список из Pod, Service, Deployment, ReplicaSet,
StatefulSet, DaemonSet, Job, CronJob. ConfigMap, Secret, Ingress, PVC,
ServiceAccount, RoleBinding, NetworkPolicy, HorizontalPodAutoscaler и
PodDisruptionBudget в него не попадают.
Полный список того, что вообще может лежать в namespace, показывает
api-resources:
kubectl get all -n dev
kubectl get cm,secret,ingress,pvc,sa,netpol,hpa,pdb -n dev
kubectl api-resources --namespaced=true -o name
До миграции стоит выписать, что именно переезжает. Пропущенный ConfigMap обнаруживается уже по CrashLoop в новом namespace.
Что экспортируется лишнего
kubectl get deploy web -n dev -o yaml отдаёт всё, включая поля,
которые apiserver заполняет сам. Если применить такой YAML без чистки,
получите ошибки:
metadata.resourceVersion: оптимистическая блокировка. Для нового объекта проставляется сам, в импорте мешает.metadata.uid: уникальный ID, генерируется apiserver-ом.metadata.creationTimestamp: время создания, ставится автоматически.metadata.generation: счётчик правок spec.status: текущее состояние, не часть spec. Правки тут apiserver игнорирует и затирает.spec.clusterIPу Service: адрес выдаётся из пула кластера, для нового Service выберется сам, а ручное значение упрётся в «already allocated».metadata.namespace: переписываем на новый.
Аннотация kubectl.kubernetes.io/last-applied-configuration тоже
приезжает из экспорта и тянет за собой старый namespace внутри JSON.
Вреда от неё нет, но чище её удалить вместе с остальным.
Самый аккуратный способ почистить, без флага --export (он давно
удалён из kubectl):
kubectl get deploy web -n dev -o yaml \
| yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .metadata.annotations."kubectl.kubernetes.io/last-applied-configuration", .status)' \
| yq '.metadata.namespace = "staging"' \
> /tmp/web.yaml
Забытый metadata.namespace превращает миграцию в обновление на
месте: манифест уедет обратно в dev, поверх старого Deployment.
Плагин kubectl neat делает то же самое одной командой, но он не входит
в поставку kubectl и есть не везде. yq обычно оказывается под рукой
чаще.
Отдельно про Pod: экспортировать их бессмысленно. Pod-ы создаёт
ReplicaSet, которым владеет Deployment, связь держится через
metadata.ownerReferences. Переносят Deployment, а Pod-ы в новом
namespace появятся сами.
Порядок миграции без даунтайма
Главное правило: сначала поднимаем в новом namespace, потом убираем старое. Иначе в окне между удалением и применением сервис лежит.
dev staging
cm, secret ─1─> cm, secret
deploy ─2─> deploy
svc ─3─> svc
delete dev <─ после endpoints
- Создаём целевой namespace:
kubectl create ns staging. - Экспортируем ConfigMap и Secret, чистим, применяем в новый namespace. Без них Deployment уйдёт в Pending или CrashLoop.
- Экспортируем Deployment, чистим, применяем в новый namespace.
- Ждём готовности:
kubectl rollout status deploy/web -n staging --timeout=120s. - Применяем Service в новом namespace, без
clusterIP. - Проверяем, что Pod-ы прошли readiness и Service отдаёт адреса.
- Только теперь удаляем объекты в старом namespace.
Если наружу трафик идёт через Ingress, на шаге 5 создаётся новый Ingress
в staging и переключается маршрут. Ingress ссылается на Service только
внутри своего namespace, так что «указать чужой Service» не выйдет:
объект Ingress должен переехать целиком. До момента переключения запросы
продолжает обслуживать старый Deployment.
Отдельная точка внимания - DNS-имя. Внутри кластера Service доступен как
web.dev.svc.cluster.local. После переезда имя становится
web.staging.svc.cluster.local. Если другие приложения ходят по
короткому web из своего namespace или по полному имени со старым
namespace, их конфигурацию тоже надо обновить, иначе после удаления
старого Service они начнут получать ошибки резолва.
Проверка перед удалением
После kubectl apply в новый namespace нужно убедиться, что Pod-ы
реально готовы, а не просто запустились:
kubectl rollout status deploy/web -n staging --timeout=120s
kubectl get pods -n staging -l app=web
kubectl get endpoints web -n staging
Объект v1 Endpoints deprecated начиная с 1.33, и kubectl печатает про
это предупреждение. Тот же список адресов через актуальный объект:
kubectl get endpointslice -n staging -l kubernetes.io/service-name=web
Смысл проверки: Service направляет трафик только на готовые Pod-ы. Пустой список адресов означает, что readiness ещё не прошёл, и запросы получают 503. Удалять старое можно после того, как в endpoints появились реальные IP.
Где живут зависимости
- PVC не переезжают между namespace. PersistentVolume биндится к конкретному PVC, и связь эта не переносится редактированием YAML. Для stateful-приложения нужен либо том с режимом ReadWriteMany и вторым PVC, либо backup и restore данных. Со stateless-сервисом, у которого из состояния только ConfigMap, вопрос не возникает.
- imagePullSecret переносится руками. Автоматическая генерация
Secret-токенов для ServiceAccount выключена с версии 1.24, и
defaultв новом namespace приезжает пустым. Если образ тянется из приватного registry, экспортируется сам Secret типаkubernetes.io/dockerconfigjson, а ссылка на него прописывается вspec.template.spec.imagePullSecretsили в ServiceAccount. - RoleBinding привязывает права к субъекту внутри namespace. В новом namespace прав нет вообще, пока туда не приедут Role и RoleBinding.
- NetworkPolicy отбирается по namespace и меткам. Правила, написанные
под
dev, вstagingработать не будут. - ResourceQuota и LimitRange переезжать не должны: это политика namespace, а не часть приложения.
Что часто ломается
- Оставили
spec.clusterIPу Service. Apply падает с «field is immutable» или «already allocated». Поле убирают. - Не перенесли ConfigMap. Pod в новом namespace встаёт с
CreateContainerConfigError, в events видно
configmap "X" not found. - Удалили старый Deployment до проверки нового. Между удалением и готовностью получается несколько секунд даунтайма. Порядок обратный: сначала endpoints, потом delete.
- Забыли про DNS-имя со старым namespace в конфигурации соседних
сервисов. Всё зелёное, а вызовы к
web.devперестают резолвиться.
Полная последовательность командами
# Что вообще лежит в исходном namespace
kubectl get all -n dev
kubectl get cm,secret,ingress -n dev
# Целевой namespace
kubectl create ns staging
# Экспорт с чисткой и подменой namespace
kubectl get cm web-config -n dev -o yaml \
| yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .status) | .metadata.namespace = "staging"' \
| kubectl apply -f -
kubectl get deploy web -n dev -o yaml \
| yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .metadata.generation, .status) | .metadata.namespace = "staging"' \
| kubectl apply -f -
# Ждём готовности
kubectl rollout status deploy/web -n staging --timeout=120s
# Service без clusterIP
kubectl get svc web -n dev -o yaml \
| yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, .spec.clusterIP, .spec.clusterIPs, .status) | .metadata.namespace = "staging"' \
| kubectl apply -f -
# Проверяем, что Service нашёл готовые Pod-ы
kubectl get endpointslice -n staging -l kubernetes.io/service-name=web
# Только после этого убираем старое
kubectl delete deploy web cm web-config svc web -n dev
Коротко
- Namespace входит в идентичность объекта, поэтому миграция это экспорт и повторное создание, а не переименование.
kubectl get allпоказывает не всё; список объектов namespace сверяют черезkubectl api-resources --namespaced=true.- Из экспорта вычищают
uid,resourceVersion,creationTimestamp,generation,statusиclusterIPу Service. - Порядок: ConfigMap и Secret, затем Deployment, дождаться rollout, затем Service и Ingress, и только потом удалять старое.
- Критерий готовности - непустой endpointslice у нового Service, а не факт запуска Pod.
- PVC, imagePullSecret, RoleBinding и NetworkPolicy переносят отдельно; DNS-имя сервиса меняется вместе с namespace.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс