Перенести критичный Kubernetes-сервис из default в отдельный namespace трудно не из-за Deployment. Проблема — в старом DNS-имени, десятках независимых клиентов и внешнем маршруте через Ingress. Безопасная миграция разделяет эти контуры, временно сохраняет старый адрес и делает каждый шаг проверяемым и обратимым.

Почему обычный перенос создаёт окно отказа

Namespace входит в идентичность большинства Kubernetes-объектов. Deployment, Service, Secret, ConfigMap, ServiceAccount, RoleBinding и Ingress нельзя просто «переместить»: в новом namespace создаются новые объекты. Вместе с Service меняется и полное DNS-имя — например, auth-svc.default.svc.cluster.local превращается в auth-svc.authentication.svc.cluster.local.

Если все клиенты принадлежат одной команде и выкатываются одновременно, можно заменить адрес одним релизом. В реальной платформе потребителей много, их циклы поставки не синхронизированы, а часть приложений месяцами не перезапускается. Попытка согласовать атомарный cutover превращает техническую миграцию в организационную блокировку.

У сервиса обычно два независимых пути трафика. Внутренние клиенты обращаются к Kubernetes Service через cluster DNS. Внешние пользователи приходят через Ingress или Gateway. Исправить только один путь — значит оставить половину системы в аварийном состоянии.

Сначала инвентаризация, потом YAML

До создания нового namespace составьте карту зависимостей. Для внутреннего трафика ищите полные и короткие DNS-имена, переменные окружения вида *_SERVICE_HOST, hardcoded ClusterIP, записи service mesh и NetworkPolicy. Короткое имя auth-svc разрешается относительно namespace клиента и может вести не туда, куда предполагает автор миграции.

Отдельно перечислите внешние host/path, TLS-секреты, аннотации ingress-контроллера, middleware и DNS-записи. Проверьте namespace-scoped зависимости: ServiceAccount, RoleBinding, Secret, ConfigMap, PodDisruptionBudget, HPA, policy и мониторинговые ресурсы. PersistentVolume не namespace-scoped, но PersistentVolumeClaim — да; stateful workload требует отдельного плана данных.

Зафиксируйте сигналы успеха до cutover: число запросов по старому и новому адресу, долю 4xx/5xx, latency, активные соединения, readiness новых Pod и бизнес-метрику критического пути. Без исходного baseline команда увидит графики, но не поймёт, стала ли миграция причиной деградации.

Старое имя как временный DNS-алиас

Для клиентов, которые используют DNS, старый Service можно заменить объектом ExternalName, указывающим на новый Service:

apiVersion: v1
kind: Service
metadata:
  name: auth-svc
  namespace: default
spec:
  type: ExternalName
  externalName: auth-svc.authentication.svc.cluster.local

CoreDNS вернёт CNAME на новое имя. Kubernetes не создаёт для такого Service ClusterIP, endpoints или прокси-маршрут: перенаправление происходит только в DNS. Поэтому сначала надо поднять workload и обычный Service в целевом namespace, проверить readiness и доступ по новому FQDN, а уже затем менять старый объект.

У схемы есть важное ограничение. Официальная документация Kubernetes предупреждает, что ExternalName может ломать HTTP и HTTPS: клиент подключается по старому имени, но HTTP Host и TLS SNI не совпадают с целевым hostname. Для обычного внутрикластерного HTTP это может быть незаметно, если сервер не проверяет host. Для HTTPS, virtual hosting, service mesh и клиентов с проверкой сертификата тест обязателен. Если протокол зависит от имени, понадобится другой переходный механизм — например, прокси, временный Service без selector с управляемыми EndpointSlice или изменение конфигурации клиентов.

DNS-алиас не спасёт потребителей с hardcoded IP. Не сработает он и для приложений, которые получили старый ClusterIP через environment variables при запуске: после замены Service они продолжат использовать сохранённое значение до рестарта. Именно поэтому discovery надо проверять не только поиском строк в репозиториях, но и наблюдением реального трафика.

Cutover как последовательность обратимых шагов

