Данная статья не претендует на какую-либо техническую глубину, и лишь призвана немного помочь коллегам, регулярно сталкивающимся с необходимостью выполнять миграцию своих кластеров kubernetes/openshift, вне зависимости от причин этой миграции - смены техстеха, переезда в другие ЦОДы, разделения систем и т.д. За пять лет использования алгоритма нигде не видел детального описания предлагаемого процесса. Притом, решение в лоб - изменить DNS записи выведенных для потребителей эндпоинтов - не всегда оправдано, во-первых из-за лага применения новых значений в DNS, а во-вторых - из-за вероятности возникновения необходимости отката на старый кластер. Иметь же перед кластером некий выделенный шлюз/балансировщик, где получилось выполнить перенаправление запросов - не всегда оправдано/технически возможно.
На малых предприятиях и небольших кластерах подобные миграции не вызывают проблем, но в крупных корпорациях, в условиях HIghLoad, где к Автоматизированной Системе, размещенной на кластере - подключены сотни потребителей - процесс может быть затянут как по техническим, так и по бюрократическим причинам - на месяцы, и потому выглядит рациональным ParallelRun - старого и нового кластера - максимально сократить, ведь эксплуатация системы в период ParallelRun сопряжена со следующими неудобствами:
Двойные расходы на содержание инфраструктуры (старый и новый кластер).
Усложнение процесса и последовательности деплоя.
Двойная нагрузка на сопутствующую инфраструктуру (коннекты к БД, кафке, мониторингу).
Усложнение DevOps-пайплайна.
Усложнение релизного процесса (необходимо выполнять тестирование, особенно нагрузочное, и в старом и в новом стеке).
Необходимость собирать два дистрибутива (под каждый кластер), или некий объединенный дистрибутив.
Затруднение диагностики проблем и инцидентов.
Конфликты фоновых операций.
Предлагаемый подход достаточно очевиден, однако же за пять лет его использования, так и не увидел нигде его детального описания: для митигации вышеупомянутых неудобств достаточно переиспользовать граничные прокси (Ingress) на входе старого кластера - для перенаправления всего входящего трафика в новый кластер.
Трафик >> старый кластер >> Ingress >> новый кластер
При этом, затраты на реализацию механизма перенаправления трафика достаточно 30 минут времени DevOps - инженера для создания Istio-обвязки (VS,GW,SE,DR). Направление трафика в указанному случае будет следующим:
Выгода данного подхода:
После подтверждения работоспособности достаточно оставить пару нод для работоспособности самих Ingress-ов. Деплойменты самого ПО и даже Egress-ов для работы не требуется.
Упрощается релизный процесс - можно единожды поставить проксификатор на старый кластер, релизиться на новый, и более не возвращаться к старому кластеру до окончания ParallelRun.
Уменьшается нагрузка на внешние ресурсы (БД, и т.д.)
При опасении в работоспособности нового кластера - можно j гибко перенаправлять не весь трафик, а определенную его часть на новый кластер, а часть - обрабатывать старым, оставив там работающее ПО (распределяя процент с помощью weight).
Не требует переработки Whitelist, Ratelimiter - старый кластер более не участвует в обработке, и трафик отправляется практически в неизменном виде (о чем далее) не новый кластер
Фактически, данный механизм позволяет поддерживать ParallelRun для перехода потребителей достаточно долгое время, не испытывая проблем от необходимости поддержки старой инфраструктуры. Из негативных эффектов - добавляется небольшой лаг на перенаправление трафика - порядка нескольких миллисекунд (в периметре одной сети), что не является критичным для систем, уже использующих istio как транспортный слой в кластере k8s/openshift.
Какие CRD нужно создать для работы данного механизма? ServiceEntry, куда помещается эндпоинт нового кластера
kind: ServiceEntry metadata: name: proxy-serviceentry spec: exportTo: [.] hosts: - ${NEW_ENDPOINT} ports: - name: https number: 443 protocol: TLS resolution: DNS location: MESH_EXTERNAL
DestinationRule для подвязки сертификата к данной маршрутизации. Обратите внимание на два момента:
Часто в ингрессе по умолчанию только серверный сертификат. Не забудьте обеспечеть в поде наличие клиентского или клиентско-серверного сертификата
Все походы от старого кластера к новому будут подписаны этим сертификатом, а не пришедшего к вам потребителя
kind: DestinationRule metadata: name: ingressgateway-dr-proxy spec: exportTo: [.] host: ${NEW_ENDPOINT} trafficPolicy: portLevelSettings: - port: number: 443 tls: caCertificates: /etc/istio/ca-certs/cert-ca.pem clientCertificate: /etc/istio/client-certs/tls.crt mode: MUTUAL privateKey: /etc/istio/client-certs/tls.key sni: ${NEW_ENDPOINT}
Gateway на старый маршрут - c селектором на проксирующий ингресс
kind: Gateway metadata: name: ingressgateway-gateway-proxy spec: selector: app: ingressgateway-proxy istio: ingressgateway-proxy servers: - hosts: - ${OLD_CLUSTER_ENDPOINT} port: name: https number: 11443 protocol: HTTPS tls: caCertificates: /etc/istio/ca-certs/cert-ca.pem mode: MUTUAL privateKey: /etc/istio/server-certs/tls.key serverCertificate: /etc/istio/server-certs/tls.crt
Service для приема туда трафика с ingresses/route
- kind: Service apiVersion: v1 metadata: name: ingressgateway-proxy labels: app: ingressgateway-proxy istio: ngressgateway-proxy spec: ports: - name: https protocol: TCP port: 443 targetPort: 11443 selector: app: ingressgateway-proxy istio: ingressgateway-proxy
Непосредственно перенаправление трафика осуществляется при помощи VirtualService, и на нем стоит остановиться поробнее:
kind: VirtualService metadata: name: ingressgateway-virtualservice-proxy spec: exportTo: - . gateways: - ingressgateway-gateway-proxy //указывается ранее созданный GW hosts: - ${OLD_CLUSTER_ENDPOINT} //маршрут, с которого перенаправляется трафик http: - match: - uri: prefix: /apiV1/ //указывается api, которая перенаправляется. Можно указать все rewrite: //обязательно указывается rewrite для переписывания адреса authority: ${NEW_ENDPOINT} uri: /apiV1/ route: - destination: host: ${NEW_ENDPOINT} port: number: 443 name: redirect_to_new_cluster //именование правила для облегчения логирования
Без указания директивы rewrite - данное перенаправление работать не будет. Также остается возможность перенаправить вообще все api на один новый адрес, или определенные api/их группы на различные новые маршруты, например, apiV1 на кластер A, а apiV2 на кластер B. При использовании данного перенаправления все заголовки (headers) потребителя, пришедшего на старый маршрут - сохраняются, и если ваш whitelist настроен на значение какого-либо заголовка - то проблем не будет. Однако, если вы смотрите на tls-сертификат потребителя, то будет проблема - так как весь перенаправленный таким образом трафик будет под тем сертификатом, что вы указали на старом кластере в DestinationRule. Передать без изменений клиентский сертификат потребителя с помощью такого перенаправления невозможно, и единственный способ сохранить о нем информацию - использовать EnvoyFilter в старом кластере для помещения информации о сертификате (например, x-forwarded-client-cert) в какой-либо кастомный заголовок.
Как можно разбить трафик 50/50 между старым и новым кластером? На примере Route с помощью weight:
to: kind: Service name: ingressgateway-service //Сервис ведет в ПО weight: 50 alternateBackends: - kind: Service name: ingressgateway-proxy //Сервис ведет в новый кластер weight: 50 port: targetPort: https tls: termination: passthrough wildcardPolicy: None
Данный подход к переезду хотя и не является оптимальным, но, фактически, безальтернативен при наличии большого количества потребителей и напряженном релизном цикле (например, релиз - каждую неделю) в условиях большой бюрократизации процесса.

