WasmEdge : Les runtimes WebAssembly sont en routes pour l'edge computing

WasmEdge : Les runtimes WebAssembly sont en routes pour l'edge computing

Vous appréciez notre travail......nous recrutons !

Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.

Avec de nombreux de nombreux défis en matière de sécurité résolus de par sa conception, beaucoup de projets bénéficient de l’utilisation du WebAssembly.

Le runtime WasmEdge est une machine virtuelle performante optimisée pour l’edge computing (traitement en ressources contraintes). Ses principaux cas d’utilisations sont les suivants :

  • Applications Jamstack, à travers l’utilisation d’un front-end static, faisant appel à un back-end serverless de type FaaS
  • Automobiles
  • Internet des Objets (IoT) et traitements continus (streaming)

C’est une machine virtuelle intégrable qui peut être utilisée en tant que processus, à l’intérieur d’un processus, ou même orchestrée en tant que conteneur OCI natif (en fournissant une interface respectant le standard OCI)

Qu’est-ce que le WebAssembly ?

Le WebAssembly (Wasm) est un bytecode conçu pour être exécuté parallèlement au JavaScript, il est le successeur de plusieurs projets dont le but principal est d’accélérer l’exécution de code dans le navigateur internet.

Ses prédécesseurs les plus notables sont asm.js et Google Native Client (GNI).

Asm.js et GNI ont des philosophies très différentes. Le premier utilise un sous-ensemble du JavaScript, le rendant compatible avec n’importe quel moteur JavaScript. Alors que le deuxième est une sandbox complète conçue pour être intégrée par les différents navigateurs internet.

EmScripten est un outil permettant de convertir n’importe bytecode LLVM (C, C++, Rust) en asm.js.

Caractéristiques du WebAssembly :

  • Portable
  • Sécurisé
  • Performant
  • Binaires de petite taille

La spécification WebAssembly définit deux langages : une forme binaire (compilée) et une forme textuelle, compréhensible par l’Homme.

Voici l’exemple d’un code écrit en Rust, ainsi que de sa version traduite en WAT (WebAssembly Text).

Rust :

pub fn ajoute_deux(a: i32, b: i32) -> i32 {
  a + b
}

WAT (WebAssembly Text) :

(module
  (type $t0 (func (param i32 i32) (result i32)))
  (func $ajouteDeux (export "ajoute_deux") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
    (i32.add
      (local.get $p0)
      (local.get $p1))))

Extensions WebAssembly

WasmEdge propose de multiples extensions dont certaines sont des implémentations de propositions du standard WebAssembly comme par exemple WASI.

WASI est une extension du standard WebAssembly permettant de s’interfacer avec le système par l’utilisation de sockets (communication TCP/UDP) ou d’accéder au système de fichier via une pile I/O compatible POSIX.

Voici une liste des extensions d’ores et déjà disponibles :

  • WASI : WebAssembly System Interface
  • Reference Types
  • Bulk Memory Operations
  • SIMD : Single Instruction Multiple Data

Pourquoi WasmEdge ?

WasmEdge est l’un des différents runtimes disponibles permettant l’exécution du WebAssembly sur un serveur, avec un focus sur l’edge computing.

Rust est un citoyen de première classe de l’écosystème WebAssembly, et en tant que tel, il est une cible principale pour WasmEdge.

Voici quelques caractéristiques mises en avant sur leur site web :

  • Performant
  • Compilation Ahead Of Time (AOT)
  • Petite empreinte mémoire
  • Portable
  • Intégrable
  • Orchestrable
  • Blockchain
  • Extensible

Les Extensions WasmEdge

WasmEdge propose un ensemble d’extensions permettant des cas d’usages plus avancés, telles que :

documentation d’extensions WasmEdge

  • TensorFlow, ONNX
  • Image Processing
  • Key-Value Storage
  • Network Sockets
  • Command Interface
  • Ethereum
  • Substrate

Tutoriel pratique

Cet article continue en tant que tutoriel pratique sur comment écrire votre première application WasmEdge, ainsi que son exécution dans un environnement en conteneur.

Veuillez trouvez ce repository github contenant les playbooks ansible ainsi que le source code de l’application. Toutes les étapes de ce tutoriel peuvent être exécutées par ces playbooks Ansible.

Première étape : Configuration de l’environment

