Привет, Хабр! Я Степан, DevOps‑инженер, занимаюсь созданием CI/CD процессов с учётом проверки кода на безопасность, поддержкой и разверткой новых кластеров Kubernetes, соблюдением требований безопасности и созданием системы мониторинга и логирования — все это в рамках одной команды. Хочу рассказать об одной незадокументированной особенности Istio, с которой мне довелось столкнуться при внедрении Service Mesh.

Об Istio на Хабре написано уже немало (например, тут и тут, может пригодиться новичкам в этой теме), но вот разбора конкретных примеров не хватает. И когда у нас в ОТП Банке возникла проблема с «Истио», решать её нашей команде пришлось самостоятельно.

Быть может, наш опыт пригодится тому, кто позже наступит на те же грабли. Даже лучше будет, если кто‑то прочитает и уже не наступит. Конкретно наши грабли пригодятся при использовании Istio для маршрутизации в рамках одного кластера. Ещё наш кейс можно проецировать на другие задачи с маршрутизацией. И использовать как пример того, как в нестандартных ситуациях искать качественное решение.

Как и статьи по ссылкам выше, этот текст в основном для начинающих. Эксперты, вероятно, и без того в курсе (возможно, даже подскажут более эффективное решение). С экспертизой и более глубокой работой с istio + может быть с envoy данное решение будет найти не так сложно. Под катом — подробности проблемы, схема нашего конфига с решением и код.

С чего всё начиналось

История началась весной. До того мы использовали в работе Bare metal с NGINX, то есть физический кластер в ЦОД с развёрнутым Kubernetes на нём. Увеличилась потребность в ресурсах и были выделены под это новые сервера. Всё осталось на Baremetal. В процессе наша конфигурация изменилась и стала включать два кластера Kubernetes на разных дата‑центрах. Пришлось задуматься о более эффективной и удобной маршрутизации.

Выбор инструмента оказался несложным: в финтехе есть списки одобренного ПО, отвечающего требованиям безопасности нашей компании. Istio в своей категории был один, и, кстати, по части безопасности с ним действительно оказалось просто: предложенные решения «из коробки» нас вполне устраивали. Решение Istio как некая замена Ingress контроллеру (всё‑таки принцип конфигурации у них отличается, как минимум наличием доп. сущностей Kubernetes (VirtualService, Gateway)), так же он обеспечивает защиту и мониторинг трафика внутри кластера, что не может не радовать. Кроме того, Istio уже был на слуху, по нему была хорошая документация, и всё выглядело так, будто никаких проблем ожидать не стоит.

Мигрировали на новые кластеры мы постепенно: приложение за приложением. До тех пор, пока кластеры работали с ними как с внешними сервисами, всё было хорошо. Так прошли первые 3 месяца. А потом мигрировало приложение с запросами в пределах своего кластера… и всё сломалось. Оказалось, Istio просто не маршрутизирует запросы внутри кластера. Буквально: Istio не отправлял трафик на новые версии приложения.

Изначальная конфигурация:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
labels:
app: my-app
name: my-app-traffic-balancing
namespace: my-app-ns
spec:
gateways:
- default/https-gateway
hosts:
- "hostname"
http:
- match:
- uri:
prefix: /my-app/
rewrite:
uri: /
route:
- destination:
host: my-app-prod.my-app-ns.svc.cluster.local #Как видно, мы ссылаемся на конкретный сервис.
weight: 99
- destination:
host: my-app-prod-new.my-app-ns.svc.cluster.local #И перенаправляем трафик уже на сервис нового приложения, в данном случае перенаправляется 1% трафика.
weight: 1
---
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: https-gateway
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- '*'
port:
name: https
number: 443
protocol: HTTPS
tls:
credentialName: my-secret-cert # secret содержащий сертификат
mode: SIMPLE

Это, к счастью, катастрофой не стало: мы практикуем подобие «канареечного» подхода к выкатке, хотя можно назвать его и A/B‑подходом. То есть, мы раскатываем обновлённые приложения по соседству с основными, отдельно, и заранее проверяем, что всё работает. Так что, когда не заработало, не пришлось метаться и искать корень проблемы здесь и сейчас. И это очень хорошо, потому что поиск занял у меня немало времени.

В документации всё есть (нет)

Первое, что я сделал, — поднял документацию и попытался настроить маршрутизацию с помощью ServiceEntry и DestinationRule, чтобы расставить метки и указать точные адреса сервисов и внешних серверов. По всем признакам это должно было сработать, но не сработало. Крайне неприятно чувствовать, что вроде бы делаешь всё, как описано в документации, а результата всё равно нет.

