Serverless функции на Rust: AWS Lambda от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Rust для Serverless — это прорыв

Serverless-архитектура уже давно стала стандартом для построения масштабируемых и экономичных приложений. Однако традиционный выбор языков (Python, Node.js, Java) часто сталкивается с проблемой холодного старта и высоким потреблением памяти. Rust, с его нулевой стоимостью абстракций и отсутствием сборщика мусора, предлагает революционное решение. В этой статье мы разберем, как создавать, настраивать и деплоить serverless функции на Rust для AWS Lambda — от установки инструментов до production-релизов.

К концу статьи вы сможете:

  • Настроить окружение для разработки Rust + AWS Lambda
  • Написать эффективную функцию с обработкой событий
  • Оптимизировать бинарный файл для минимального времени холодного старта
  • Развернуть функцию в AWS с помощью Cargo Lambda

Подготовка окружения: Cargo Lambda и AWS CLI

Для работы с Rust на AWS Lambda нам понадобится специальный инструмент — cargo-lambda. Он автоматизирует сборку, тестирование и деплой. Установим его через Cargo:

cargo install cargo-lambda

Также убедитесь, что у вас установлены aws-cli (настроенный с IAM-ключами) и Docker (для локального тестирования). Создадим новый проект:

cargo lambda new my-rust-lambda
cd my-rust-lambda

Команда создаст базовую структуру с файлом Cargo.toml и src/main.rs. Обратите внимание на зависимости в Cargo.toml:

[package]
name = "my-rust-lambda"
version = "0.1.0"
edition = "2021"

[dependencies]
lambda_runtime = "0.11"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["fmt"] }

Ключевая библиотека — lambda_runtime. Она предоставляет трейт LambdaHandler, который мы будем реализовывать.

Написание первой функции: обработка API Gateway запроса

Рассмотрим классический сценарий: функция, принимающая JSON с именем пользователя и возвращающая приветствие. Откроем src/main.rs и напишем следующий код:

use lambda_runtime::{run, service_fn, Error, LambdaEvent};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request {
    name: String,
}

#[derive(Serialize)]
struct Response {
    message: String,
}

async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    let name = event.payload.name;
    let message = format!("Привет, {}! Ты только что запустил Rust на Lambda!", name);
    Ok(Response { message })
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .init();

    run(service_fn(function_handler)).await
}

Разберем ключевые моменты:

  • LambdaEvent<Request> — структура, содержащая тело запроса и контекст выполнения.
  • #[tokio::main] — асинхронный рантайм, необходимый для работы Lambda runtime.
  • serde — десериализация входящего JSON и сериализация ответа.

Протестируем локально:

cargo lambda watch

Затем в другом терминале:

cargo lambda invoke --data-ascii '{"name": "Алексей"}'

Вы должны увидеть: {"message":"Привет, Алексей! Ты только что запустил Rust на Lambda!"}

Оптимизация холодного старта и размера бинарника

Главное преимущество Rust — минимальный холодный старт. Однако чтобы его достичь, нужно правильно собрать бинарник. Добавим в Cargo.toml секцию профиля release:

[profile.release]
opt-level = "z"     # Минимизация размера
lto = true          # Link-time optimization
codegen-units = 1   # Лучшая оптимизация
strip = true        # Удаление символов debug
panic = "abort"     # Уменьшение размера за счет abort при панике

Теперь соберем функцию:

cargo lambda build --release

Бинарный файл будет весить около 3-5 МБ (против 50+ МБ для Node.js). Для сравнения: холодный старт Rust-функции обычно занимает 100-200 мс, тогда как Python — 500-1000 мс.

Дополнительные советы по оптимизации:

  • Используйте mimalloc вместо стандартного аллокатора (добавьте mimalloc = "0.1" и установите глобальный аллокатор).
  • Избегайте динамических библиотек — собирайте статически.
  • Минимизируйте количество зависимостей — каждая крейта увеличивает время компиляции и размер.

Деплой в AWS Lambda и настройка триггеров

Теперь развернем функцию в облаке. Сначала создайте IAM-роль с базовыми правами для Lambda. Затем выполните:

cargo lambda deploy --iam-role arn:aws:iam::XXXXXXXXXXXX:role/lambda-execution-role

После успешного деплоя вы увидите ARN функции. Теперь настроим триггер — например, API Gateway HTTP API. Самый простой способ — через AWS Console:

  1. Перейдите в Lambda → ваша функция → "Добавить триггер".
  2. Выберите "API Gateway" → "Create an HTTP API".
  3. Настройте маршрут POST /hello.

Теперь можно отправлять запросы:

curl -X POST https://YOUR_API_ID.execute-api.REGION.amazonaws.com/hello \
  -H "Content-Type: application/json" \
  -d '{"name": "Мир"}'

Ответ: {"message":"Привет, Мир! Ты только что запустил Rust на Lambda!"}

Для production-среды рекомендую использовать Infrastructure as Code (Terraform, Pulumi или AWS CDK). Пример деклара

Блоги

Рекомендаций по книгам