git clone https://github.com/adaltas/wasmedge-hands-on-tutorial
cd wasmedge-hands-on-tutorial
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
ansible-galaxy collection install -r requirements.yml
vagrant up
./hosts.sh # génère un fichier hosts contenant la configuration de la machine virtuelle
Inventory:
master ansible_host=192.168.121.117 ansible_port=22 ansible_user=vagrant ansible_private_key_file=/path/to/key

TL;DR

L’entièreté des actions peut être exécutée en lançant la commande ansible-playbook ansible/00_all.yml.

Cela exécuration chaque action, telle que compiler le projet en wasm, l’exécuter dans un conteneur avec et sans kubernetes.

Seconde étape : Installer WasmEdge

Pour installer WasmEdge automatiquement, exécutez le playbook ansible/01_install_wasmedge.yml :

ansible-playbook ansible/01_install_wasmedge.yml
PLAY [master] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [master]

TASK [Install packages] ***********************************************************************************************************************************************************************************
changed: [master]

TASK [Get wasmedge install script] ************************************************************************************************************************************************************************
changed: [master]

TASK [Install WasmEdge globally] **************************************************************************************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************************************************************************************
master                     : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Troisième étape : Installer la toolchain Rust

Le Rust est un citoyen de première classe dans l’ecosysème WASM, durant ce tutoriel, nous utiliserons un projet en Rust.

Pour installer la toolchain Rust, exécutez le playbook ansible-playbook ansible/02_install_rust.yml.

ansible-playbook ansible/02_install_rust.yml
PLAY [master] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [master]

TASK [Check if cargo is installed] ************************************************************************************************************************************************************************
fatal: [master]: FAILED! => changed=true
  cmd: command -v /home/vagrant/.cargo/bin/cargo
  delta: '0:00:00.001685'
  end: '2022-06-12 18:49:36.987147'
  msg: non-zero return code
  rc: 127
  start: '2022-06-12 18:49:36.985462'
  stderr: ''
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>
...ignoring

TASK [Download Installer] *********************************************************************************************************************************************************************************
changed: [master]

TASK [Install rust/cargo] *********************************************************************************************************************************************************************************
changed: [master]

TASK [Install wasm32-wasi toolchain] **********************************************************************************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************************************************************************************
master                     : ok=5    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1

Quatrième étape : Téléverser et compiler le projet

Dans cette étape, nous allons téléverser puis compiler le projet en utilisant le playbook suivant : ansible-playbook ansible/03_build_echo_server.yml

ansible-playbook ansible/03_build_echo_server.yml
PLAY [master] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [master]

TASK [Upload echo server project] *************************************************************************************************************************************************************************
changed: [master]

TASK [Build project] **************************************************************************************************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************************************************************************************
master                     : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Nous allons compiler un projet d’exemple écrit en Rust. C’est un simple serveur TCP renvoyant toutes données envoyées, tel un écho. Vous trouverez le code source à l’emplacement suivant : /opt/echo_server/src/main.rs

fn main() {
    let port = env::var("PORT").unwrap_or_else(|_| String::from("8080"));
    let host_address = format!("0.0.0.0:{}", &port);
    let listener = TcpListener::bind(&host_address, false).expect("Failed to bind on address");
    println!("Listening on {}", host_address);
    loop {
        let stream = listener.accept(0).unwrap().0;
        if let Err(error) = echo(stream) {
            println!("Error: {:#?}", error);
        };
    }
}

fn echo(mut stream: TcpStream) -> Result<()> {
    let mut buff = [0u8; 1024];
    let mut data = Vec::new();

    loop {
        let n = stream.read(&mut buff)?;
        data.extend_from_slice(&buff[0..n]);
        if n < 1024 {
            break;
        }
    }
    println!("Received {} bytes", data.len());
    stream.write_all(&data)?;
    stream.shutdown(Shutdown::Both)?;

    Ok(())
}

Avant de passer à l’étape suivante, nous allons nous connecter à la machine virtuelle, pour tester les commandes WasmEdge.

vagrant ssh
cd /opt/echo_server
cargo clean
cargo build --release --target wasm32-wasi
   Compiling libc v0.2.126
   Compiling wasmedge_wasi_socket v0.3.3
   Compiling echo_server v0.1.0 (/opt/echo_server)
    Finished release [optimized] target(s) in 1.59s
