zurück zum Artikel

Ferris Talk #8: Wasm loves Rust – WebAssembly und Rust jenseits des Browsers

Rainer Stropek
Ferris Talk – Neuigkeiten zu Rust. Eine Heise-Kolumne von Rainer Stropek und Stefan Baumgartner für Rustaceans

Wasm ist mehr als ein JavaScript-Konkurrent im Browser: Serverseitig hat es das Potenzial, fundamentale Probleme verteilter Systeme zu lösen, und zwar mit Rust.

Wenn heute von WebAssembly (Wasm) die Rede ist, denken die meisten sofort an Webentwicklung. Wasm kann aber viel mehr, als JavaScript für die Client-seitige Programmierung herauszufordern. Auf der Serverseite hat durch Wasm eine kleine Revolution begonnen, die das Potenzial hat, innovative Lösungen für heutzutage noch nicht oder schlecht gelöste, fundamentale Probleme im Bereich verteilter Systeme hervorzubringen.

Ferris Talks – die Kolumne für Rustaceans

Wasm bricht zurzeit das Monopol von JavaScript im Browser auf, und Firmen bringen große bestehende Codeteile in C/C++ mithilfe dieser Technologie in den Browser. Sprachen wie C# mit dem Blazor-Framework oder Rust mit Yew versuchen, erste Schritte zur Eroberung des Web-Clients zu gehen. Viele Entwicklerinnen und Entwickler sind begeistert, ihre Lieblingssprache, ihr bestehendes Wissen und bereits vorhandene Codeteile in den Browser bringen zu können, ohne gezwungen zu sein, tief in die JavaScript-Welt einzutauchen. Dadurch bekommt Wasm als neuer W3C-Standard eine Menge Aufmerksamkeit.

In dieser Ausgabe der Rust-Kolumne werfen wir einen Blick auf aktuelle Entwicklungen im Bereich WebAssembly und sehen uns an, welche Auswirkungen sie abseits des Webbrowsers auf zukünftige Softwarearchitekturen haben. Schließlich gibt es trotz der Plattform- und Programmiersprachenunabhängigkeit von Wasm eine enge Bindung zu Rust. Was Go für die Entwicklung von Containertechnologie war, ist Rust für Wasm.

Beginnen wir mit der Frage, wozu es auf dem Server überhaupt Wasm braucht. Im Zentrum des offenen Standards steht die Idee, ein portables (also nicht betriebssystem- oder architekturspezifisches), speicher- und laufzeiteffizientes Binärformat zu definieren, das als Compile-Ziel für beliebige Programmiersprachen dient. Wasm-Code lässt sich vor der Ausführung performant in systemspezifische Maschinensprache übersetzen und während der Ausführung bietet er die Vorteile der jeweiligen Zielplattform wie unter anderem spezifische Hardwarefunktionen. Dabei läuft Wasm-Code in einer Sandbox und die Zuständigen können kontrollieren, wie er mit der Außenwelt (Dateisystem, Netzwerk) kommuniziert.

Bevor wir tiefer in die Materie einsteigen, sehen wir uns an, wie einfach es ist, ein "Hello World"-Programm mit Rust und Wasm zu erstellen:

Eine Voraussetzung ist das Installieren des wasm32-wasi Compilation Target für Rust mit rustup durch folgenden Befehl: rustup target add wasm32-wasi. Ist dieser Schritt erledigt, lassen sich Rust-Programme bereits in Wasm-Module kompilieren: cargo build --target wasm32-wasi

Zum Ausführen außerhalb des Browsers ist eine Wasm-Runtime nötig. Verschiedene Laufzeitumgebungen stehen zur Auswahl, beispielsweise Wasmer und Wasmtime. In diesem Artikel ist Wasmtime zum Zuge gekommen [16], daher lässt sich ein Wasm-Programm mit folgender Kommandozeile starten:

wasmtime target/wasm32-wasi/debug/hello_wasm.wasm

