image

Язык программирования Rust за последние несколько лет значительно распространился. Он несколько лет подряд считается самым любимым языком программирования среди разработчиков и включён в состав ядра Linux. Rust позволяет разработчикам писать корректные программы, обеспечивающие безопасность при работе с памятью, а также такие же компактные и быстрые, как на языке C. Он идеально подходит для создания инфраструктурного программного обеспечения, включая серверные приложения, от которых требуется высокая надёжность и производительность.

Однако при разработке серверных приложений Rust также представляет определенные трудности. Написанные на Rust программы компилируются в нативный машинный код, который не является портируемым и небезопасен в многопользовательских облачных средах. Нам также не хватает инструментов для управления и оркестрации нативных приложений в облаке.

Поэтому серверные приложения Rust обычно выполняются на виртуальных машинах или в контейнерах Linux, что приводит к значительным затратам памяти и ресурсов ЦП. Это снижает преимущества Rust в эффективности и затрудняет развёртывание сервисов в средах с ограниченными ресурсами, таких как дата-центры граничных вычислений и граничные облака. Решить эти проблемы помогает WebAssembly (Wasm).

Зародившись как безопасная среда выполнения в веб-браузерах, Wasm позволяет надёжно изолировать в собственной песочнице написанные на нём программы. Когда возникло новое поколение сред выполнения Wasm, таких как WasmEdge Runtime от Cloud Native Computing Foundation, появилась возможность запускать приложения Wasm на сервере. Вы можете компилировать Rust-программы в Wasm-байткод, а затем развернуть Wasm-приложение в облаке.

Согласно исследованию, опубликованному в журнале IEEE Software, приложения Wasm могут работать в 100 раз быстрее (особенно при запуске) и занимать около 1 процента по объёму в сравнении с исходно скомпилированными приложениями Rust в контейнерах Linux. Поэтому они лучше всего подходят для сред с ограниченными ресурсами, таких как граничные облака.

Песочницы среды выполнения Wasm характеризуются гораздо меньшей площадью атаки, чем контейнеры Linux, а также обеспечивают более качественную изоляцию. Кроме того, среда выполнения Wasm портируется между операционными системами и аппаратными платформами. После того, как программа скомпилирована на языках Rust в Wasm, она может функционировать везде – от компьютера разработчика до продакшена, от облака до периферии.

image

Анатомия микросервиса Rust в песочнице WebAssembly.

В этой статье мы рассмотрим инструменты, библиотеки, API, фреймворки и методы, необходимые для создания микросервисов на языке Rust. Мы также продемонстрируем, как развертывать, запускать и масштабировать эти микросервисы в WasmEdge WebAssembly Runtime.

Предпосылки


Чтобы понять эту статью, вам потребуются:

  • Базовые знания о микросервисах как о паттерне проектирования;
  • Базовые знания об операционной системе Linux;
  • Представление о языке программирования Rust;
  • Элементарные знания о SQL-базах данных.

Создание веб-сервиса


Микросервис – это, прежде всего, веб-сервер. WasmEdge Runtime поддерживает асинхронные и неблокирующие сетевые сокеты. Вы можете писать сетевые приложения на языке Rust, компилировать их в Wasm и запускать в WasmEdge Runtime. В экосистеме Rust, WasmEdge поддерживает следующие возможности:

  • Крейты tokio и mio для асинхронных сетевых приложений;
  • Крейт hyper для серверных и клиентских приложений, работающих по протоколу HTTP.

Следующий пример из демонстрационного приложения microservice-rust-mysql показывает, как создать веб-сервер в hyper для WasmEdge. Основной цикл аудита веб-сервера выглядит следующим образом:

	let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
	let make_svc = make_service_fn(|_| {
    	let pool = pool.clone();
    	async move {
        	Ok::<_, Infallible>(service_fn(move |req| {
            	let pool = pool.clone();
            	handle_request(req, pool)
        	}))
    	}
	});
	let server = Server::bind(&addr).serve(make_svc);
	if let Err(e) = server.await {
    	eprintln!("server error: {}", e);
	}
	Ok(())