Надёжный порядок выглядит так:

  1. Создать целевой namespace и все зависимости, не меняя production-трафик.
  2. Развернуть новую копию приложения и дождаться readiness, прогрева кэшей и подключения к downstream.
  3. Проверить новый Service из нескольких namespace и через тот же mesh/proxy, которым пользуются клиенты.
  4. Заменить старый Service на ExternalName либо включить выбранный переходный прокси.
  5. Подтвердить по метрикам и трассам, что запросы к старому адресу доходят до новых Pod.
  6. Масштабировать старый Deployment до нуля, но не удалять его сразу.
  7. Выдержать наблюдаемый период, затем убрать старые объекты и алиас по отдельному change.

Scale-to-zero сохраняет быстрый rollback: старые Pod можно вернуть, пока манифесты, секреты и зависимости ещё существуют. Но rollback тоже надо отрепетировать. Возврат старого Deployment бессмысленен, если Service уже стал ExternalName; сценарий должен включать восстановление его прежнего типа и selector.

Не держите алиас бессрочно. Назначьте владельца, deadline и метрику оставшихся обращений к старому имени. Иначе временный мост станет новым легаси, а следующий переезд снова упрётся в неизвестных потребителей.

Внешний маршрут переключается отдельно

Ingress не использует старое внутреннее DNS-имя тем же способом, поэтому ему нужен собственный overlap. Сначала создайте маршрут к Service в новом namespace, проверьте его через тестовый host или контролируемую долю трафика, затем переключите production-host и только после подтверждения удаляйте старый маршрут.

Политика, запрещающая одинаковый host в двух namespace, защищает от неоднозначной маршрутизации. Обходить её постоянной дырой нельзя. Если контролируемый overlap действительно необходим, исключение должно быть узким: конкретный namespace и host, ограниченный срок, владелец, audit trail и автоматическое истечение. Поведение двух одновременно существующих Ingress зависит от конкретного контроллера; нельзя считать, что он детерминированно выберет новый объект.

Для критичного внешнего пути безопаснее использовать возможности самого ingress-контроллера или Gateway API: weighted routing, canary, отдельный backend либо явное изменение одного маршрута. Выбор проверяют в staging на той же версии контроллера и с теми же admission policies.

Что наблюдать во время миграции

Одного kubectl get pods мало. Нужны четыре слоя проверки:

  • DNS: старое имя возвращает ожидаемый CNAME, TTL известен, резолв работает из реальных клиентских namespace;
  • сеть: NetworkPolicy, mesh authorization и egress позволяют путь к новому Service;
  • приложение: ошибки, latency, saturation и бизнес-операции не деградируют;
  • маршрутизация: трассы или access logs подтверждают, что старое имя и внешний host приходят именно в новые Pod.

После scale-to-zero продолжайте наблюдать хотя бы один полный цикл типичной нагрузки. Отдельно проверьте cron-задачи, редкие интеграции и долго живущие соединения: они часто не попадают в короткий smoke test.

Ограничения паттерна

DNS-переадресация подходит для stateless-сервиса с DNS-based discovery и совместимым протоколом. Она не переносит данные, не копирует namespace-scoped конфигурацию и не гарантирует бесшовность для TLS, hardcoded IP, старых environment variables или клиентов с агрессивным DNS-кэшированием. Stateful-сервисы, очереди и базы требуют отдельной репликации и проверки семантики записи.

Наконец, «без downtime» — не свойство YAML, а проверяемый SLO. Формулируйте допустимую долю ошибок и время переключения, собирайте доказательства в staging и оставляйте rollback до окончания наблюдаемого периода.

Вывод

Миграция между namespace становится управляемой, когда команда перестаёт считать её перемещением Deployment. Это смена адреса и границ владения. Разделите внутренний DNS и внешний ingress, сохраните старое имя только как временный совместимый мост, измерьте реальный трафик, выключите старую копию обратимым шагом и удаляйте её после выдержки. Тогда десятки клиентов смогут обновляться в своём темпе, а критичный сервис — переехать без общего релиза.

Источник и лицензия

Автор исходной статьи — George Sims; в byline CNCF указана организация Downtherabbithole.dev. Оригинал «Migrating a critical Kubernetes deployment from the default namespace without any downtime» опубликован 3 сентября 2026 года в корпоративном инженерном блоге Cloud Native Computing Foundation. Материал доступен по лицензии Creative Commons Attribution 3.0 согласно условиям Linux Foundation. Это самостоятельная русская адаптация: текст переведён, сокращён, дополнен ограничениями из актуальной документации Kubernetes и редакционно переработан; правообладатель её не одобрял и не спонсировал.