Wer die Checkliste durchgeht, stößt beim Versuch, eine Datei im Rust-Programm zu lesen, auf einen Fehler. Der folgende Code führt zur Fehlermeldung "Error: failed to find a pre-opened file descriptor through which "./text.txt" could be opened":

vuse std::fs;
use anyhow::Result;

fn main() -> Result<()> {
    let content = fs::read_to_string("./text.txt")?;
    println!("{}", content);

    Ok(())
}

Listing: Wasm "Hello World"

Erst, wenn der Aufruf von wasmtime explizit den Zugriff auf ein Verzeichnis erlaubt (wasmtime --dir=. target/wasm32-wasi/debug/hello_wasm.wasm, man beachte den dir-Parameter), kann das Programm den Inhalt der referenzierten Datei ./text.txt einlesen. Hinter dem kontrollierten Zugriff auf Betriebssystem-APIs steckt das WebAssembly System Interface (WASI) [17].

Wasm-Laufzeitumgebungen wie wasmtime können aber mehr als nur Wasm-Module in der Kommandozeile ausführen. Sie bieten auch Bibliotheken, mit denen sich Wasm-Module in Anwendungen einbetten lassen. Dabei spielt es keine Rolle, ob beide Seiten – Host und eingebettete Komponente – in Rust geschrieben sind oder nicht. Programmiersprachen, die Wasm und verbundene Technologien unterstützen, lassen sich mischen. Einen Einstieg in das Einbetten von Wasm-Modulen in eine Rust-Anwendung findet sich in der Wasm-Dokumentation unter "Using WebAssembly from Rust" [18].

Klingt das nicht ähnlich wie das, was sich mit Container-Technologie erreichen lässt? In der Microservice-Architektur geht es darum, lose gekoppelte Komponenten bereitzustellen, die mit unterschiedlichen Sprachen programmiert sein können, sich unabhängig von einander aktualisieren lassen und aus ihrer Sandbox heraus nur eingeschränkt auf die Außenwelt zugreifen können.

Auch wenn Wasm technisch anders funktioniert, verfolgen Wasm-Komponenten zum Teil ähnliche Ziele: Wie es ein Container Image Format gibt, so bietet Wasm das zuvor erwähnte Binärformat für portablen Code. Für Container braucht es zum Ausführen eine Container-Runtime, bei Wasm gibt es Wasm-Runtimes. Anwendungen in Containern können über die API des Betriebssystems (unter anderem über die Linux-API) auf Dinge wie das Dateisystem und das Netzwerk zugreifen, bei Wasm hat WASI eine im weitesten Sinn vergleichbare Rolle.

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung [19].

Wenn Container und Wasm so viel gemeinsam haben, warum nicht bei Containern bleiben? Die Antwort liegt darin, dass Wasm-Module deutlich leichtgewichtiger sind als Container Images. Während beispielsweise eine Web-API in einem Container ihren eigenen Webserver mitbringen muss, lässt sich ein Wasm-Modul mit einem HTTP-Handler direkt in einen bestehenden Webserver einbetten. Wenn der Host mit der eingebetteten Wasm-Komponente kommunizieren muss, braucht es dafür kein aufwendiges Kommunikationsprotokoll wie gRPC, es ist nicht einmal zwingend eine Prozess-Grenze zu überwinden. Trotzdem will niemand die Isolation potenziell schädlicher Softwarekomponenten aufgeben. Wo Teams früher aus Performancegründen auf die Sicherheitsvorteile von Containern verzichteten und native Plug-ins einsetzten, können sie in Zukunft auf Wasm-Module zurückgreifen und bekommen dadurch das Beste aus beiden Welten.

Mit Wasm lassen sich also kleine, leichtgewichtige Komponenten bauen, die in größere Systeme wie Webserver, Laufzeitumgebungen (wie Node.js oder CPython), modulare Geschäftsanwendungen und elastisch skalierbare Cloud-Umgebungen effizient und sicher einbindbar sind.

