SecondState | Бессерверные функции Rust и WebAssembly в AWS Lambda
авторRobby Qiu,Second Stateразработан сWasmEdgeАвтор
Бессерверные функции избавляют разработчиков от многих хлопот по управлению серверной инфраструктурой. Serverless также упрощает процесс разработки, поскольку разработчикам нужно сосредоточиться только на логике самого бизнеса. Эта статья представляет собой пошаговое руководство по написанию и развертыванию бессерверных функций WebAssembly на AWS Lambda, бессерверной вычислительной платформе Amazon. В нашей демонстрации функция WebAssembly используетWasmEdgeисполнение во время выполнения. На приведенной ниже диаграмме показана общая архитектура нашего решения.
В первой части этой статьи мы объясним, почему WebAssembly — отличная среда выполнения для бессерверных функций. Мы сравниваем байт-код WebAssembly (обычно скомпилированный из Rust, C++), языки программирования высокого уровня, такие как Python и JavaScript, и машинно-нативные исполняемые файлы (нативный клиент или NaCl). Затем, во второй части, мы продемонстрируем два примера бессерверных функций, написанных на Rust и скомпилированных в WebAssembly для развертывания. В первом примере демонстрируется способность WasmEdge быстро обрабатывать изображения, а во втором примереРасширение TensorFlow для WasmEdgeОбеспечивает поддерживаемый вывод ИИ.
Почему стоит выбрать WebAssembly?
Короткий ответ: WebAssembly быстрый, безопасный и портативный. Так почему именно? Ниже подробный ответ.
WebAssembly против Python и JavaScript
Недавний опрос DataDogВыяснилось, что большинство бессерверных функций AWS Lambda написано на JavaScript и Python. Оба являются двумя самыми популярными языками программирования в мире, так что в этом нет ничего удивительного.
Однако, как известно, языки высокого уровня работают медленно. В самом деле, согласно опубликованным вСтатья в науке, Python работает до 60 000 раз медленнее, чем та же программа, написанная на C или C++.
Таким образом, хотя JavaScript и Python отлично подходят для простых функций, они не подходят для ресурсоемких задач, таких как обработка изображений, видео, аудио и естественного языка, которые все чаще встречаются в современных приложениях.
С другой стороны, WebAssembly работает наравне с собственными двоичными файлами, скомпилированными на C/C++ (NaCl), сохраняя при этом переносимость, безопасность и управляемость, связанные со средами выполнения языков высокого уровня.WasmEdgeв настоящее время находится на рынкесамый быстрыйОдна из сред выполнения WebAssembly.
WebAssembly против родного клиента
Но каковы преимущества WebAssembly по сравнению с NaCl, когда оба работают внутри контейнера Docker или микроVM?
Наше видение будущего заключается в нативной инфраструктуре,WebAssembly как альтернативная облегченная среда выполнения, работает параллельно с Docker и microVM. WebAssembly работает лучше и потребляет меньше ресурсов, чем Docker-подобные контейнеры или микроVM. Но на данный момент AWS Lambda и многие другие платформы поддерживают запуск WebAssembly только внутри микроВМ. Тем не менее, запуск функций WebAssembly на микроВМ имеет много преимуществ по сравнению с запуском контейнерных программ NaCl.
Во-первых, WebAssembly обеспечивает точную изоляцию времени выполнения для отдельных функций. Микросервис может иметь несколько функций и сервисов поддержки, работающих в микроВМ. WebAssembly позволяет использовать микросервисыБезопаснее и стабильнее.
Во-вторых, байт-код WebAssemblyпортативный. Даже внутри контейнера NaCl по-прежнему зависит от базового ЦП, операционной системы и динамических библиотек, установленных в ОС. И приложения байт-кода WebAssembly являются кроссплатформенными. Разработчики могут написать один раз и развернуть в любом облаке, в любом контейнере и на любой аппаратной платформе.
В-третьих, приложения WebAssembly** просты в развертывании и управлении. ** Они гораздо менее зависимы от платформы и сложнее, чем динамические библиотеки и исполняемые файлы NaCl.
Наконец, WebAssembly является многоязычным. Программы C/C++, Rust, Swift, Kotlin можно легко скомпилировать в WebAssembly. WebAssembly даже поддерживает JavaScript.WasmEdge Tensorflow APIПредоставляет способ запуска моделей Tensorflow на языке программирования Rust.самый обычный способ.
Мы видим, что WebAssembly + WasmEdge — лучший выбор. Чтобы действительно убедиться в этом выводе, давайте погрузимся в пример и попробуем сами!
Предварительная подготовка
Поскольку наша демонстрационная функция WebAssembly написана на Rust, вам необходимо установитьКомпилятор ржавчины.убедитесь, что вы добавилиwasm32-wasi
Цель компилятора (ниже), которая генерирует байт-код WebAssembly.
$ rustup target add wasm32-wasi
Передняя часть демонстрационного приложенияНаписано Next.jsи развернуты на AWS Lambda. Мы предполагаем, что у вас уже есть основы работы с Next.js и Lambda.
Случай 1: обработка изображений
Наше первое демонстрационное приложение позволяет пользователю загрузить изображение, которое затем пользователь вызывает бессерверную функцию, чтобы превратить его в черно-белое. Вы можете просмотреть, что было развернуто, через страницы GitHub.Живая демонстрация.
демонстрационная ссылка:второе состояние.GitHub.IO/AWS-лямбда-…
Fork Репозиторий GitHub для демо-приложения, вы можете приступить к развертыванию своей функции. Конкретный процесс развертывания приложения на AWS Lambda см. в репозитории вREADMEруководство.
Шаблон репозитория GitHub:GitHub.com/second-stat…
создать функцию
Репозиторий шаблонов представляет собой стандартное приложение Next.js. Бэкэнд-бессерверные функции находятся вapi/functions/image_grayscale
папка.src/main.rs
Файл содержит исходный код программы Rust. Программа Rust начинается сSTDIN
Прочитайте данные, затем выведите черно-белое изображение наSTDOUT。
use hex;
use std::io::{self, Read};
use image::{ImageOutputFormat, ImageFormat};
fn main() {
let mut buf = Vec::new();
io::stdin().read_to_end(&mut buf).unwrap();
let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap();
let img = image::load_from_memory(&buf).unwrap();
let filtered = img.grayscale();
let mut buf = vec![];
match image_format_detected {
ImageFormat::Gif => {
filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();
},
_ => {
filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();
},
};
io::stdout().write_all(&buf).unwrap();
io::stdout().flush().unwrap();
}
Доступно в ржавчинеcargo
Инструменты для создания программ на Rust в виде байт-кода WebAssembly или собственного кода.
$ cd api/functions/image-grayscale/
$ cargo build --release --target wasm32-wasi
Скопируйте артефакт сборки вapi 文件夹。
$ cp target/wasm32-wasi/release/grayscale.wasm ../../
Когда мы создаем образ докера, он выполняет
api/pre.sh
.pre.sh
Установите среду выполнения WasmEdge, затем скомпилируйте каждую программу байт-кода WebAssembly в собственныйso
библиотека для более быстрого выполнения.
Создайте сервисный скрипт, загрузите функцию
api/hello.js
Сценарий загружает среду выполнения WasmEdge, запускает скомпилированную программу WebAssembly в WasmEdge и передает загруженные данные изображения черезSTDIN
передача. Уведомлениеapi/hello.js
запустить скомпилированныйapi/pre.sh
произведеноgrayscale.so
файл для лучшей производительности.
const { spawn } = require('child_process');
const path = require('path');
function _runWasm(reqBody) {
return new Promise(resolve => {
const wasmedge = spawn(path.join(__dirname, 'wasmedge'), [path.join(__dirname, 'grayscale.so')]);
let d = [];
wasmedge.stdout.on('data', (data) => {
d.push(data);
});
wasmedge.on('close', (code) => {
let buf = Buffer.concat(d);
resolve(buf);
});
wasmedge.stdin.write(reqBody);
wasmedge.stdin.end('');
});
}
hello.js
изexports.handler
Частично экспортирует обработчик асинхронной функции для обработки другого события при каждом вызове бессерверной функции. В этом примере мы просто обрабатываем изображение, вызывая вышеуказанную функцию и возвращая результат, но при необходимости вы можете определить более сложное поведение обработки событий. Нам также нужно вернуть некоторыеAccess-Control-Allow
заголовок, чтобы избежать совместного использования ресурсов между источниками при вызове без сервера из браузераCross-Origin Resource Sharing (CORS)ошибка. Если вы получаете ошибки CORS при воспроизведении нашего примера, вы можетездесьСм. дополнительные сведения об ошибках CORS.
exports.handler = async function(event, context) {
var typedArray = new Uint8Array(event.body.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
}));
let buf = await _runWasm(typedArray);
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"
},
body: buf.toString('hex')
};
}
Создайте образ Docker для развертывания Lambda
Теперь у нас есть функции байт-кода WebAssembly и скрипты для загрузки и подключения к веб-запросам. Чтобы развернуть их как функциональный сервис на AWS Lambda, все это все еще необходимо упаковать в образ Docker.
Мы не будем вдаваться в подробности того, как создать образ Docker и развернуть его на AWS Lambda, вы можете обратиться краздел развертывания в README. Однако мы выделимDockerfile
часть, чтобы избежать некоторых ловушек.
FROM public.ecr.aws/lambda/nodejs:14
# Change directory to /var/task
WORKDIR /var/task
RUN yum update -y && yum install -y curl tar gzip
# Bundle and pre-compile the wasm files
COPY *.wasm ./
COPY pre.sh ./
RUN chmod +x pre.sh
RUN ./pre.sh
# Bundle the JS files
COPY *.js ./
CMD [ "hello.handler" ]
Во-первых, мы начинаем сБазовый образ Node.js для AWS LambdaСоздайте образ. Преимущество использования базового образа AWS Lambda заключается в том, что он содержитКлиент интерфейса времени выполнения Lambda (RIC), который необходим при развертывании образа Docker в AWS Lambda. Использование Amazon Linuxyum
в качестве менеджера пакетов.
Эти базовые образы содержат операционную систему Amazon Linux Base, среду выполнения для данного языка, зависимости и клиент интерфейса среды выполнения Lambda (RIC), который реализует Lambda.Runtime API. лямбдаRuntime APIКлиент позволяет вашей среде выполнения получать запросы и отправлять запросы в службу Lambda.
Во-вторых, нам нужно поместить нашу функцию и все ее зависимости в/var/task
в каталоге. AWS Lambda не выполняет файлы в других папках.
В-третьих, нам нужно определить команду по умолчанию при запуске контейнера.CMD [ "hello.handler" ]
означает, что всякий раз, когда вызывается бессерверная функция, мы будем вызыватьhello.js
серединаhandler
функция. Напомним, что на предыдущих шагах мы прошлиhello.js
серединаexports.handler = ...
Функция обработчика определена и экспортирована.
Необязательно: протестируйте образ Docker локально.
Вы можете делать то, что дает AWSгидПротестируйте локально образ Docker, созданный на основе базового образа AWS Lambda. Требуется для локального тестированияAWS Lambda Runtime Interface Emulator (RIE), который уже установлен во всех базовых образах AWS Lambda. Чтобы протестировать свой образ, сначала запустите контейнер Docker, выполнив:
docker run -p 9000:8080 myfunction:latest
Эта команда устанавливает конечную точку функции на вашем локальном компьютере.http://localhost:9000/2015-03-31/functions/function/invocations
.
Затем из отдельного окна терминала запустите:
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
Вы должны получить ожидаемый результат в терминале.
Если вы не хотите использовать базовый образ из AWS Lambda, вы также можете использовать свой собственный базовый образ и установить RIC и/или RIE при создании образа Docker. Просто следуйте инструкциям AWS.гид, создайте образ из раздела Переопределить базовый образ.
Вот и все! После создания образа Docker вы можете выполнить шаги, описанные в репозитории.READMEШаги, описанные для извлечения его в AWS Lambda. Теперь ваша бессерверная функция готова! Давайте посмотрим на вторую сложную функцию
Случай 2: вывод ИИ
второе демоПриложение позволяет пользователю загрузить изображение, а затем запустить бессерверную функцию для определения основного элемента на изображении.
Там же, где и предыдущий примерGitHub repo, но в ветке тензорного потока. Бэкэнд-бессерверные функции для классификации изображений расположены по адресуtensorflow
разветвленныйapi/functions/image-classification
папка.src/main.rs
Файл содержит исходный код программы Rust. Rust программы отSTDIN
Прочитайте данные изображения, затем выведите вывод скрипта вSTDOUT
. Он использует API WasmEdge Tensorflow для выполнения вывода ИИ.
Шаблон вывода ИИ:GitHub.com/second-stat…
pub fn main() {
// Step 1: Load the TFLite model
let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");
// Step 2: Read image from STDIN
let mut buf = Vec::new();
io::stdin().read_to_end(&mut buf).unwrap();
// Step 3: Resize the input image for the tensorflow model
let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);
// Step 4: AI inference
let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
session.add_input("input", &flat_img, &[1, 224, 224, 3])
.run();
let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");
// Step 5: Find the food label that responds to the highest probability in res_vec
// ... ...
let mut label_lines = labels.lines();
for _i in 0..max_index {
label_lines.next();
}
// Step 6: Generate the output text
let class_name = label_lines.next().unwrap().to_string();
if max_value > 50 {
println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
} else {
println!("It does not appears to be any food item in the picture.");
}
}
ты можешь использоватьcargo
Инструменты для создания программ на Rust в виде байт-кода WebAssembly или собственного кода.
$ cd api/functions/image-classification/
$ cargo build --release --target wasm32-wasi
Скопируйте артефакты сборки вapi
папка.
$ cp target/wasm32-wasi/release/classify.wasm ../../
такой же,api/pre.sh
Сценарий устанавливает среду выполнения WasmEdge и ее зависимости Tensorflow в этом приложении. Он также будет развернутclassify.wasm
Программа байт-кода компилируется вclassify.so
Родная общая библиотека.
api/hello.js
Сценарий загружает среду выполнения WasmEdge, запускает скомпилированную программу WebAssembly в WasmEdge и передаетSTDIN
Передайте загруженные данные изображения. Уведомлениеapi/hello.js
бегатьapi/pre.sh
сгенерированный скомпилированныйclassify.so
файл для лучшей производительности. Функция Handler аналогична нашему предыдущему примеру и не будет здесь подробно описываться.
const { spawn } = require('child_process');
const path = require('path');
function _runWasm(reqBody) {
return new Promise(resolve => {
const wasmedge = spawn(
path.join(__dirname, 'wasmedge-tensorflow-lite'),
[path.join(__dirname, 'classify.so')],
{env: {'LD_LIBRARY_PATH': __dirname}}
);
let d = [];
wasmedge.stdout.on('data', (data) => {
d.push(data);
});
wasmedge.on('close', (code) => {
resolve(d.join(''));
});
wasmedge.stdin.write(reqBody);
wasmedge.stdin.end('');
});
}
exports.handler = ... // _runWasm(reqBody) is called in the handler
Вы можете создать образ Docker и развернуть функцию, как описано в предыдущем примере. Теперь вы создали веб-приложение для категоризации тем!
Глядя в будущее
Запуск WasmEdge из контейнера Docker, развернутого на AWS Lambda, — это простой способ добавить высокопроизводительные функции в ваше веб-приложение. В дальнейшем лучше использоватьWasmEdgeкак и сам контейнер. Это устраняет необходимость в Docker и Node.js для установки WasmEdge. Таким образом, мы можем более эффективно запускать бессерверные функции. ВасмЭджУже совместим с инструментами Docker. Если вы хотите присоединиться к WasmEdge и CNCF в этой захватывающей работе,пожалуйста скажи нам!