
WasmEdge: WebAssembly runtimes come for the edge
With many security challenges solved by design in its core concept, many projects benefit from using WebAssembly.
WasmEdge runtime is an efficient virtual machine optimized for edge computing. Its main use cases are:
- Jam stack apps, through a static interface with a serverless backend (FaaS)
- Cars
- IoT and Stream Processing
It is an embeddable virtual machine that can be used as a process, in a process, or orchestrated as an embedded OCI container (providing a OCI compatible interface).
What is WebAssembly?
WebAssembly (Wasm) is a bytecode designed to run alongside JavaScript, it is a successor to a few projects designed to speed up code that runs in a browser.
Its most notable predecessors are asm.js and Google Native Client (GNI).
Asm.js and GNI had two different philosophies. The former uses a subset of JavaScript, therefore you only need a JavaScript engine to run it. While the latter is a full-fledged sandbox intended to be integrated into browsers.
The EmScript is a tool that allows you to convert LLVM-based bytecode (C, C++, Rust) to asm.js.
WebAssembly features are:
- Portable
- Secured
- Performance
- Reduced binary size
The WebAssembly specification defines two languages, the binary (compiled) and the text, which are made to be readable by humans.
Here is an example of a code written in Rust and how it is translated in WAT (WebAssembly Text).
Rust:
pub fn add_two(a: i32, b: i32) -> i32 {
a + b
}
WAT (WebAssembly Text):
(module
(type $t0 (func (param i32 i32) (result i32)))
(func $addTwo (export "add_two") (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(i32.add
(local.get $p0)
(local.get $p1))))
WebAssembly extension
WasmEdge suggests several extensions that are either a feature or a proposed feature of WebAssembly, such as WASI.
For example, WASI is an extension that allows interfacing with the system using sockets (TCP/UDP communication) or even accessing the file system using a POSIX-like file I/O.
Here is a list of the current additions that are proposed:
- WASI: WebAssembly System Interface
- Reference types
- Bulk memory operations
- SIMD: Single Instruction Multiple Data
Where does WasmEdge come in?
WasmEdge is one of the various runtimes that allow server-side WebAssembly execution with a focus on edge computing.
Rust is a first class citizen of the WebAssembly ecosystem and as such is a primary target for WasmEdge.
Here are the highlighted points on theirs website:
- Performance
- Ahead Of Time (AOT) compilation
- Small footprint
- Portable
- Embeddable
- Orchestratable
- Blockchain
- Retractable
WasmEdge Extensions
WasmEdge proposes a set of extensions that allow more advanced use cases, such as:
WasmEdge Extensions documentation
- TensorFlow, ONNX
- Image processing
- Key-value storage
- Network socket
- Command interface
- Ethereum
- Substrate
Tutorial
The following sections explain how to write your first WasmEdge application and how to run it in a content environment.
Here is one github archive which contains possible playbooks and the source code to run all the steps shown in this tutorial.
First step: Create your 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
Inventory:
master ansible_host=192.168.121.117 ansible_port=22 ansible_user=vagrant ansible_private_key_file=/path/to/key
TL;DR
You can run all playbooks by running ansible-playbook ansible/00_all.yml
.
This will perform any action, such as building the project to wasm, run in a container with and without kubernetes.
Second step: install WasmEdge
To install WasmEdge automatically, run the 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
Step Three: Install Rust Tool Chain
Rust is a first class citizen of the WASM ecosystem, in this tutorial we will be using a Rust project.
To install the rust toolchain, run the 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
Fourth step: Upload and build project
In this step we will upload and build the project with the following playbook: 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
We will compile an example project written in Rust. It is a simple TCP server that renders all the data that is sent.
You can find the source code at: /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(())
}
Before moving into the next step, we will step into the virtual machine to play with 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
echo "adaltas" | nc localhost 8080
Received 8 bytes
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
Fifth step: Let's contain
In this step we will run our project echo_server
inside a container.
As you know, current container technologies are based on a container runtime that will interact with your operating system's kernel. These runtimes are used to run C (aka native) applications. If you run a Python container, your container runtime will actually run the Python interpreter (a compiled program -> built-in) that runs the Python script!
We will use a container runtime that can natively speak wasm bytecode!
If you look into the playbook ansible/04_install_container_runtime.yml
we build a container runtime called crun (one more option runc).
NOTE: using crun is a personal choice, you can run the WasmEdge application with runc
also
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
Next, with ansible/05_install_container_tools.yml
we install buildah (a container building tool) and podman (a rootless container runtime tool) and configuration podman
to use our custom crun
.
NOTE: using buildah
and podman
is a personal choice, you can get by using only docker
!
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
Finally our container environment is ready, let's build a container and run some code!
At the root of echo_server
project you can find one Containerfile
which will create an image from scratch
(Scratch is a noop, there is no base distribution!) and copy the Wasm bytecode to the image.
FROM scratch
COPY target/wasm32-wasi/release/echo_server.wasm /
CMD ["/echo_server.wasm"]
You can find all the steps performed in the playbook ansible/06_run_in_container.yml
.
Let's go back into the virtual machine and try to build this container.
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
In this step you can see that we annotate the image with module.wasm.image/variant=compat
.
This annotation will be stored in the image's manifest. It will allow the container runtime to know that the image is a web assembly program.
As you can see, the image contains only .wasm
and is therefore really light.
Next, let's run the container:
podman run --name echo_server --publish 8080:8080 --detach localhost/echo_server:latest
356b31a98a942b841eaf1eecc861d0b5e94053d76ff498e4d5ad546b18d35b24
echo "adaltas" | nc localhost 8080
adaltas
podman logs echo_server
Listening on 0.0.0.0:8080
Received 8 bytes
Sixth step: Let's try with Kubernetes
Now that we can run containers that speak webassembly, let's deploy pods inside Kubernetes.
You must deploy the following playbooks before you can deploy on kubernetes:
ansible/07_install_kubernetes.yml
: Deploy a single kubernetes cluster and configure Containerd such as container driving withcrun
ansible/08_private_registry.yml
: Configure a private registry to store our container images (OCI format).
Let's go back into the virtual machine one last time:
You can find all the steps performed in the playbook ansible/09_deploy_on_k8s.yml
in the following blocks:
vagrant scp ./ansible/files/echo_server.yml /tmp/echo_server.yml
echo_server.yml 100% 922 905.3KB/s 00:00
vagrant ssh
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
kubectl apply -f /tmp/echo_server.yml
deployment.apps/echo-server-deployment created
service/echo-server-service created
kubectl wait deployment echo-server-deployment --for condition=Available
deployment.apps/echo-server-deployment condition met
echo "adaltas" | nc localhost 31808
adaltas
kubectl logs deployment/echo-server-deployment
Listening on 0.0.0.0:8080
Received 8 bytes
Conclusion
By following the tutorial, you've seen the power of WebAssembly. By bringing a new type of bytecode, optimized for the edge that can run anywhere, from the browser to a linux container, WebAssembly brought a new paradigm upon us.
WebAssembly is a powerful idea that can lead to better performance, better observability by developing instrument technologies that can target Wasm bytecode, and better functionality by allowing to deploy a single technology instead of many.
What's next for WasmEdge?
Here is Road map.
#WasmEdge #WebAssembly #runtimes #edge
Source link