Schluss mit der Theorie – es folgen zwei praktische Projekte, die zeigen, welches Potenzial in Wasm auf dem Server steckt. Es sei an dieser Stelle darauf hingewiesen, dass alle Projekte und APIs, zu denen hier Erklärungen und Demonstrationen folgen, experimentell sind, sich daher noch häufig und grundlegend ändern werden und noch ein Stück von der Produktionstauglichkeit entfernt sind. Jetzt ist die Zeit, die Technologie zu evaluieren, die Auswirkungen auf künftige Softwarearchitekturen einzuschätzen, Feedback zu geben und vielleicht sogar etwas zu den entsprechenden Open-Source-Projekten beizutragen.

Zum Einstieg soll es um das WebAssembly Gateway Interface-Projekt (WAGI) gehen [20]. Wer schon länger in der Webentwicklung arbeitet, kennt vielleicht von früher das Common Gateway Interface (CGI). WAGI funktioniert wie CGI, nur basiert es eben auf Wasm. Die Grundprinzipien von WAGI sind schnell erklärt:

WAGI ist eine besonders einfache Möglichkeit, HTTP-Handler in Form von Wasm-Modulen zu entwickeln. Durch die Verwendung von Kommandozeilenargumenten, Umgebungsvariablen, stdin und stdout ist es möglich, Handler in jeder beliebigen Sprache zu schreiben, die Wasm als Compile-Target unterstützt. Entwicklerinnen und Entwickler benötigen dafür keine gesonderten Frameworks. WAGI ist in Rust entwickelt, funktioniert auch ausgezeichnet mit dieser Sprache – ist aber eben nicht auf Rust beschränkt.

Wer mehr über WAGI mit Rust erfahren möchte, kann einen Blick auf einen Vortrag von Stefan Baumgartner und mir beim Rust Linz Meetup werfen ("Introduction to WAGI – Simple HTTP-based WebAssembly Workloads [21]") und sich die zugehörigen Beispiele auf GitHub ansehen [22].

Die Architektur von WAGI ist zwar einfach und elegant, hat jedoch auch Nachteile. Die wesentlichste Einschränkung ist, dass das Starten eines eigenen Prozesses für jeden Web-Request sich relativ aufwendig gestaltet. Da Wasm beim Starten schnell ist, lässt sich der Performancenachteil bei kleineren Projekten verschmerzen. Für den Produktivbetrieb von Anwendungen mit großer Last ist das Konzept aber nur bedingt geeignet. Tatsächlich gilt WAGI daher nur als Proof of Concept beziehungsweise als eine Brückentechnologie, die nötig ist, bis sich eine weitere, mit Wasm verbundene Technologie etabliert hat: das WebAssembly Component Model [23].

Beim Wasm Component Model steht nicht die Entwicklung eigenständiger Anwendungen im Vordergrund. Stattdessen geht es darum, einbettbare Wasm-basierende Komponenten zu ermöglichen. Dabei gehen Wasm-typische Eigenschaften wie die Plattform- und Sprachenunabhängigkeit, die sichere Ausführung von Komponenten in einer Sandbox, Geschwindigkeit und Effizienz beim Aufruf von Funktionen in Wasm-Komponenten nicht verloren.

Rust ist, was die Unterstützung des Wasm Component Models betrifft, ganz vorne mit dabei. Es ist jedoch ausdrückliches Ziel des Wasm Component Models, dass sich verschiedene Programmiersprachen mischen lassen. Was diesen Punkt betrifft, sind die unterschiedlichen Typsysteme von Programmiersprachen jenseits von Basisdatentypen wie i32 eine Herausforderung. Strings oder Listen in Rust sind schließlich etwas anderes als Strings oder Listen in C# oder Java. Das Wasm Interface Types-Projekt (WIT) [24] versucht, das Problem in den Griff zu bekommen.

