Pull to refresh

Cloudflare открыла код Rust-фреймворка для программируемых сетевых сервисов — Pingora

Level of difficultyEasy
Reading time6 min
Views5.1K
Original author: Yuchen Wu, Edward Wang, Andrew Hauck



Мы гордимся тем, что открываем исходный код Pingora — фреймворка на Rust, который мы используем для создания сервисов, обеспечивающих значительную часть трафика в Cloudflare. Pingora выпускается под лицензией Apache 2.0.


Как упоминалось в нашем предыдущем посте, Pingora — асинхронный многопоточный фреймворк на Rust, который помогает нам создавать прокси-сервисы HTTP. С момента нашей последней публикации в блоге Pingora обработал почти квадриллион [1015] интернет-запросов в нашей глобальной сети.


Мы делаем это, чтобы помочь построить лучший и более безопасный Интернет за пределами нашей собственной инфраструктуры. Мы хотим предоставить инструменты, идеи и вдохновение нашим клиентам, пользователям и другим лицам для создания собственной интернет-инфраструктуры с использованием безопасного для памяти фреймворка. Наличие такой структуры особенно важно, учитывая растущее понимание важности безопасности памяти в отрасли. Для достижения этой общей цели мы сотрудничаем с Группой исследования интернет-безопасности (ISRG) Prossimo Project, чтобы способствовать внедрению Pingora в самой критической инфраструктуре Интернета.


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


Pingora предоставляет строительные блоки не только для прокси, но также для клиентов и серверов. Наряду с этими компонентами мы также предоставляем несколько служебных библиотек, реализующих общую логику, такую ​​как подсчёт событий, обработка ошибок и кеширование.


Что внутри


Pingora предоставляет библиотеки и API для создания сервисов поверх HTTP/1 и HTTP/2, TLS или просто TCP/UDP. В качестве прокси-сервера он поддерживает сквозное проксирование HTTP/1 и HTTP/2, gRPC и WebSocket. (Поддержка HTTP/3 — в планах). Pingora также включает в себя настраиваемые стратегии балансировки нагрузки и аварийного переключения. Чтобы соответствовать требованиям и безопасности он поддерживает как широко используемые библиотеки OpenSSL, так и BoringSSL, которые соответствуют требованиям FIPS [федеральных стандартов обработки информации США] и пост-квантового шифрования.


Помимо этих функций, Pingora предоставляет фильтры и обратные вызовы, позволяющие пользователям полностью настраивать то, как сервис должен обрабатывать, преобразовывать и пересылать запросы. Эти API будут особенно знакомы пользователям OpenResty и NGINX, поскольку многие из них интуитивно сопоставляются с обратными вызовами OpenResty "*_by_lua".


В рабочем режиме Pingora обеспечивает плавный перезапуск без простоев для самостоятельного обновления, не теряя ни одного входящего запроса. Syslog, Prometheus, Sentry, OpenTelemetry и другие необходимые инструменты наблюдения легко интегрируются с Pingora.


Кому может быть полезен Pingora


Вам следует рассмотреть Pingora, если:


  • Безопасность — главный приоритет. Pingora — это более безопасная в смысле памяти альтернатива сервисам на C/C++. Хотя некоторые могут спорить о безопасности памяти среди языков программирования, наш практический опыт показывает, что у нас гораздо меньше шансов совершить ошибки кодинга, которые приводят к проблемам с безопасностью памяти. Кроме того, поскольку мы тратим меньше времени на решение этих проблем, то более продуктивно внедряем новые функции.
  • Ваш сервис чувствителен к производительности: Pingora работает быстро и эффективно. Как объяснялось в нашем предыдущем сообщении в блоге, мы сэкономили много ресурсов ЦП и памяти благодаря многопоточной архитектуре Pingora. Экономия времени и ресурсов может оказаться существенной для рабочих нагрузок, чувствительных к стоимости и/или скорости системы.
  • Ваш сервис требует обширной настройки: API, предоставляемые прокси-инфраструктурой Pingora, легко программируются. Для пользователей, которые хотят создать настраиваемый и расширенный шлюз или балансировщик нагрузки, Pingora предлагает мощные, но простые способы его реализации. Ниже мы приводим примеры.

Создадим балансировщик нагрузки


Изучим программируемый API Pingora: создадим простой балансировщик нагрузки. Он будет по кругу выбирать между https://1.1.1.1/ и https://1.0.0.1/ вышестоящим в потоке данных [upstream] сервером.


Сначала создадим пустой HTTP-прокси:


pub struct LB();

#[async_trait]
impl ProxyHttp for LB {
 async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
 todo!()
 }
}

Любой объект, реализующий трейт ProxyHttp ([трейт] схож с интерфейсом в C++ или Java), является HTTP-прокси. Единственный обязательный метод — upstream_peer(), который вызывается для каждого запроса. Эта функция должна возвращать HttpPeer, содержащий исходный IP-адрес для подключения и способ подключения к нему.