В Istio много возможностей и сервисов, но для нашего случая особенно важно сказать о VirtualService. Как подсказывает название, это виртуальный сервис, который на основе Service строит свою маршрутизацию. По документации у него должны быть Gateway, с помощью которых открываются порты наружу. Вы можете открыть, допустим, 443-й или 80-й порт на Gateway этого виртуального сервиса и по этому порту обращаться в кластер.

Именно VirtualService я и настраивал, варьируя DestinationRule. Самодиагностика средствами Istio при этом проблем не показывала, кластер подтверждал, что соединение с Istio есть, но маршрутизации не было. Тем не менее несколько напрягало менять конфигурации почти вслепую, не понимая, что может не устраивать Istio. Мы с командой в том числе направляли трафик на разные сервисы, пытались его размаршрутизировать. Так я без особого успеха бился над Istio около двух недель.

Думайте сами, решайте сами

Помимо документации, я искал ответы где только мог. Например, в книге «Istio: приступаем к работе», Калькот Л., Бутчер З. В ней описаны принципы работы «Истио» и приведены примеры конфигурации. Единственное, что книжка уже отстала от текущей версии «Истио» и нужно ориентироваться больше на документацию. Наша проблема нигде не упоминалась. Однако размышления над схемами настроек даром не прошли: в какой‑то момент меня озарило, как всё работает и где происходит сбой.

Если у VirtualService есть указанный Gateway, сервис будет ждать, что к нему придёт запрос извне. Можно отправить ему внутренний запрос, он ответит, что всё в порядке… и продолжит ждать внешнего сигнала. Это логично, если у вас трафик приходит из другого кластера или с другого сервера или вообще пользователь заходит через браузер. Но при маршрутизации внутри кластера надо принудительно отвязать VirtualService от Gateway: не указывать их.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
labels:
app: my-app
name: my-app-balancing-inside
namespace: my-app-ns
spec:
hosts:
- my-app-service.my-app-ns.svc.cluster.local # Важно, в данном VirtualService отсутствует привязка к GateWay.
http:
- match:
- uri:
prefix: /
name: my-app-dr
route:
- destination:
host: my-app-service.my-app-ns.svc.cluster.local # В отличие от предыдущего примера направляем трафик на один сервис.
port:
number: 8443
subset: v1
weight: 99
- destination:
host: my-app-service.my-app-ns.svc.cluster.local # В отличие от предыдущего примера направляем трафик на один сервис.
port:
number: 8443
subset: v2
weight: 1

Как видно, в данной конфигурации указано поле subset об этом будет далее.

Конечно, при этом сервис должен общаться с приложениями — и тут поможет DestinationRule. Так как виртуальный сервис ссылается на Service Kubernetes, нам нужно, чтобы он (Service) сам по себе видел все версии приложения, — для этого как раз подойдут метки (labels). Это выглядит так в Deployment‑е приложений мы проставляем разные метки: для прод‑версии — version: v1, для новой версии — version: v2. При этом хорошо бы не забыть проставить общую метку для Service, например — app: my‑app. Это позволит в дальнейшем при задании поля selector определить, по каким меткам выбирать созданные Pod. Далее с помощью DestinationRule мы определяем так называемый subset:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: my-app-dr
namespace: my-app-ns
spec:
host: my-app-service.my-app-ns.svc.cluster.local
subsets:
- labels:
app: my-app
version: v1
name: v1 # Данное имя указывается в VirtualService.
- labels:
app: my-app
version: v2
name: v2 # Данное имя указывается в VirtualService.
---
Важно, чтобы сервис my-app-service сам по себе маршрутизировал трафик на обе версии приложения, достигается это настройкой поля selector в Service.
...
selector:
app: my-app
...
При этом в Deployment нужно указать дополнительные лейблы, чтобы в последующем DestinationRule смог корректно определить subsets.
Приложение v1
... <Deployment>
labels:
app: my-app
version: v1
...
Приложение v2
... <Deployment>
labels:
app: my-app
version: v2
...

Этого достаточно, чтобы маршрутизация работала корректно, и каждая версия получала только свои данные.

А как же безопасность?

Естественно, важной составляющей настройки Istio является обеспечение безопасности. Один из способов обеспечения безопасности в Istio — это внедрение механизмов взаимной аутентификации и шифрования трафика, таких как mTLS (Mutual TLS). mTLS обеспечивает аутентификацию как клиента, так и сервера, устанавливая двустороннее TLS‑шифрование между ними.

Все эти нюансы можно настроить с помощью PeerAuthentication — одного из важных ресурсов в Istio. Этот инструмент позволяет точно настроить, как приложения взаимодействуют между собой. Мы можем сделать так, чтобы нагрузка строго требовала использования mTLS или была более гибкой, допуская незашифрованный трафик.

Интересно, что настройки можно применять на разных уровнях:

  • Глобально для всей сети сервисов.

  • Для конкретного пространства имен.

  • И для отдельных приложений по их селекторам.

