Привет, Хабр!
Сегодня мы рассмотрим, как поднять gRPC‑микросервис на tonic и обвязать его аутентификацией плюс метриками через Tower‑middleware.
Зачем gRPC в Rust-экосистеме микросервисов?
gRPC уверенно занимает свою нишу в Rust‑экосистеме микросервисов, потому что даёт то, чего не хватает REST — настоящую пропускную способность, строгую типизацию и стабильную двустороннюю коммуникацию. По бенчмаркам, при серьёзной параллельной нагрузке (500 клиентов и более) gRPC способен держать до 9× больше запросов, чем REST на JSON, а при использовании protobuf через HTTP — выигрывает около 30%.
Второй козырь — это зрелый сетевой стек: gRPC работает поверх HTTP/2 с полноценным мультиплексированием, без проблем head‑of‑line blocking и с поддержкой стриминга и bidirectional‑каналов сразу из коробки. Кодогенерация на основе .proto создаёт строгие SDK с полной типобезопасностью. А сам tonic — это стабильная обёртка на Tokio и Hyper, активно поддерживаемая и адаптированная под актуальные версии Hyper 1.0 и Prost 0.13. В общем, всё, что нужно для адекватного сервиса, тут уже есть.
Подготовка .proto и генерация кода
На первом этапе мы определяем gRPC‑контракт — для этого создаём отдельную директорию proto/, в ней описываем структуру и API нашего сервиса на языке Protocol Buffers. Получается базовая структура проекта:
my-svc/ ├─ Cargo.toml ├─ build.rs └─ proto/ └─ greeter.proto
Содержимое greeter.proto:
syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
Объявляем сервис Greeter с одним методом SayHello, который принимает HelloRequest с полем name и возвращает HelloReply с текстом ответа.
Теперь нужно сгенерировать код на Rust, который реализует этот контракт. Это делается с помощью tonic-build — плагина, который по .proto файлам генерирует клиентские и серверные трейты и структуры. Для этого юзаем build.rs:
fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::configure() // настройка генерации .build_server(true) // генерируем серверную сторону .build_client(true) // и клиентскую .compile(&["proto/greeter.proto"], &["proto"])?; // путь к .proto и include-пути Ok(()) }
configure() позволяет настроить, что именно мы хотим сгенерировать, указываем, что нужны и сервер, и клиент. compile() принимает список файлов .proto и include‑пути (их может быть несколько, если .proto‑файлы разбиты по модулям). Всё это интегрировано с системой сборки Cargo: при первом cargo build код будет сгенерирован автоматически и попадёт в папку OUT_DIR, откуда мы его позже подключим через tonic::include_proto!.
Фрагмент Cargo.toml, нужный для этого:
[dependencies] tokio = { version = "1.38", features = ["rt-multi-thread", "macros"] } tonic = { version = "0.13", features = ["transport", "tls"] } prost = "0.13" # для метрик и интерсепторов tower = "0.5" tower-http = { version = "0.5", features = ["trace"] } tracing = "0.1" tracing-subscriber = "0.3"
На выходе получаем полностью типизированный код — трейты сервиса, структуры сообщений, клиентскую обёртку, серверный интерфейс и всё это совместимо с остальным Rust‑кодом.
Асинхронный сервер на tonic
Создаём gRPC‑сервер, который реализует интерфейс Greeter и отвечает на метод SayHello. Подключаем сгенерированный код и реализуем логику:
use tonic::{transport::Server, Request, Response, Status}; pub mod greeter { tonic::include_proto!("helloworld"); } #[derive(Default)] pub struct MyGreeter; #[tonic::async_trait] impl greeter::greeter_server::Greeter for MyGreeter { async fn say_hello( &self, request: Request<greeter::HelloRequest>, ) -> Result<Response<greeter::HelloReply>, Status> { let name = request.into_inner().name; let reply = greeter::HelloReply { message: format!("Привет, {name}!") }; Ok(Response::new(reply)) } }
Главный main.rs:
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { tracing_subscriber::fmt::init(); let addr = "0.0.0.0:50051".parse()?; let greeter = MyGreeter::default(); // Перехватчик для заголовочного токена let auth_interceptor = tonic::service::Interceptor::new(|req: Request<()>| { match req.metadata().get("authorization") { Some(v) if v == "Bearer super-secret" => Ok(req), _ => Err(Status::unauthenticated("missing or invalid token")), } }); Server::builder() // Tower-слой для трассировки и метрик .layer( tower::ServiceBuilder::new() .layer(tower_http::trace::TraceLayer::new_for_grpc()) .into_inner(), ) .add_service( greeter::greeter_server::GreeterServer::with_interceptor(greeter, auth_interceptor), ) .serve(addr) .await?; Ok(()) }
Interceptor в tonic реализуется через замыкание FnMut(Request) -> Result. TraceLayer из tower‑http сразу умеет выводить tracing‑спаны для gRPC‑методов.
Немного про безопасность
Чтобы сервер шифровал трафик, включаем фичу tls или tls-ring и настраиваем Server::builder().tls_config(...). Если нужно mTLS (взаимная аутентификация), обязательно подключаем клиентские сертификаты и валидацию subjectAltName.
Ну и конечно не забываем про базовые защиты на уровне middleware: concurrency_limit, timeout, rate_limit, in_flight_requests из Tower — это обязательный слой при боевом деплое. Он защищает от внезапных всплесков, медленных клиентов и сетевых глюков.
Метрики и Prometheus
Если хотите интеграцию с Prometheus:
use tower_http::metrics::{InFlightRequestsLayer, PrometheusMetricLayer};
Подключаем:
.layer(InFlightRequestsLayer::new()) .layer(PrometheusMetricLayer::new())
Сами метрики можно затем отдавать через отдельный HTTP‑сервер (например, на axum, warp, hyper) — это уже зависит от общей архитектуры проекта.
Стуб-клиент
Для обращения к gRPC‑сервису используем автоматически сгенерированный клиент. Он типизирован, работает асинхронно и поддерживает те же механизмы Interceptor’ов, что и сервер.
Пример:
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // Создаём канал к серверу let channel = tonic::transport::Channel::from_static("http://localhost:50051") .connect() .await?; // Оборачиваем канал в клиент с Interceptor'ом let mut client = greeter::greeter_client::GreeterClient::with_interceptor( channel, |mut req: Request<()>| { req.metadata_mut() .insert("authorization", "Bearer super-secret".parse().unwrap()); Ok(req) }, ); // Отправляем gRPC-запрос let reply = client .say_hello(greeter::HelloRequest { name: "Habr".into() }) .await? .into_inner(); println!("Server answered: {}", reply.message); Ok(()) }
СоздаемChannel, который является абстракцией над gRPC‑транспортом. Он может быть from_static, либо динамически через DNS / load balancing.
GreeterClient::with_interceptor позволяет внедрить middleware на клиентской стороне — удобно, чтобы централизованно добавлять токены, трейсинг, идентификаторы, tenant‑id и т. п.
say_hello(...) вызывается как обычная асинхронная функция — типизация, структура запроса и ответа полностью совпадают с тем, что было описано в .proto.
Клиентский Interceptor использует точно такой же API, как и серверный — это даёт единообразие в обработке метаданных. Один раз написали обёртку — используете и на входящих, и на исходящих вызовах.
А ну и еще, если бы мы не использовали Interceptor, можно было бы добавлять метаданные вручную к каждому отдельному запросу, но это не масштабируется. Лучше один раз обернуть — и забыть.
Если вы уже обкатывали gRPC в Rust или внедряли что‑то похожее с tonic, делитесь своим опытом в комментариях — какие подходы сработали, какие мидлвары зашли и как решали вопросы с безопасностью.
Когда разбираешься с gRPC, хочется верить, что это решает всё. Но настоящая боль начинается там, где нужно продумать бизнес-логику, распределить ответственность между сервисами, не утонуть в очередях сообщений и не превратить архитектуру в клубок. Если вы тоже на этом этапе — заглядывайте на открытые уроки:
-
28 июля, 20:00
Основы проектирования бизнес-логики в микросервисной архитектуре
Как не похоронить масштабируемость под слоем хаотичной логики. Разбор паттернов, ошибок и живых кейсов. -
11 августа, 20:00
RabbitMQ против Kafka — что выбрать для вашей структуры
Что реально стоит за этими системами, когда вы работаете не с туториалами, а с продом. Честное сравнение, нюансы и рекомендации для вашей архитектуры.
Пройдите вступительный тест и получите скидку на курс «Microservice Architecture».
ссылка на оригинал статьи https://habr.com/ru/articles/930778/
Добавить комментарий