Wer mag, kann das Wasm Component Model schon heute ausprobieren. Eine kürzlich vorgestellte Plattform, die darauf aufbaut, ist Spin von Fermyon [25]. Spin ist ein Framework zur Entwicklung von Event-getriebenen Microservices mit Wasm-Komponenten, das in Rust geschrieben ist, jedoch schon jetzt neben Rust weitere Programmiersprachen wie Go unterstützt. Spin kann sowohl mit WAGI-Handlern als auch mit Wasm Components umgehen. WAGI bietet die Kompatibilität zu Sprachen, die Wasm Components noch nicht unterstützen. In Rust empfiehlt es sich aber, auf die neueren und zukunftsträchtigeren Wasm Components zu setzen.

Der folgende Codeausschnitt soll veranschaulichen, wie Spin funktioniert. Gezeigt wird eine "Hello World"-Wasm-Komponente, die sich durch eingehende HTTP-Requests aktivieren lässt und die eine einfache HTTP-Response zurückgibt. Neben dem hier demonstrierten HTTP-Trigger enthält Spin bereits einen solchen für eingehende Nachrichten in Redis Channels. Es ist abzusehen, dass bald weitere Trigger folgen werden.

use anyhow::Result;
use spin_sdk::{
    http::{Request, Response},
    http_component,
};

#[http_component]
fn hello_world(req: Request) -> Result<Response> {
    println!("{:?}", req);
    Ok(http::Response::builder()
        .status(200)
        .body(Some("Hello, heise!".into()))?)
}

Listing: Spin "Hello World"

Eine Konfigurationsdatei (spin.toml) definiert das Routing. Außerdem lassen sich in der Datei Zugriffe auf gewisse Verzeichnisse oder Domänen freigeben (über WASI).

spin_version = "1"
name = "spin-hello-world"
version = "1.0.0"
trigger = { type = "http", base = "/" }

[[component]]
id = "hello"
source = "target/wasm32-wasi/release/spinhelloworld.wasm"
[component.trigger]
route = "/hello"

Listing: Spin-Konfiguration

Spin ist ein gutes Beispiel dafür, wie das Zusammenfügen leichtgewichtiger Wasm-Module zu Anwendungen in Zukunft funktionieren kann. Die Wasm-Module lassen sich wie Container unabhängig voneinander aktualisieren und sind in dem, was sie tun können, eingeschränkt. Die Performancenachteile, die es bei der Kommunikation zwischen Containern gibt, entfallen allerdings.

Auch wenn die IT-Branche den Ruf hat, sich unglaublich schnell zu verändern, finden grundlegende Veränderungen auch hier nicht jeden Tag statt. Hypervisor und Container waren in der Vergangenheit Beispiele für Veränderungen, die an den Grundfesten der Arbeitsweise von Entwicklungsteams gerüttelt haben. Die Chancen stehen gut, dass Wasm in der Zukunft ähnliche Auswirkungen hat.

Die Anwendungsmöglichkeiten von portablen, leichtgewichtigen und sicher ausführbaren Wasm-Modulen sind vielfältig. Das in diesem Artikel beschriebene Spin-Framework ist nur ein Beispiel dafür. Es wird spannend, zu sehen, welche neuartigen Softwarearchitekturen Wasm und verbundene Technologien noch ermöglichen. Eines scheint aber schon jetzt sicher: Rust wird dabei eine wichtige Rolle spielen.

Ferris Talk – Neuigkeiten zu Rust. Kolumnist:
Rainer Stropek, timecockpit.com, Rust Meetup Linz, Autor der Ferris Talks, der Kolumne über die Programmiersprache Rust bei Heise Developer

Rainer Stropek, Autor von Ferris Talk #8

ist Softwareentwickler, Trainer, Autor und Vortragender im Microsoft-Umfeld und seit über 25 Jahren als Unternehmer in der IT-Industrie tätig.

