Упрощение миграции потребителей между HighLoad кластерами k8s/openshift с использованием Istio
Данная статья не претендует на какую-либо техническую глубину, и лишь призвана немного помочь коллегам, регулярно сталкивающимся с необходимостью выполнять миграцию своих кластеров 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
Данный подход к переезду хотя и не является оптимальным, но, фактически, безальтернативен при наличии большого количества потребителей и напряженном релизном цикле (например, релиз - каждую неделю) в условиях большой бюрократизации процесса.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.