Как только поступает запрос, обработчик события, handle_request(), вызывается асинхронно, чтобы он мог обрабатывать несколько одновременных запросов. Он генерирует ответ на основе того, каким методом был сделан запрос, и по какому пути прошёл:

async fn handle_request(req: Request<Body>, pool: Pool) -> Result<Response<Body>, anyhow::Error> {
	match (req.method(), req.uri().path()) {
    	(&Method::GET, "/") => Ok(Response::new(Body::from(
        	"... ...",
        ))),
 
        // Просто отправьте тело обратно клиенту в виде отклика.
        (&Method::POST, "/echo") => Ok(Response::new(req.into_body())),
 
    	(&Method::GET, "/init") => {
        	let mut conn = pool.get_conn().await.unwrap();
        	"DROP TABLE IF EXISTS orders;".ignore(&mut conn).await?;
        	"CREATE TABLE orders (order_id INT, product_id INT, quantity INT, amount FLOAT, shipping FLOAT, tax FLOAT, shipping_address VARCHAR(20));".ignore(&mut conn).await?;
        	drop(conn);
        	Ok(Response::new(Body::from("{\"status\":true}")))
    	}
 
    	(&Method::POST, "/create_order") => {
        	// ... ...
    	}
 
        // Возврат состояния 404 Not Found для других путей.
        _ => {
        	let mut not_found = Response::default();
        	*not_found.status_mut() = StatusCode::NOT_FOUND;
        	Ok(not_found)
        }
	}
}
 

Теперь у нас есть HTTP-сервер для веб-сервиса.

Создание клиента веб-сервиса


Типичный веб-сервис также должен потреблять другие веб-сервисы. С помощью критериев tokio и/или mio приложения WasmEdge могут легко включать в себя HTTP-клиенты для веб-сервисов. В WasmEdge поддерживаются следующие крейты Rust:

  • Крейт reqwest для простых HTTP-клиентов;
  • Крейт http_req для клиентов HTTP и HTTPS.

В следующем примере показано, как выполнить запрос HTTP POST к API веб-сервиса из микросервиса:

let client = reqwest::Client::new();
 
	let res = client
    	.post("http://eu.httpbin.org/post")
    	.body("msg=WasmEdge")
    	.send()
    	.await?;
	let body = res.text().await?;
 
	println!("POST: {}", body);

Создание клиента базы данных


Большинство микросервисов поддерживаются базами данных. В WasmEdge поддерживаются следующие Rust-крейты для драйверов MySQL:

  • Крейт myql — синхронный клиент MySQL;
  • Крейт mysql_async — асинхронный клиент MySQL.

В примере ниже показано, как вставить набор записей в таблицу базы данных:

let orders = vec![
    	Order::new(1, 12, 2, 56.0, 15.0, 2.0, String::from("Mataderos 2312")),
    	Order::new(2, 15, 3, 256.0, 30.0, 16.0, String::from("1234 NW Bobcat")),
    	Order::new(3, 11, 5, 536.0, 50.0, 24.0, String::from("20 Havelock")),
    	Order::new(4, 8, 8, 126.0, 20.0, 12.0, String::from("224 Pandan Loop")),
    	Order::new(5, 24, 1, 46.0, 10.0, 2.0, String::from("No.10 Jalan Besar")),
	];
 
	r"INSERT INTO orders (order_id, production_id, quantity, amount, shipping, tax, shipping_address)
  	VALUES (:order_id, :production_id, :quantity, :amount, :shipping, :tax, :shipping_address)"
    	.with(orders.iter().map(|order| {
        	params! {
            	"order_id" => order.order_id,
            	"production_id" => order.production_id,
            	"quantity" => order.quantity,
            	"amount" => order.amount,
            	"shipping" => order.shipping,
            	"tax" => order.tax,
            	"shipping_address" => &order.shipping_address,
        	}
        }))
        .batch(&mut conn)
        .await?;

