Модуль 5.2 — Rust в облаке
В этом разделе мы покажем, как использовать Rust для создания облачных сервисов. Первое упражнение использует AWS от Amazon, а второе — Scaleway, европейского облачного провайдера.
Упражнение 5.2.1: Lettuce Crop на AWS
В этом упражнении мы перенесём наш сайт Lettuce Crop из упражнения 5.1.1 в облако с помощью AWS Lambda. AWS Lambda позволяет запускать код в облаке в бессерверной (serverless) конфигурации. Это значит, что машины в дата-центрах Amazon будут автоматически запускать ваш код при необходимости: заботиться о серверах не нужно, а платите вы только за фактически использованное время вычислений.
Для этого упражнения должно хватить бесплатного уровня AWS. Однако не забудьте отключить свои Lambda-функции после тестирования, чтобы избежать непредвиденных расходов! Посмотреть, сколько квот бесплатного уровня вы ещё не использовали в этом месяце, можно на странице бесплатного уровня в разделе Billing and cost management консоли AWS.
5.2.1.A Установка Cargo Lambda
Чтобы собирать код на Rust для AWS Lambda, будем использовать Cargo Lambda. Установить Cargo Lambda можно с помощью Cargo Binstall:
cargo binstall cargo-lambda
Может понадобиться и Zig, который используется для кросс-компиляции. Если Zig не установлен, Cargo Lambda сообщит об этом при сборке Lambda и попытается помочь установить его автоматически через pip или npm.
Также можно воспользоваться любым другим способом установки Cargo Lambda, описанным здесь.
5.2.1.B Роутер axum с lambda_http
Крейт lambda_runtime предоставляет рантайм для AWS Lambda, написанных на Rust. Крейт lambda_http добавляет слой абстракции поверх lambda_runtime, который упрощает разработку HTTP-серверов на AWS Lambda с помощью Rust. Это идеально подходит для небольших динамических сайтов и REST API.
Добавьте lambda_http в проект командой:
cargo add lambda_http
Поскольку lambda_http умеет запускать роутеры axum, почти ничего менять не нужно: достаточно изменить функцию main, чтобы превратить наш сервер Lettuce Crop в Lambda. Роутер создаём как обычно, но вместо того, чтобы отдавать его через axum::serve, запускаем его функцией run из lambda_http:
use lambda_http::{run, tracing, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
// нужно, чтобы рантайм включил логирование ошибок в CloudWatch
tracing::init_default_subscriber();
let app = Router::new()
.route("/crop", post(crop_image))
.fallback_service(ServeDir::new("assets"));
run(app).await
}
Обновите функцию main, как показано выше, и затем попробуйте Lambda командой:
cargo lambda watch
Эта команда эмулирует Lambda локально на вашем устройстве. По умолчанию сервер доступен по адресу http://localhost:9000/.
5.2.1.C Создание функции Lambda в консоли AWS
Теперь, когда мы протестировали Lambda локально, создадим функцию Lambda в консоли AWS. Перейдите на страницу AWS Lambda в консоли AWS и нажмите «Create a function». Затем настройте её так:
- Выберите «Author from scratch» (создать с нуля).
- Задайте имя, например
lettuce-crop. - Выберите рантайм «Amazon Linux 2023».
- Выберите архитектуру «arm64» (она дешевле, чем x86_64).
- В разделе «Additional Configurations» включите «Enable function URL» и выберите тип аутентификации «NONE», чтобы получить общедоступный URL для функции Lambda.
Наконец, нажмите «Create function» и подождите несколько секунд, пока Lambda будет создана.
5.2.1.D Сборка и развёртывание функции Lambda
Прежде чем развернуть Lambda, нужно собрать проект для нужной архитектуры:
cargo lambda build --release --arm64 --output-format zip
Эта команда создаст файл bootstrap.zip в папке target/lambda/{имя проекта}, который можно загрузить в консоль AWS, чтобы развернуть Lambda.
Однако этот zip-файл не содержит наши ресурсы. Если мы хотим, чтобы Lambda отдавала HTML-документ, а также соответствующие CSS-файл и изображение, эти ресурсы нужно включить в архив. Создадим конфигурационный файл CargoLambda.toml, в котором опишем, как собирать Lambda, и добавим в него следующее:
[build]
arm64 = true
output_format = "zip"
include = ["assets/index.html", "assets/styles.css", "assets/crop.webp"]
Теперь, если собрать Lambda командой cargo lambda build --release, мы получим zip, который содержит и наши ресурсы. Аргументы командной строки --arm64 и --output-format больше не нужны: они заданы в конфигурационном файле.
Также, если вы используете memory-serve для отдачи ресурсов, как описано в упражнении 5.1.1.G, включать ресурсы в zip не нужно: они уже входят в бинарник.
Чтобы развернуть Lambda, нажмите кнопку «Upload from» на вкладке «Code» для нашей Lambda в консоли AWS и загрузите файл bootstrap.zip. Теперь Lambda должна работать! Откройте URL функции, указанный в обзоре функции вверху страницы, чтобы её проверить!
Также можно развернуть Lambda через CLI командой cargo lambda deploy. Однако для этого сначала нужно настроить учётные данные AWS.
Обратите внимание, что AWS Lambda принимает файлы размером не более 50 МБ. Для больших проектов можно загружать файлы в бакет S3. У S3 нет бессрочного бесплатного уровня, но есть 12-месячный бесплатный пробный период.
5.2.1.E Анализ использования Lambda через CloudWatch
Теперь, когда наша Lambda работает, посмотрим на консоль AWS. Если открыть вкладку «Monitor», можно увидеть метрики по запросам, которые обработала функция. Эти базовые метрики CloudWatch собирает автоматически и бесплатно.
Если прокрутить страницу вниз до CloudWatch Logs, вы увидите недавние вызовы функции Lambda. Если открыть log stream одного из таких запросов, вы увидите логи, созданные при его обработке. Вывод println! и логи крейта tracing должны появиться здесь. Бесплатный уровень CloudWatch позволяет хранить до 5 ГБ логов.
На вкладке «Monitor» также можно увидеть список самых дорогих вызовов. Стоимость измеряется в гигабайт-секундах, то есть в объёме памяти, умноженном на время обработки запроса. Бесплатный уровень AWS Lambda включает 1 000 000 запросов и 400 000 гигабайт-секунд в месяц.
По умолчанию Lambda настроены на 128 МБ памяти. Значение можно увеличить на вкладке «Configuration» (меньше 128 МБ задать нельзя). На этой же вкладке можно настроить таймаут обработки запросов. По умолчанию Lambda прерывается через 3 секунды, но при необходимости это можно изменить.
Что дальше?
- Репозиторий Rust Runtime for AWS Lambda на GitHub содержит множество полезных примеров, например, как работать с бакетами S3 или как создавать расширения Lambda.
- AWS SDK для Rust позволяет взаимодействовать с сервисами AWS из кода на Rust.
Не забудьте ограничить или удалить функцию Lambda после тестирования, чтобы избежать непредвиденных расходов!
Упражнение 5.2.2: Lettuce Crop на Scaleway
В этом упражнении мы перенесём наш сайт Lettuce Crop из упражнения 5.1.1 в облако с помощью Scaleway Serverless Functions. Scaleway Serverless Functions позволяет запускать код в облаке в бессерверной конфигурации: машины в дата-центрах Scaleway будут автоматически запускать ваш код при необходимости. О серверах заботиться не нужно, а платите вы только за фактически использованное время вычислений.
Для этого упражнения должно хватить бесплатных ресурсов Scaleway. Однако не забудьте отключить свои serverless-функции после тестирования, чтобы избежать непредвиденных расходов! Актуальную информацию о стоимости работы serverless-функций см. на странице цен serverless.
5.2.2.A Общая структура проекта
Serverless-функции в Scaleway работают так: вы предоставляете функцию-обработчик, которая принимает запрос и возвращает ответ. В exercises/5-rust-for-web/2-rust-in-the-cloud/2-lettuce-crop-scaleway уже подготовлена базовая структура проекта serverless-функции Scaleway. В src/handler.rs есть функция-обработчик, которая превращает любой запрос в ответ с телом «Hello, world!».
Чтобы Scaleway могла использовать эту функцию, настройте проект как библиотеку, указав src/handler.rs в качестве главной точки входа. Для этого добавьте в Cargo.toml следующее:
[lib]
path = "src/handler.rs"
5.2.2.B Развёртывание serverless-функции
Чтобы развернуть serverless-функцию, перейдите на страницу функций в консоли Scaleway, выберите подходящий регион и пространство имён (или создайте новое). В этом пространстве имён нажмите кнопку «Create function» и выберите Rust в качестве рантайма.
Чтобы загрузить код, сначала создайте zip-файл с каталогом src и Cargo.toml. Загрузите этот zip в Scaleway и укажите в качестве обработчика имя функции из src/handler.rs, которая будет обрабатывать запросы (в нашем случае my_handler).
Теперь можно задать имя и настроить ресурсы по необходимости, а затем создать функцию. Дождитесь развёртывания (это может занять несколько минут), после чего откройте URL эндпоинта функции, чтобы увидеть её в действии.
5.2.2.C Обработка запросов с маршрутизацией axum
Чтобы добавить этому обработчику более интересную функциональность, можно настроить роутер axum, как в упражнении 5.1.1. Однако serverless-функции нельзя запустить через axum::serve. Вместо этого можно вызвать роутер как сервис, передав ему текущий входящий запрос:
let mut router = Router::new()
.route("/", get("Hello, world!"))
.route("/crop", get("Lettuce crop!"));
router.as_service().call(req).await.unwrap()
Метод call вернёт ответ на основе запроса, используя заданные маршруты.
Измените обработчик так, чтобы он использовал роутер с несколькими маршрутами, и заново разверните его на Scaleway для проверки.
5.2.2.D Отдача файлов из serverless-функций
Чтобы разместить Lettuce Crop на serverless-функции Scaleway, нужно отдавать статические ресурсы сайта. Обычно для этого подошёл бы ServeDir, но у serverless-функций Scaleway во время работы нет доступа к файлам. Вместо этого можно включить файлы в бинарник с помощью memory-serve, как описано в упражнении 5.1.1.G.
Скопируйте папку assets и функцию crop_image из упражнения 5.1.1, настройте роутер так, чтобы он обрезал изображения, отправленные POST-запросами на /crop, и отдавал ресурсы через memory-serve как fallback-сервис. Если теперь заново развернуть код (не забудьте включить папку assets в zip), у нас должна заработать полноценная версия Lettuce Crop на Scaleway!
В реальных приложениях лучше (и дешевле!) размещать статическую часть страницы отдельно, например в бакетах хранилища, а в serverless-функции обрабатывать только интерактивную часть, то есть POST-запросы для обрезки изображений.