wasmedge target/wasm32-wasi/release/echo_server.wasm
Listening on 0.0.0.0:8080
# Ouvrez un second terminal, puis connectez-vous en ssh à la vm
echo "adaltas" | nc localhost 8080
# vous pouvez fermer ce terminal
# Sur le premier terminal, une nouvelle ligne devrait être apparue
Received 8 bytes
# ctrl + c pour arrêter l'exécution de ce serveur
# la commande suivante compilera le bytecode webassembly en tant que librairie native
# cela résultera en de meilleures performances !
wasmedgec target/wasm32-wasi/release/echo_server.wasm echo_server
[2022-06-12 19:02:00.209] [info] compile start
[2022-06-12 19:02:00.222] [info] verify start
[2022-06-12 19:02:00.234] [info] optimize start
[2022-06-12 19:02:01.317] [info] codegen start
[2022-06-12 19:02:02.237] [info] output start
[2022-06-12 19:02:02.239] [info] compile done
[2022-06-12 19:02:02.240] [info] output start
wasmedge echo_server
Listening on 0.0.0.0:8080

Cinquième étape : Conteneurisons !

Dans cette étape, nous allons exécuter notre projet echo_server à l’intérieur d’un conteneur.

Comme vous le savez, les techologies de conteneur actuelles sont basées sur un runtime de conteneur qui s’interfacera avec le noyau de votre système d’exploitation. Ces runtimes sont utilisés pour exécuter des applications C (ou natives). Si vous exécutez un conteneur Python, en réalité, votre runtime de conteneur exécutera l’interpréteur python (un programme compilé -> code natif) qui exécutera le script Python !

Nous allons utiliser un runtime de conteneur qui sait parler nativement le bytecode wasm !

Si vous inspectez le contenu du playbook ansible/04_install_container_runtime.yml, vous pourrez vous apercevoir que nous compilons le runtime de conteneur crun (une alternative à runc).

N.B : utiliser crun est un choix personnel, vous pouvez parfaitement utiliser runc

ansible-playbook ansible/04_install_container_runtime.yml
PLAY [master] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [master]

TASK [Install packages to build crun] *********************************************************************************************************************************************************************
changed: [master]

TASK [Clone crun repository] ******************************************************************************************************************************************************************************
changed: [master]

TASK [Generate build configuration] ***********************************************************************************************************************************************************************
changed: [master]

TASK [Build crun] *****************************************************************************************************************************************************************************************
changed: [master]

TASK [Install crun] ***************************************************************************************************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************************************************************************************
master                     : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ensuite, avec le playbook ansible/05_install_container_tools.yml, nous installons buildah (un outil de génération d’image de conteneur) et podman (un outil d’exécution de conteneur), puis nous configurons podman afin qu’il utilise notre version personnalisée de crun.

N.B : utiliser buildah ainsi que podman est un choix personnel, utiliser docker est parfaitement compatible !

ansible-playbook ansible/05_install_container_tools.yml
PLAY [master] *********************************************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [master]

TASK [Add OpenSuse apt key] *******************************************************************************************************************************************************************************
changed: [master]

TASK [Add OpenSuse repository] ****************************************************************************************************************************************************************************
changed: [master]

TASK [Install Buildah and Podman] *************************************************************************************************************************************************************************
changed: [master]

TASK [Copy default containers configuration] **************************************************************************************************************************************************************
changed: [master]

TASK [Restart podman] *************************************************************************************************************************************************************************************
changed: [master]

PLAY RECAP ************************************************************************************************************************************************************************************************
master                     : ok=6    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Enfin, notre environnement de conteneur est prêt, générons notre conteneur and exécutons notre programme !

À la racine du projet echo_server, vous trouverez un Containerfile qui générera une image depuis scratch (scratch est une non-operération, cela veut dire que l’image ne sera basée sur aucune distribution !) and copiera le bytecode wasm dans l’image.

FROM scratch
COPY target/wasm32-wasi/release/echo_server.wasm /

CMD ["/echo_server.wasm"]

Connectons-nous à la machine virtuelle, et essayons de générer ce conteneur :

(Toutes ces actions sont effectuées dans le playbook ansible/06_run_in_container.yml)