В приведенном ниже примере показано, как запросить таблицу базы данных и вернуть набор записей:

	let loaded_orders = "SELECT * FROM orders"
    	.with(())
    	.map(
        	&mut conn,
        	|(order_id, production_id, quantity, amount, shipping, tax, shipping_address)| {
            	Order::new(
                	order_id,
                	production_id,
                	quantity,
                	amount,
                	shipping,
                	tax,
                	shipping_address,
            	)
        	},
    	)
    	.await?;
	dbg!(loaded_orders.len());
	dbg!(loaded_orders);

Создание, развёртывание и запуск микросервиса


Проект microservice-rust-mysql – это полностью расписанный пример микросервиса, управляемого базой данных. Давайте воспользуемся им в качестве упражнения для сборки, развертывания и запуска этого сервиса.

Docker CLI и Docker Desktop обеспечивают бесшовную поддержку при разработке приложений WasmEdge. Из корневого каталога репозитория проекта достаточно выполнить одну команду, чтобы собрать и поднять все компоненты микросервиса (т. е. приложение WasmEdge и сервер базы данных MariaDB):

docker compose up

Затем вы можете протестировать операции CRUD, совершаемые над базой данных через микросервис. Для этого воспользуйтесь curl.

В качестве альтернативных вариантов можно:


Следующие команды задают вышеуказанные необходимые условия в системе Linux:

// Установка Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
 
// Установка WasmEdge
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -e all
 
// Установка MySQL. Она доступен в виде пакета в большинстве дистрибутивов Linux
sudo apt-get update
sudo apt-get -y install mysql-server libmysqlclient-dev
sudo service mysql start
 
Затем создайте микросервисное приложение в байткоде Wasm:
cargo build --target wasm32-wasi --release
Затем запустите микросервис в WasmEdge Runtime:
wasmedge --env "DATABASE_URL=mysql://user:passwd@127.0.0.1:3306/mysql" order_demo_service.wasm
 
Затем вы можете обращаться к базе данных через веб-API микросервиса  
// Запуск таблицы базы данных e
curl http://localhost:8080/init
 
// Вставка набора записей
curl http://localhost:8080/create_orders -X POST -d @orders.json
 
// Запрос записей из базы данных
curl http://localhost:8080/orders
 
// Обновление записей
curl http://localhost:8080/update_order -X POST -d @update_order.json
 
// Удаление записи по ее идентификатору
curl http://localhost:8080/delete_order?id=2
 

Запуск в продакшен


Выше мы видели в действии один полный микросервис, управляемый базой данных. Однако в реальном мире у компании могут быть сотни микросервисов. Они должны управляться и организовываться с помощью облачных нативных фреймворков, таких как Kubernetes.

Приложения WasmEdge полностью совместимы с OCI. Они могут управляться и храниться в Docker Hub или других репозиториях Open Container Initiative. Благодаря crun integration, WasmEdge может работать бок о бок с контейнерными приложениями Linux в одном кластере Kubernetes. Этот репозиторий демонстрирует, как запускать приложения WasmEdge в популярных контейнерных инструментальных цепочках, включая CRI-O, containerd, Kubernetes, Kind, OpenYurt, KubeEdge и другие.

image
Время выполнения WebAssembly в стеке Kubernetes.

Кроме того, микросервисы часто развертываются вместе с сервисными фреймворками. Например, Dapr — это популярная среда выполнения для микросервисов. Она предоставляет сервис-прицеп (sidecar) для каждого микросервиса. Микросервис обращается к побочной службе через API Dapr для обнаружения и вызова других служб в сети, управления данными состояния и доступа к очередям сообщений.

Dapr SDK для WASI (WebAssembly System Interface) позволяет микросервисам на базе WasmEdge получать доступ к тем прицепам Dapr, что к ним подключены. Также можете посмотреть полное демонстрационное приложение со статическим веб-фронтендом Jamstack, тремя микросервисами и службой базы данных.