Реализуем циклический перебор. Фреймворк Pingora уже предоставляет LoadBalancer общие алгоритмы выбора, такие как циклический перебор и хеширование, поэтому просто воспользуемся им. Если ваш случай требует более сложной или настраиваемой логики выбора сервера, пользователи могут просто реализовать её в этой функции самостоятельно.


pub struct LB(Arc<LoadBalancer<RoundRobin>>);

#[async_trait]
impl ProxyHttp for LB {
 async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
 let upstream = self.0
 .select(b"", 256) // при выборе по кругу хеш не имеет значения
 .unwrap();

 // Установка SNI в значение one.one.one.one
 let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
 Ok(peer)
 }
}

Поскольку мы подключаемся к HTTPS-серверу, ещё необходимо установить SNI. Сертификаты, таймауты и другие параметры подключения, если это необходимо, также можно установить здесь, в объекте HttpPeer.


Наконец, приведём сервис в действие. В этом примере мы жёстко запрограммировали IP-адреса исходного сервера. При реальных рабочих нагрузках IP-адреса исходного сервера также можно обнаружить динамически при вызове upstream_peer() или в фоновом режиме. После создания сервиса мы просто сообщаем сервису балансировщика нагрузки прослушивать 127.0.0.1:6188. В конце концов мы создали сервер Pingora, и он будет процессом, запускающим сервис балансировки:


fn main() {
 let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();

 let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
 lb.add_tcp("127.0.0.1:6188");

 let mut my_server = Server::new(None).unwrap();
 my_server.add_service(lb);
 my_server.run_forever();
}

Попробуем:


curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden

Мы видим, что прокси-сервер работает, но исходный сервер отклоняет нас с кодом 403. Это связано с тем, что наш сервис просто проксирует заголовок Host, 127.0.0.1:6188, установленный Curl, что расстраивает исходный сервер. Как нам заставить прокси это исправить? Это можно сделать, просто добавив ещё один фильтр под названием upstream_request_filter. Он запускается при каждом запросе после подключения исходного сервера и до отправки любого HTTP-запроса. В этом фильтре мы можем добавлять, удалять или изменять заголовки HTTP-запросов.


async fn upstream_request_filter(…, upstream_request: &mut RequestHeader, …) -> Result<()> {
 upstream_request.insert_header("Host", "one.one.one.one")
}

Попробуем ещё раз:


curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK

На этот раз работает! Полный пример можно найти здесь.


Ниже приведена очень простая диаграмма того, как этот запрос проходит через обратный вызов и фильтр в этом примере. Прокси-фреймворк Pingora сейчас предоставляет больше фильтров и обратных вызовов на разных этапах запроса, что позволяет пользователям изменять, отклонять, маршрутизировать и/или логировать запрос (и ответ):



За кулисами прокси-фреймворк Pingora заботится о пуле соединений, рукопожатиях TLS, чтении, записи, анализе запросов и о решении любых других распространённых задачах прокси, чтобы пользователи могли сосредоточиться на важной для них логике.


Открытый исходный код, настоящее и будущее


Pingora — это библиотека и набор инструментов, а не двоичный исполняемый файл. Другими словами, Pingora — двигатель, приводящий автомобиль в движение, а не сам автомобиль. Хотя Pingora готов к использованию в продакшене, мы понимаем, что многим людям нужен готовый к работе веб-сервис с батарейками и опциями конфигурации с малым количеством кодирования или без него. Создание такого приложения поверх Pingora будет в центре внимания нашего сотрудничества с ISRG с целью расширения охвата Pingora. Следите за объявлениями об этом проекте [в блоге].


Другие предостережения, которые следует иметь в виду:


  • Сегодня стабильность API не гарантируется. Хотя мы постараемся свести к минимуму частоту внесения изменений, мы оставляем за собой право добавлять, удалять или изменять такие компоненты, как фильтры запросов и ответов, по мере развития библиотеки, особенно в период до версии 1.0.
  • Поддержка операционных систем на основе не-Unix [non-Unix] в настоящее время не предусмотрена. У нас нет ближайших планов по поддержке этих систем, хотя это может измениться в будущем.

Как внести вклад


Не стесняйтесь сообщать об ошибках, проблемах с документацией или запрашивать новые функции в нашем трекере проблем на GitHub. Прежде чем открывать Pull Request, мы настоятельно рекомендуем вам ознакомиться с нашим руководством по участию.


Заключение


Независимо от того, создаёте ли вы веб-сервисы в продакшене или экспериментируете с сетевыми технологиями, мы надеемся, что Pingora вам пригодится. Это был долгий путь, но поделиться этим проектом с сообществом Open Source было целью с самого начала. Мы хотели бы поблагодарить сообщество Rust, поскольку Pingora построен на основе множества замечательных крейтов Rust с открытым исходным кодом. Переход к безопасному в смысле памяти Интернету может показаться невозможным, но мы надеемся, что вы присоединитесь.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 18: ↑18 and ↓0+18
Comments2

Articles