Er gründete und führte in dieser Zeit mehrere IT-Dienstleistungsunternehmen. Neben der Tätigkeit als Trainer und Berater in seiner Firma software architects [26] entwickelt er mit seinem Team die preisgekrönte Software time cockpit [27]. Rainer hat Abschlüsse der höheren technischen Schule für Informatik Leonding (AT) sowie der University of Derby (UK). Er ist Autor mehrerer Fachbücher und Artikel in Magazinen im Umfeld von Microsoft .NET und C#, Azure, Go und Rust. Seine technischen Schwerpunkte sind Cloud Computing, die Entwicklung verteilter Systeme sowie Datenbanksysteme.

Regelmäßig tritt er als Speaker und Trainer auf namhaften Konferenzen in Europa und den USA auf. 2010 wurde er von Microsoft zu einem der ersten MVPs (Most Valuable Professionals) für die Azure-Plattform ernannt. Seit 2015 ist er Microsoft Regional Director. 2016 hat er zudem den MVP Award für Visual Studio und Developer Technologies erhalten.

(sih [28])


URL dieses Artikels:
https://www.heise.de/-7064040

Links in diesem Artikel:
[1] https://www.heise.de/hintergrund/Ferris-Talk-1-Iteratoren-in-Rust-6175409.html
[2] https://www.heise.de/hintergrund/Ferris-Talk-2-Abstraktionen-ohne-Mehraufwand-Traits-in-Rust-6185053.html
[3] https://www.heise.de/hintergrund/Ferris-Talk-3-Neue-Rust-Edition-2021-ist-da-mit-Disjoint-Capture-in-Closures-6222248.html
[4] https://www.heise.de/hintergrund/Ferris-Talk-4-Asynchrone-Programmierung-in-Rust-6299096.html
[5] https://www.heise.de/hintergrund/Ferris-Talk-5-Tokio-als-asynchrone-Laufzeitumgebung-ist-ein-Fast-Alleskoenner-6341018.html
[6] https://www.heise.de/hintergrund/Ferris-Talk-6-Ein-neuer-Trick-fuer-die-Formatstrings-in-Rust-6505377.html
[7] https://www.heise.de/hintergrund/Ferris-Talk-7-Vom-Ungetuem-zur-Goldrose-eine-kleine-Rust-Refactoring-Story-6658167.html
[8] https://www.heise.de/hintergrund/Ferris-Talk-8-Wasm-loves-Rust-WebAssembly-und-Rust-jenseits-des-Browsers-7064040.html
[9] https://www.heise.de/hintergrund/Ferris-Talk-9-Vom-Builder-Pattern-und-anderen-Typestate-Abenteuern-7134143.html
[10] https://www.heise.de/hintergrund/Ferris-Talk-10-Constant-Fun-mit-Rust-const-fn-7162074.html
[11] https://www.heise.de/hintergrund/Ferris-Talk-11-Memory-Management-Speichermanagement-in-Rust-mit-Ownership-7195773.html
[12] https://www.heise.de/hintergrund/Ferris-Talk-12-Web-APIs-mit-Rust-erstellen-7321340.html
[13] https://www.heise.de/hintergrund/Ferris-Talk-13-Rust-Web-APIs-und-Mocking-mit-Axum-7457143.html
[14] https://www.heise.de/hintergrund/Ferris-Talk-14-Rust-bekommt-endlich-asynchrone-Methoden-in-Traits-8929334.html
[15] https://www.heise.de/hintergrund/Ferris-Talk-15-Bedingte-Kompilierung-in-Rust-9337115.html
[16] https://wasmtime.dev/
[17] https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-overview.md
[18] https://docs.wasmtime.dev/lang-rust.html
[19] https://www.heise.de/Datenschutzerklaerung-der-Heise-Medien-GmbH-Co-KG-4860.html
[20] https://github.com/deislabs/wagi
[21] https://www.youtube.com/watch?v=9NDwHBjLlhQ
[22] https://github.com/rstropek/rust-samples/tree/master/hello-wagi
[23] https://github.com/WebAssembly/component-model
[24] https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md
[25] https://spin.fermyon.dev/
[26] https://www.linkedin.com/company/software-architects-og/about/
[27] https://www.timecockpit.com/
[28] mailto:sih@ix.de