Заключение


В этой статье мы обсудили, почему WebAssembly отлично подходит в качестве «песочницы» для выполнения серверных приложений на основе Rust. Мы рассмотрели конкретные примеры кода, демонстрирующие создание веб-сервиса HTTP, потребление других веб-сервисов, доступ к реляционным базам данных из Rust, а затем запустили скомпилированные приложения в WasmEdge. Мы также изучили вопросы развертывания, затронули интеграцию с Kubernetes и Dapr.

С помощью показанных крейтов и шаблонных приложений вы сможете создавать свои собственные легковесные микросервисы в Rust!

Помимо микросервисов, WasmEdge Runtime находит широкое применение в качестве песочницы для приложений во многих разнохарактерных случаях:

  • flows.network — это бессерверная платформа для автоматизации SaaS. Вы можете создавать боты, настройки и соединения для SaaS-продуктов, используя Rust и WebAssembly.
  • В WebAssembly можно создавать функции, определяемые пользователем (UDF), и функции извлечения-преобразования-загрузки (ETL) для баз данных, а также встраивать или размещать эти функции вместе с базой данных «бессерверным» способом
  • С помощью WebAssembly можно создавать портируемые и высокопроизводительные приложения для периферийных устройств, работающих под управлением Android, Open Harmony и даже seL4 RTOS.
  • WebAssembly широко используется в качестве среды исполнения для блокчейна и для выполнения смарт-контрактов. WasmEdge, например, запускает узлы и смарт-контракты для сетей Polkadot, FileCoin и XRP (Ripple). Виртуальная машина блокчейна Ethereum нового поколения, известная как ewasm, также основана на WebAssembly.

Чтобы подробнее разобраться в WasmEdge, обратитесь к официальной документации.



Возможно, захочется почитать и это:


Комментарии (9)


  1. akurilov
    13.07.2023 11:14

    Оверхед k8s и контейнеров вообще - неочевиден. Можно ли привести какие то примерные числа для сравнения эффективности условного нативного микросервиса на WASM и в K8s?


    1. akurilov
      13.07.2023 11:14
      +1

      А, кажется я понял, на WASM контейнер может стартовать быстрее, чем в Docker. Но тогда речь не о микросервисах, а о serverless функциях.


      1. slonopotamus
        13.07.2023 11:14

        В Kubernetes нет Docker.


    1. SabMakc
      13.07.2023 11:14

      Compared with Linux containers, WasmEdge could be 100x faster at start-up and 20% faster at runtime.

      A WasmEdge app could take 1/100 of the size of a similar Linux container app.

      Заявления с сайта wasmedge. Впрочем, объективность подобных заявлений под большим вопросом. Ни примеров, ни методики измерения не указано.

      Максимум - готов поверить, что такие приложения могут занимать значительно меньше места. Впрочем, образы на базе alpine тоже занимают значительно меньше места относительно прочих образов, так что и тут остается открытым вопрос - с чем и как сравнивали.


      1. akurilov
        13.07.2023 11:14

        Да, оборот "could be" здесь намекает на то, что wasm быстрее lxc в лучшем случае иногда)


        1. SabMakc
          13.07.2023 11:14

          Согласен.
          Но я бы не прочь увидеть и специально подобранный пример, где эти заявления более-менее выполняются.
          Правда именно грамотный пример хотел бы увидеть - скажем, сравнение с alpine-образом и с примерно одинаковыми параметрами компиляции приложения.


          1. slonopotamus
            13.07.2023 11:14

            Зачем alpine? Просто статически слинкованный бинарник с самим приложением.


      1. slonopotamus
        13.07.2023 11:14

        Заявления с сайта wasmedge.

        Как в том анекдоте: ну так и вы говорите.


  1. KReal
    13.07.2023 11:14

    Я правильно понимаю, что этот подход теоретически сработает с любым языком, компилируемым в wasm?