😎 Manage WebAssembly Apps in WasmEdge Using Docker Tools
Tags: webassembly, rust, docker
Published: 2021-06-29 11:55:31



Developers can leverage Docker tools such as the DockerHub and CRI-O to deploy, manage, and run lightweight WebAssembly applications in [WasmEdge](https://github.com/WasmEdge/WasmEdge). WasmEdge, an advanced WebAssembly runtime [hosted by the CNCF](https://www.cncf.io/sandbox-projects/) (Cloud Native Computing Foundation), is an execution sandbox for Edge Computing applications. 

![WasmEdge](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fswz98zqk3e6t70cv8o.png)

While WebAssembly was initially invented as a runtime for browser applications, its lightweight and high-performance sandbox design has made it an appealing choice as a general-purpose application container. 


> If WASM+WASI existed in 2008, we wouldn't have needed to create Docker. — Solomon Hykes, co-founder of Docker


Compared with Docker, [WebAssembly could be 100x faster at startup](https://www.infoq.com/articles/arm-vs-x86-cloud-performance/), have a much smaller memory and disk footprint, and have a better-defined safety sandbox. However, the trade-off is that WebAssembly requires its own language SDKs, and compiler toolchains, making it a more constrained developer environment than Docker. WebAssembly is increasingly used in Edge Computing scenarios where it is difficult to deploy Docker or when the application performance is vital.

One of the great advantages of Docker is its rich ecosystem of tools. At WasmEdge, we would like to bring Docker-like tooling to our developers. To accomplish that, we created an alternative runner for CRI-O, called [runw](https://github.com/second-state/runw), to load and run WebAssembly bytecode programs as if they are Docker image files. 

## Install an WebAssembly runner in CRI-O

In order to support WebAssembly in CRI-O, you just need to download the `runw` binary release and install it into your CRI-O.


> Since the `runw` binary already includes WasmEdge, there is no need to install WasmEdge or any other WebAssembly VM separately. 


First, make sure that you are on Ubuntu 20.04 with LLVM-10 installed. If you are on a different platform, please refer to [the project documentation on how to build](https://github.com/second-state/runw#build-from-source) `runw` for your OS. 


```
sudo apt install -y llvm-10-dev liblld-10-dev
```


Also make sure that you have [cri-o](https://cri-o.io/), [crictl](https://github.com/kubernetes-sigs/cri-tools), [containernetworking-plugins](https://github.com/containernetworking/plugins), and [buildah](https://github.com/containers/buildah) or [docker](https://github.com/docker/cli) installed. 

Next, download the `runw` binary build. 


```
wget https://github.com/second-state/runw/releases/download/0.1.0/runw
```


Now, you can install `runw` into CRI-O as an alternative runtime for WebAssembly.


```
# Get the wasm-pause utility
sudo crictl pull docker.io/beststeve/wasm-pause

# Install runw into cri-o
sudo cp -v runw /usr/lib/cri-o-runc/sbin/runw
sudo chmod +x /usr/lib/cri-o-runc/sbin/runw
sudo sed -i -e 's@default_runtime = "runc"@default_runtime = "runw"@' /etc/crio/crio.conf
sudo sed -i -e 's@pause_image = "k8s.gcr.io/pause:3.2"@pause_image = "docker.io/beststeve/wasm-pause"@' /etc/crio/crio.conf
sudo sed -i -e 's@pause_command = "/pause"@pause_command = "pause.wasm"@' /etc/crio/crio.conf
sudo tee -a /etc/crio/crio.conf.d/01-crio-runc.conf <<EOF
[crio.runtime.runtimes.runw]
runtime_path = "/usr/lib/cri-o-runc/sbin/runw"
runtime_type = "oci"
runtime_root = "/run/runw"
EOF
```


Finally, restart `cri-o` for the new WebAssembly runner to take effect.


```
sudo systemctl restart crio
```




## Build a Wasm application from Rust

The sample Wasm application is written in Rust. To make it work, make sure that you have [Rust](https://www.rust-lang.org/tools/install) and the [rustwasmc](https://www.secondstate.io/articles/rustwasmc/) toolchains installed. 


> You only need Rust compiler and rustwasmc to build Rust source into a wasm bytecode file. If you already have a wasm bytecode program, and just want to run it with cri-o, you can simply skip this section. 


The application source code is just a `main.rs` function. [It is here](https://github.com/second-state/wasm-learning/tree/master/ssvm/wasi). The application demonstrates how to access the file system and other operating system resources from WasmEdge using the standard Rust API.


```
fn main() {
  println!("Random number: {}", get_random_i32());
  println!("Random bytes: {:?}", get_random_bytes());
  println!("{}", echo("This is from a main function"));
  print_env();
  create_file("/tmp.txt", "This is in a file");
  println!("File content is {}", read_file("/tmp.txt"));
  del_file("/tmp.txt");
}

pub fn get_random_i32() -> i32 {
  let x: i32 = random();
  return x;
}

pub fn get_random_bytes() -> Vec<u8> {
  let mut rng = thread_rng();
  let mut arr = [0u8; 128];
  rng.fill(&mut arr[..]);
  return arr.to_vec();
}

pub fn echo(content: &str) -> String {
  println!("Printed from wasi: {}", content);
  return content.to_string();
}

pub fn print_env() {
  println!("The env vars are as follows.");
  for (key, value) in env::vars() {
    println!("{}: {}", key, value);
  }

  println!("The args are as follows.");
  for argument in env::args() {
    println!("{}", argument);
  }
}

pub fn create_file(path: &str, content: &str) {
  let mut output = File::create(path).unwrap();
  output.write_all(content.as_bytes()).unwrap();
}

pub fn read_file(path: &str) -> String {
  let mut f = File::open(path).unwrap();
  let mut s = String::new();
  match f.read_to_string(&mut s) {
    Ok(_) => s,
    Err(e) => e.to_string(),
  }
}

pub fn del_file(path: &str) {
  fs::remove_file(path).expect("Unable to delete");
}
```


You can build the application into a wasm bytecode file as follows. 


```
rustwasmc build
```


The outcome wasm bytecode file is [available here](https://github.com/second-state/wasm-learning/blob/master/ssvm/wasi/wasi_example_main.wasm). 


## Build and publish a Docker Hub image for the Wasm app

You can now publish the entire wasm bytecode file into Docker hub as if it is a Docker image.

First, create a Dockerfile in the `pkg/` directory as follows. 


```
FROM scratch
ADD wasi_example_main.wasm .
CMD ["wasi_example_main.wasm"]
```


Create an image and publish it to Docker hub!


```
sudo buildah bud -f Dockerfile -t wasm-wasi-example
sudo buildah push wasm-wasi-example docker://registry.example.com/repository:tag

# Example: the following command publishes the wasm image to the public Docker hub under user account "hydai"
sudo buildah push wasm-wasi-example docker://docker.io/hydai/wasm-wasi-example:latest
```


Now, you can use Docker tools, such as the `crictl`, to pull the publish wasm file as an image. Below is an example for the wasm file image we published.


```
sudo crictl pull docker.io/hydai/wasm-wasi-example
```



## Start the Wasm app using CRI-O

To start and run the wasm file, you will need to create two configuration files for CRI-O. Create a `container_wasi.json` file as follows. It tells the CRI-O runtime where to pull the wasm file image from the Docker repository.


```
{
  "metadata": {
    "name": "podsandbox1-wasm-wasi"
  },
  "image": {
    "image": "hydai/wasm-wasi-example:latest"
  },
  "args": [
    "wasi_example_main.wasm", "50000000"
  ],
  "working_dir": "/",
  "envs": [],
  "labels": {
    "tier": "backend"
  },
  "annotations": {
    "pod": "podsandbox1"
  },
  "log_path": "",
  "stdin": false,
  "stdin_once": false,
  "tty": false,
  "linux": {
    "resources": {
      "memory_limit_in_bytes": 209715200,
      "cpu_period": 10000,
      "cpu_quota": 20000,
      "cpu_shares": 512,
      "oom_score_adj": 30,
      "cpuset_cpus": "0",
      "cpuset_mems": "0"
    },
    "security_context": {
      "namespace_options": {
        "pid": 1
      },
      "readonly_rootfs": false,
      "capabilities": {
        "add_capabilities": [
          "sys_admin"
        ]
      }
    }
  }
}
```


Next, create a `sandbox_config.json` file as follows. It defines the sandbox environment to run the wasm application.


```
{
  "metadata": {
    "name": "podsandbox12",
    "uid": "redhat-test-crio",
    "namespace": "redhat.test.crio",
    "attempt": 1
  },
  "hostname": "crictl_host",
  "log_directory": "",
  "dns_config": {
    "searches": [
      "8.8.8.8"
    ]
  },
  "port_mappings": [],
  "resources": {
    "cpu": {
      "limits": 3,
      "requests": 2
    },
    "memory": {
      "limits": 50000000,
      "requests": 2000000
    }
  },
  "labels": {
    "group": "test"
  },
  "annotations": {
    "owner": "hmeng",
    "security.alpha.kubernetes.io/seccomp/pod": "unconfined"
  },
  "linux": {
    "cgroup_parent": "pod_123-456.slice",
    "security_context": {
      "namespace_options": {
        "network": 0,
        "pid": 1,
        "ipc": 0
      },
      "selinux_options": {
        "user": "system_u",
        "role": "system_r",
        "type": "svirt_lxc_net_t",
        "level": "s0:c4,c5"
      }
    }
  }
}
```


Now you can create a CRI-O pod as follows. 


```
# Create the POD. Output will be different from example.
sudo crictl runp sandbox_config.json
7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4

# Set a helper variable for later use.
POD_ID=7992e75df00cc1cf4bff8bff660718139e3ad973c7180baceb9c84d074b516a4
```


From the pod, you can create a container to run the wasm bytecode program in isolation. 


```
# Create the container instance. Output will be different from example.
sudo crictl create $POD_ID container_wasi.json sandbox_config.json
1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f
```


Finally, start the container and see the output from the wasm application.


```
# List the container, the state should be `Created`
sudo crictl ps -a

CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Created             podsandbox1-wasm-wasi   0                   7992e75df00cc

# Start the container
sudo crictl start 1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f
1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

# Check the container status again.# If the container is not finishing its job, you will see the Running state# Because this example is very tiny. You may see Exited at this moment.
sudo crictl ps -a
CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Running             podsandbox1-wasm-wasi   0                   7992e75df00cc

# When the container is finished. You can see the state becomes Exited.
sudo crictl ps -a
CONTAINER           IMAGE                           CREATED              STATE               NAME                     ATTEMPT             POD ID
1d056e4a8a168       hydai/wasm-wasi-example:latest   About a minute ago   Exited              podsandbox1-wasm-wasi   0                   7992e75df00cc

# Check the container's logs
sudo crictl logs 1d056e4a8a168f0c76af122d42c98510670255b16242e81f8e8bce8bd3a4476f

Test 1: Print Random Number
Random number: 960251471

Test 2: Print Random Bytes
Random bytes: [50, 222, 62, 128, 120, 26, 64, 42, 210, 137, 176, 90, 60, 24, 183, 56, 150, 35, 209, 211, 141, 146, 2, 61, 215, 167, 194, 1, 15, 44, 156, 27, 179, 23, 241, 138, 71, 32, 173, 159, 180, 21, 198, 197, 247, 80, 35, 75, 245, 31, 6, 246, 23, 54, 9, 192, 3, 103, 72, 186, 39, 182, 248, 80, 146, 70, 244, 28, 166, 197, 17, 42, 109, 245, 83, 35, 106, 130, 233, 143, 90, 78, 155, 29, 230, 34, 58, 49, 234, 230, 145, 119, 83, 44, 111, 57, 164, 82, 120, 183, 194, 201, 133, 106, 3, 73, 164, 155, 224, 218, 73, 31, 54, 28, 124, 2, 38, 253, 114, 222, 217, 202, 59, 138, 155, 71, 178, 113]

Test 3: Call an echo function
Printed from wasi: This is from a main function
This is from a main function

Test 4: Print Environment Variables
The env vars are as follows.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TERM: xterm
HOSTNAME: crictl_host
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The args are as follows.
/var/lib/containers/storage/overlay/006e7cf16e82dc7052994232c436991f429109edea14a8437e74f601b5ee1e83/merged/wasi_example_main.wasm
50000000

Test 5: Create a file `/tmp.txt` with content `This is in a file`

Test 6: Read the content from the previous file
File content is This is in a file

Test 7: Delete the previous file
```



## What's next

In this article, we have seen how to start, run, and manage WasmEdge applications using Docker-like CRI-O tools.

Our next step is to use Kubernetes to manage WasmEdge containers. To accomplish that, we will need to install a runner binary inside Kubernetes so that it could support regular Docker images and wasm bytecode images at the same time. 


**Join us in the WebAssembly revolution!**

👉 Slack Channel: #wasmedge on [CNCF Slack channel](https://slack.cncf.io/)

👉 Mailing list: Send an email to [WasmEdge@googlegroups.com](https://groups.google.com/g/wasmedge/)

👉 Be a contributor: [checkout our wish list](https://github.com/WasmEdge/WasmEdge/tree/master/doc/wish_list.md) to start contributing!