Для усиления безопасности нашей сети мы можем запретить передачу незашифрованного трафика. Создадим политику, которая жестко устанавливает требование использования mTLS для всей сети. Это действие — шаг в сторону большей безопасности. Однако, стоит помнить, что радикальные изменения могут вызвать сложности в текущих проектах, где требуется внедрение изменений согласованно между различными командами. Поэтому, более гибкий подход — постепенное ужесточение ограничений с установкой временных рамок для миграции сервисов. Режим PERMISSIVE mTLS позволяет именно это: он дает возможность приложениям принимать как зашифрованный, так и незашифрованный трафик, обеспечивая переходный период.

Вот примеры.

Запрещаем передачу незашифрованного трафика для всей сети:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: strict-all
namespace: istio-system
spec:
mtls:
mode: STRICT

Разрешаем передачу незашифрованного трафика для определенного неймспейса:

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: permissive-all
namespace: my-app-ns
spec:
mtls:
mode: PERMISSIVE

Счастливый конец

Мы внедрили решение, и проблема с маршрутизацией пропала: уже несколько месяцев наблюдаю, что всё работает как надо.

От себя добавлю, что вышеописанное поведение Istio даже кажется логичным, если знать о нём заранее. Так что, надеюсь, наша статья будут полезна девопсам, которые собираются с помощью Istio маршрутизировать трафик в пределах одного и того же кластера Kubernetes.

Комментарии (15)


  1. chemtech
    10.11.2023 13:00
    -1

    Спасибо за пост. У вас yaml невалидные. Поправьте пожалуйста yaml конфиги.


    1. Step_devops Автор
      10.11.2023 13:00

      Спасибо за комментарий! Все поправили.


      1. chemtech
        10.11.2023 13:00

        У вас Изначальная конфигурация пишет ошибку "error: error parsing VirtualService.yaml: error converting YAML to JSON: yaml: line 22: did not find expected key". Пожалуйста также поправьте форматирование


  1. GavinBelson4real
    10.11.2023 13:00
    -1

    это очень мощный инструмент для контроля, развертывания и мониторинга микросервисных приложений. Istio предоставляет гибкие инструменты для настройки и конфигурирования маршрутов, а также предоставляет мощный механизм мониторинга, который позволяет получать данные о происходящих внутри приложения событиях. Все это помогает разработчикам лучше исследовать и понимать приложение.


    1. v0rdych
      10.11.2023 13:00

      ботов с чатгпт и на хабр завезли, наконец-то?


      1. Step_devops Автор
        10.11.2023 13:00

        Раньше только статьи чатомгпт генерировали, а теперь ещё и комментарии начали. Скоро вообще человеков не останется на Хабре)


  1. lacrimosa_dies_irae
    10.11.2023 13:00

    Спасибо за пост!


  1. anverbogatov
    10.11.2023 13:00

    Степан, отличный пост! Благодарю!


  1. TatianaFilippova
    10.11.2023 13:00

    Степан, спасибо! Поздравляю с первой статьей ОТП на Хабре!


  1. Serav
    10.11.2023 13:00
    +1

    В документации в разделе VirtualService в описании поля gateways это описано.

    The reserved word mesh is used to imply all the sidecars in the mesh. When this field is omitted, the default gateway (mesh) will be used


  1. Stok1y
    10.11.2023 13:00

    По поводу трафика, сталкивался с такой проблемой при реализации canary деплоя.
    Это поведение и особенности работы можно подсмотреть например во Flagger.
    Flagger как раз занимается такими вещами как Blue/Green, Canary deploy. И при использовании можно увидеть какик оператор расставляет лейблы и куда.


  1. EkaterinaOTP
    10.11.2023 13:00

    Степан,поздравляю с первой публикацией,так держать!


  1. Miroshama
    10.11.2023 13:00

    Благодарю за статью! Классно, что отп теперь и здесь)


  1. kiriydmitriy
    10.11.2023 13:00

    Ещё один пост от людей, не знающих инфраструктуры от слова совсем.

    Задержки Istio - в районе одной-двух Миллисекунды. Задержки аппаратной сети на раундтрипе - 10 Микросекунд. Вы собственными руками тормозите сетевой трафик в сотни раз.


  1. mrrofe
    10.11.2023 13:00

    Отлично, вы обратили внимание на важность обеспечения безопасности в настройке Istio, особенно в контексте взаимной аутентификации и шифрования трафика с использованием mTLS. Ваши примеры настройки PeerAuthentication демонстрируют гибкий подход к управлению безопасностью на различных уровнях, что может быть весьма полезным при работе с разными проектами и командами. Благодарю за подробный обзор этого аспекта внедрения Istio.