vagrant ssh
cd /opt/echo_server
buildah bud --annotation "module.wasm.image/variant=compat" -t echo_server:latest
STEP 1/3: FROM scratch
STEP 2/3: COPY target/wasm32-wasi/release/echo_server.wasm /
STEP 3/3: CMD ["/echo_server.wasm"]
COMMIT echo_server:latest
Getting image source signatures
Copying blob 74c551b5129a done
Copying config 8fe3b74406 done
Writing manifest to image destination
Storing signatures
--> 8fe3b74406f
Successfully tagged localhost/echo_server:latest
8fe3b74406fe14983be2d0b579f4a6f68441e49841b2d35a5f9c505724f37ae1
podman image ls
localhost/echo_server       latest      8fe3b74406fe  1 minutes ago  2.01 MB

Vous pouvez voir que nous annotons l’image avec l’annotation module.wasm.image/variant=compat.

Cette annotation sera enregistrée dans le manifeste de l’image. Cela permet à notre runtime de conteneur de savoir que l’image contient un programme webassembly.

Comme vous pouvez le voir, cette image ne contient que le .wasm, et est donc très légère ! (environ 2.01 Méga Octets)

Ensuite, exécutons ce conteneur :

podman run --name echo_server --publish 8080:8080 --detach localhost/echo_server:latest
356b31a98a942b841eaf1eecc861d0b5e94053d76ff498e4d5ad546b18d35b24 # ce hash est différent de votre côté
echo "adaltas" | nc localhost 8080
adaltas
podman logs echo_server
Listening on 0.0.0.0:8080
Received 8 bytes

Sixième étape : Introduisons Kubernetes

Maintenant que nous sommes capables d’exécuter des conteneur webassembly, déployons des pods dans Kubernetes.

Vous aurez besoins de déployer ces playbooks avant de pouvoir déployer sur Kubernetes :

  • ansible/07_install_kubernetes.yml : Ce playbook déploie un cluster Kubernetes ne contenant qu’un seul noeud et configure Containerd en tant qu’interface de runtime de conteneur utilisant crun
  • ansible/08_private_registry.yml : Ce playbook configure un registry privé pour stocker nos images de conteneur au format OCI

Retournons sur la machine virtuelle pour la dernière fois :

Vous trouverez toutes les actions exécutées ci-dessous dans le playbook ansible/09_deploy_on_k8s.yml

# installer le plugin vagrant scp: vagrant plugin install vagrant-scp
vagrant scp ./ansible/files/echo_server.yml /tmp/echo_server.yml
echo_server.yml                    100%  922   905.3KB/s   00:00
vagrant ssh
# on tag l'image avec le nom du registry privé, puis on la push dans le registry
podman image tag localhost/echo_server:latest localhost:5000/echo_server:latest
podman push --tls-verify=false localhost:5000/echo_server:latest
Getting image source signatures
Copying blob 74c551b5129a done
Copying config 8fe3b74406 done
Writing manifest to image destination
Storing signatures
# on applique le déploiement
kubectl apply -f /tmp/echo_server.yml
deployment.apps/echo-server-deployment created
service/echo-server-service created
# on attend que le déploiement soit disponible
kubectl wait deployment echo-server-deployment --for condition=Available
deployment.apps/echo-server-deployment condition met
# on envoie une payload au conteneur
echo "adaltas" | nc localhost 31808
adaltas
# on récupère les logs du déploiement
kubectl logs deployment/echo-server-deployment
Listening on 0.0.0.0:8080
Received 8 bytes

Conclusion

En suivant ce tutoriel, vous avez pu vous rendre compte de la puissance et la simplicité d’utilisation du WebAssembly. En créant un nouveau type de bytecode, optimisé pour l’edge computing qui peut s’exécuter n’importe où. Depuis le navigateur ou un conteneur linux. Le WebAssembly nous apporte un nouveau paradigme dans lequel penser nos applications.

Le WebAssembly est une idée puissante qui peut nous mener à de meilleures performances, une meilleure observabilité en développant de nouvelles technologies d’instrumentation ciblant le bytecode WASM et nous donnant une meilleure interopérabilité en nous permettant de ne déployer qu’une seule technologie au lieu d’une dizaine.

Qu’elles sont les prochaines étapes pour WasmEdge ? Voici la feuille de route du projet.

Partagez cet article

Canada - Maroc - France

Nous sommes une équipe passionnée par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Science le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.

Support Ukrain