Мы гордимся тем, что открываем исходный код 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 с открытым исходным кодом. Переход к безопасному в смысле памяти Интернету может показаться невозможным, но мы надеемся, что вы присоединитесь.