Что такое gRPC? Как работать с APISIX?
September 28, 2022
Что такое gRPC
gRPC — это RPC-фреймворк с открытым исходным кодом, разработанный Google, который направлен на унификацию способов взаимодействия между сервисами. Фреймворк использует HTTP/2 в качестве транспортного протокола и Protocol Buffers в качестве языка описания интерфейсов. Он может автоматически генерировать код для вызовов между сервисами.
Доминирование gRPC
gRPC стал стандартом среди RPC-фреймворков благодаря исключительному влиянию Google на разработчиков и облачные среды.
Хотите вызывать функции etcd? gRPC!
Хотите отправлять данные OpenCensus? gRPC!
Хотите использовать RPC в микросервисе, реализованном на Go? gRPC!
Доминирование gRPC настолько велико, что если вы не выбрали gRPC в качестве своего RPC-фреймворка, вам придется привести веские причины, почему. В противном случае кто-то всегда спросит: почему вы не выбрали mainstream gRPC? Даже Alibaba, которая активно продвигала свой RPC-фреймворк Dubbo, значительно переработала дизайн протокола в последней версии Dubbo 3, изменив его на вариант gRPC, совместимый как с gRPC, так и с Dubbo 2. Фактически, вместо того чтобы говорить, что Dubbo 3 — это обновление Dubbo 2, это больше похоже на признание превосходства gRPC.
Многие сервисы, предоставляющие gRPC, также предоставляют соответствующие HTTP-интерфейсы, но такие интерфейсы часто предназначены только для совместимости. Версия gRPC предлагает гораздо лучший пользовательский опыт. Если вы можете получить доступ через gRPC, вы можете напрямую импортировать соответствующий SDK. Если вы можете использовать только обычные HTTP API, вас обычно направят на страницу документации, и вам нужно будет самостоятельно реализовать соответствующие HTTP-операции. Хотя доступ через HTTP может генерировать соответствующий SDK через спецификацию OpenAPI, лишь немногие проекты относятся к HTTP-пользователям так же серьезно, как к gRPC, поскольку HTTP имеет низкий приоритет.
Стоит ли использовать gRPC
APISIX использует etcd в качестве центра конфигураций. Начиная с версии v3, etcd перенес свои интерфейсы на gRPC. Однако в экосистеме OpenResty ни один проект не поддерживает gRPC, поэтому APISIX может вызывать только HTTP API etcd. HTTP API etcd предоставляется через gRPC-gateway. По сути, etcd запускает HTTP-прокси для gRPC на своей стороне сервера, а затем внешние HTTP-запросы преобразуются в gRPC-запросы через gRPC-gateway. После нескольких лет использования этого метода взаимодействия мы обнаружили некоторые проблемы в работе между HTTP API и gRPC API. Наличие gRPC-gateway не означает, что доступ через HTTP полностью поддерживается. Все еще существуют тонкие различия.
Вот список связанных проблем, с которыми мы столкнулись с etcd за последние несколько лет:
- gRPC-gateway отключен по умолчанию. Из-за невнимательности разработчиков, в некоторых проектах gRPC-gateway не включен в конфигурации по умолчанию. Поэтому нам пришлось добавить инструкции в документацию, чтобы проверить, включен ли gRPC-gateway в текущей версии etcd. См. https://github.com/apache/apisix/pull/2940.
- По умолчанию gRPC ограничивает размер ответа до 4 МБ. etcd снимает это ограничение в предоставляемом SDK, но забыл сделать это в gRPC-gateway. Оказалось, что официальный etcdctl (построенный на предоставляемом SDK) работает нормально, но APISIX — нет. См. https://github.com/etcd-io/etcd/issues/12576.
- Та же проблема — на этот раз с максимальным количеством запросов для одного соединения. Реализация HTTP2 в Go имеет конфигурацию
MaxConcurrentStreams, которая контролирует количество одновременных запросов, которые может отправлять один клиент, по умолчанию это 250. Какой клиент обычно отправляет более 250 запросов одновременно? Поэтому etcd всегда использовал эту конфигурацию. Однако gRPC-gateway, "клиент", который проксирует все HTTP-запросы к локальному gRPC-интерфейсу, может превысить этот лимит. См. https://github.com/etcd-io/etcd/issues/14185. - После включения mTLS в etcd, etcd использует один и тот же сертификат как для серверного, так и для клиентского сертификата: серверный сертификат для gRPC-gateway и клиентский сертификат, когда gRPC-gateway обращается к gRPC-интерфейсу. Если расширение серверной аутентификации включено на сертификате, но расширение клиентской аутентификации не включено, это приведет к ошибке проверки сертификата. Опять же, доступ напрямую через etcdctl работает нормально (поскольку сертификат не используется как клиентский сертификат в этом случае), но APISIX — нет. См. https://github.com/etcd-io/etcd/issues/9785.
- После включения mTLS, etcd позволяет настраивать политики безопасности для информации о пользователях в сертификатах. Как упоминалось выше, gRPC-gateway использует фиксированный клиентский сертификат при доступе к gRPC-интерфейсу, а не информацию сертификата, используемую для доступа к HTTP-интерфейсу в начале. Таким образом, эта функция естественно не будет работать, поскольку клиентский сертификат фиксирован и не будет изменяться. См. https://github.com/apache/apisix/issues/5608.
Мы можем обобщить проблемы в двух пунктах:
- gRPC-gateway (и, возможно, другие попытки преобразования HTTP в gRPC) — это не серебряная пуля, которая решает все проблемы.
- Разработчики etcd не уделяют достаточного внимания HTTP-методу. И их крупнейший пользователь, Kubernetes, не использует эту функцию.
Мы не говорим здесь о проблемах конкретного программного обеспечения, etcd — это просто типичный пример использования gRPC. Все сервисы, которые используют gRPC в качестве основного RPC-фреймворка, имеют схожие ограничения в поддержке HTTP.
Как APISIX 3.0 решает эту проблему
Как говорится, "если гора не идет к Магомету, то Магомет идет к горе". Если мы реализуем gRPC-клиент под OpenResty, мы сможем напрямую взаимодействовать с gRPC-сервисом.
Учитывая объем работы и стабильность, мы решили разработать на основе часто используемой библиотеки gRPC, а не изобретать велосипед. Мы рассмотрели следующие библиотеки gRPC:
- gRPC-сервис NGINX. NGINX не предоставляет gRPC внешним пользователям, даже высокоуровневый API. Если вы хотите его использовать, вам придется скопировать несколько низкоуровневых функций и затем интегрировать их в высокоуровневый интерфейс. Интеграция вызовет дополнительные трудозатраты.
- Официальная библиотека gRPC для C++. Поскольку наша система основана на NGINX, интеграция библиотек на C++ может быть немного сложной. Кроме того, зависимость этой библиотеки составляет около 2 ГБ, что будет большим вызовом для сборки APISIX.
- Официальная реализация gRPC на Go. Go имеет мощный инструментарий, и мы можем быстро собрать проекты. Однако, к сожалению, производительность этой реализации далека от версии на C++. Поэтому мы рассмотрели другую реализацию на Go: https://github.com/bufbuild/connect-go/. К сожалению, производительность этого проекта также не лучше официальной версии.
- Реализация библиотеки gRPC на Rust. Эта библиотека была бы естественным выбором для сочетания управления зависимостями и производительности. К сожалению, мы не знакомы с Rust и не стали бы делать на нее ставку.
Учитывая, что операции gRPC-клиента в основном связаны с вводом-выводом, требования к производительности не являются приоритетными. После тщательного рассмотрения мы реализовали его на основе Go-gRPC.
Для координации с планировщиком корутин Lua мы написали модуль NGINX на C: https://github.com/api7/grpc-client-nginx-module. Изначально мы хотели интегрировать код Go в этот C-модуль, скомпилировав его в статически связанную библиотеку через cgo. Однако мы обнаружили, что поскольку Go — это многопоточное приложение, дочерний процесс не наследует все потоки родительского процесса после форка. Нет способа адаптироваться к архитектуре master-worker с несколькими процессами в NGINX. Поэтому мы скомпилировали код Go в динамически связанную библиотеку (DLL) и затем загрузили ее в рабочий процесс во время выполнения.
Мы реализовали механизм очереди задач для координации корутин Go с корутинами Lua. Когда код Lua инициирует операцию ввода-вывода gRPC, он отправляет задачу на сторону Go и приостанавливает себя. Корутина Go выполнит эту задачу, и результат выполнения будет записан в очередь. Фоновый поток на стороне NGINX потребляет результат выполнения задачи, перепланирует соответствующую корутину Lua и продолжает выполнение кода Lua. Таким образом, операции ввода-вывода gRPC ничем не отличаются от обычных операций с сокетами с точки зрения кода Lua.
Теперь большая часть работы модуля NGINX на C завершена. Все, что нам нужно сделать, — это взять файл .proto etcd (который определяет его gRPC-интерфейс), изменить его и затем загрузить файл в Lua, чтобы получить следующий клиент etcd:
local gcli = require("resty.grpc") assert(gcli.load("t/testdata/rpc.proto")) local conn = assert(gcli.connect("127.0.0.1:2379")) local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = ngx.var.arg_key}}, {timeout = 30000}) if not st then ngx.status = 503 ngx.say(err) return end for i = 1, (ngx.var.arg_count or 10) do local res, err = st:recv() ngx.log(ngx.WARN, "received ", cjson.encode(res)) if not res then ngx.status = 503 ngx.say(err) break end end
Эта реализация на основе gRPC лучше, чем lua-resty-etcd, проект клиента etcd-HTTP, который содержит 1600 строк кода только на Lua.
Конечно, нам еще далеко до замены lua-resty-etcd. Чтобы полностью интегрироваться с etcd, grpc-client-nginx-module также должен реализовать следующие функции:
- Поддержка mTLS
- Поддержка конфигурации метаданных gRPC
- Поддержка конфигурации параметров (например,
MaxConcurrentStreamsиMaxRecvMsgSize) - Поддержка запросов на уровне L4
К счастью, мы заложили основу, и поддержка этих функций — это лишь вопрос времени.
grpc-client-nginx-module будет интегрирован в APISIX 3.0, после чего пользователи APISIX смогут использовать методы этого модуля в плагинах APISIX для прямого взаимодействия с gRPC-сервисами.
С нативной поддержкой gRPC, APISIX получит лучший опыт работы с etcd и откроет двери для таких функций, как проверка здоровья gRPC и отчетность данных open telemetry на основе gRPC.
Мы с нетерпением ждем появления большего количества функций APISIX, основанных на gRPC, в будущем!