Комментарии 6
Разработчики, когда пишут REST API, очень много времени могут тратить не на бизнес-код, а на различную логику, которую можно заменить одним только прокси.
На практике логика размазывается со слоя разработки на ещё и прокси, что приводит к увеличению когнитивной нагрузки. Всё прекрасно до первых инцедентов, в которых придётся лезть в код сначала прокси, а потом приложения.
Кстати, внедрение фич теперь тоже занимает больше человеко-часов. Предположим, добавляется новый роут, в котором требуется авторизация/аутентификация пользователя и запрещены retry (предположим, нельзя повторять платёж). Эту информацию разработчик доносит до девопса, который раздувает свой конфиг на ещё полэкрана ямла, потом они вместе это тестируют... В конце концов разработчику быстрее самому написать этот конфиг, но он же мог сделать это и в коде приложения.
На мой взгляд, вынос бизнес-логики на уровень балансировщика является плохой идеей, хотя и выглядит красиво. Нагрузку распределить - пожалуйста, а вот что-то сложнее пусть будет там, где ему и место - в приложении.
Большинство сетевых проблем, а также типичные ответы HTTP (особенно ошибки 5xx), поддаются стандартным подходам к обработке. Пытаться добавлять сложную логику на стороне приложения для таких ситуаций – это не только увеличивает вероятность багов, но и нарушает принципы разделения ответственности.
Лучше избегать превращения микросервисов в раздутые сущности или монолиты, нагруженные лишней логикой. Для обработки сетевых сбоев и ошибок 5xx на транспортном уровне существуют проверенные решения.
Разработчику же, на мой взгляд, нет смысла углубляться в тонкости работы TCP-стека – это задача инфраструктуры и сетевых решений. Его фокус должен оставаться на бизнес-логике, а не на решении транспортных проблем.
Мне кажется, что мы друг друга не совсем поняли.
Моя позиция - бизнес-логика на стороне сервера, предоставляющего REST API, включает в себя всё, что не относится к TCP/IP и HTTP, а именно: балансировка нагрузки, SSL/TLS, маршрутизация между сервисами. Это разработчику действительно необязательно, может ему неплохо знать, как доставать значения из http-заголовков. Но вот всё, что идёт дальше, возлагается на плечи именно разработчика. У каждого сервиса есть своя зона ответственности, почему её надо дробить и вытаскивать на слой выше?
Предположим, что есть сервис для доступа к личному кабинету, доступ к которому (для пользователя) осуществляется путём предоставления http-заголовка Authorization: Bearer <токен>
. С envoy обработка токена и решение пустить/запретить произойдёт на балансировщике, приложение даже не узнает о том, что к нему кто-то хотел сходить, верно? А раз слоя авторизации в сервисе нет, то внутри вашего контура (за envoy) к нему может ходить кто угодно как угодно и эти запросы будут считаться легитимными, так? Такой вариант нарушает принцип zero trust, создаёт дыру в безопасности и позволяет разработчикам с честными глазами говорить - "а мы ничего не знаем, к нам ничего не ходило, идите к девопсу". Ну или "нас защищает кто-то выше, мы только отдаём данные". Разве сервис не должен защищать свои данные?
С точки зрения разбора инцидентов система стала только сложнее, всё, что становится легче - это минус пара дней работы разработчика во время написания сервиса; пострадала эксплуатация.
Вот кстати хороший пример: платёжный шлюз пытается донести в вашу систему информацию о проведённом платёже - делает POST {json}
на ваш /api/payment и получает 403. Заметьте, 403 может отдавать как envoy, так и сервис payment (по причине бизнес-логики или опечатки). Пользователь получает ошибку от платёжного шлюза "что-то пошло не так, обратитесь в вашу систему". Пользователь пишет вам в техподдержку, техподдержка идёт к разработчику сервиса payment, разработчик идёт к девопсу... чтобы что? Чтобы они вместе перелопатили сначала конфиги envoy, а потом и сервиса /api/payment? Три-четыре инцидента и сэкономленные два дня обнулятся, ещё парочка и вы заплатите больше за рабочие часы разраба+девопса.
Лучше избегать превращения микросервисов в раздутые сущности или монолиты, нагруженные лишней логикой.
Вы вынесли "лишнюю логику" на уровень выше, да ещё и в одно место, которое через пару лет превратится в монолит на 1к+ строчек yaml-кода с регулярками и прочими синтаксическими ужасами, которые ещё и проверить нельзя. При этом логика не отвязалась от приложения, а просто скрылась до поры до времени.
Я не против инструмента, но если им злоупотреблять, то он превратится в серебряную пулю, которая будет еженедельно оказываться в одной из ног.
По статье: спрячьте, пожалуйста, конфиги под спойлер, например,envoy.filters.http.oauth2
занимает два экрана.
Здесь я хотел больше сосредоточиться на использовании Envoy как Service mesh или Ambient Mesh в legacy инфраструктуре, а не превращать его в огромный gateway с кучей логики на борту. Конечно, часть функционала нужно будет вынести в приложение, но акцент я делал именно на TCP-стек и его взаимодействие. Я бы не стал добавлять JWT-аутентификацию в Envoy, а перенес её в приложение, если это действительно необходимо. В этом вопросе есть нюанс: где-то это может быть полезно, а где-то — нет. Каждый решает для себя, нужно ли это вообще. Поэтому я и не приводил конкретных примеров, их может быть слишком много, и каждый проект требует индивидуального подхода
Если уж брать JWT-аутентификацию, то полезней реализовать ее через sidecar непосредственно рядом с приложением(krakend, gogatekeeper и тд). Sidecar для того и сделаны чтоб расширять функциональность приложения без изменения самого приложения. В этом случае достигается простота сопровождения: листинг настроек в едином месте и сами настройки можно отдать на сторону разрабов(под контроллем); нет нужды допиливать приложение под функционал аутентификации; логи в едином месте рядом с приложением.
Envoy — как писать чистый бизнес-код для микросервисной архитектуры