Will "WebAssembly" be the next generation of Java and Node.js? --Running "Wasm Container" with Kubernetes

Will "WebAssembly" be the next generation of Java and Node.js? --Running "Wasm Container" with Kubernetes

In this article, we will review the recent focus on WebAssembly and show how to try WebAssembly applications with Kubernetes.

From web browser to server side - WebAssembly in the spotlight

WebAssembly (often abbreviated as "Wasm") is gaining attention as a platform to run applications faster in the browser. While it was standardized by the W3C (World Wide Web Consortium), by around 2017, major browsers already joined to support WebAssembly. Furthermore, WebAssembly is used in popular apps such as "Zoom," "Google Meet," "Google Earth," "Unity," etc.
WebAssembly first emerged as a new method enabling high-speed code processing on the browser. Now, it is also becoming appliable for processing on the host environment, and the nonprofit organization "Bytecode Alliance", which Google, Microsoft, Amazon.com, VMware, Intel, Docker, and others are participating in, developed WebAssembly runtime. In September 2022, Wasmtime version 1.0, the WebAssembly runtime to run code on the host, developed by the Bytecode Alliance, was released with much fanfare.

Just as Java and JavaScript started in the browser and spread to the server side with Java Application Server and Node.js, WebAssembly is gradually shifting its target to server-side operation. The number of WebAssembly users by usage in the WebAssembly research report "The State of WebAssembly 2022" (see figure below) shows that next to web development, most developers are using it for serverless and containers.

Number of WebAssembly users by usage

Number of WebAssembly users by usage

Kubernetes also makes it possible to run WebAssembly applications through "Krustlet", a Kubelet which supports WebAssembly, and "crun", a container runtime which supports WebAssembly execution. In this article, we will review WebAssembly, explain the status of Kubernetes support and introduce how to run WebAssembly applications on Kubernetes using "kind" (short for "Kubernetes in Docker," a tool to build Kubernetes clusters on Docker) and crun.
Please note that the situation of WebAssembly and Kubernetes is constantly changing and is not stable as of now. This article shows the status as of December 2022. Please also note that this article is not to guarantee future operation feasibility of WebAssembly.

What is WebAssembly?

JavaScript has long been used as a standard language for web browsers, but the recent trends such as AI and data analysis have driven an increasing demand for faster processing on the web browsers. With this background, "asm.js" was proposed to enable faster JavaScript execution, and then WebAssembly was proposed as a further evolving solution from asm.js, and standardized by the W3C.
WebAssembly is one of the virtual instruction-set architectures or low-level programming languages. WebAssembly defines two formats: Wasm binary with a binary format, and "WAT" (WebAssembly Text format), which is a human-readable text format.
A Wasm binary is architecture- and OS-independent code that can be executed by a virtual machine, similar to Java bytecode or CIL (Common Intermediate Language) in .NET. Specific Wasm binaries and corresponding WATs can be found in the "wat2wasm demo", so if you are interested, please try it.

Although WAT is text format, it is a low-level language similar to assembler and is difficult to use in programs. C/C++, Go language, Rust, C#, etc. are used as programming languages, and code written in these languages is compiled into Wasm binaries by a compiler.

Six features of WebAssembly

Let's look at a few more features of WebAssembly.

1. High speed

JavaScript has been faster, but it is still slower than native binaries. It requires type checking due to dynamic typing, parsing of scripts, and also because it is a textual language, file sizes tend to be large and therefore takes a long time for loading.
With static typing, type checking is no more needed. Writing code in binary form also makes it faster by eliminating the parsing. It also does not use garbage collection, which means there is no memory management overhead.

2. Portability

It is OS and CPU architecture independent, so as long as the WebAssembly runtime is provided, it will run on the same binary on any OS (Windows, macOS, Linux, etc.) and architecture (x86, Arm, Risc V, etc.).

By having this feature, it is also becoming an option for extension plug-ins for tools and middleware.

3. Standardization of specifications by W3C and standard support in browsers

The WebAssembly specification is standardized by the W3C, a standards organization for web specifications, not by a specific vendor or alliance. The first public working draft of the WebAssembly specification was released as "WebAssembly Core Specification Version 2.0" on April 19, 2022.
By being standardized by the W3C, it is not affected by vendor strategy or their conveniences. Unlike Java and Adobe Flash, which requires additional runtime installation, WebAssembly is supported by many browsers as standard, therefore additional plug-ins or add-ons are not required.

4. Multilingual support

WebAssembly only specifies a virtual instruction set architecture, not a programming language. Therefore, code can be written in languages such as C/C++, Rust, Go language, C#, etc., and compiled into Wasm binaries for execution.
Like Python and Ruby, there are new languages for which interpreters and virtual machines have been ported to WebAssembly and can run scripts on it.
According to the State of WebAssembly 2022 mentioned earlier, we see high percentage of Rust, C++, and Go languages in use, while use of Python and "Blazor*" are rapidly increasing in 2021-2022.

% of languages used in WebAssembly

  • * Blazor is an open-source software web framework that allows developers to create web applications using C# and HTML. The framework "Blazor WebAssembly" is provided for developing applications as Wasm binaries.
    For the list of languages, implementations, and compilers supported by WebAssembly, see Stephen A.'s "Awesome WebAssembly Languages".

5. Secure

WebAssembly itself defines a virtual instruction set architecture and does not use OS functionality or does not provide network access capabilities. It is highly secure as said, "when it runs on a sandbox, it is difficult to influence outside the sandbox."
In Java, vulnerable code could be loaded and executed through class loaders and reflections, meanwhile WebAssembly has low vulnerability risk since it does not have such a mechanism.

6. Define APIs according to usage

Even though it is mentioned earlier that it does not have OS functions, that seems too inconvenient. Therefore WebAssembly JavaScript Interface Version 2.0, which works with JavaScript when used in a browser, and WebAssembly System Inferface (WASI), which uses OS functions such as file and network access when running on the host, are provided. In that sense, it could sound as contradicting to what is described in "5. Secure," but still it is safer than Java, as only the minimum number of APIs can be selected upon demand, compared to Java which has all the functions inclusively within runtime.

WebAssembly support in Kubernetes

From here, we explain WebAssembly support in Kubernetes. There are three ways how WebAssembly can be used.

1. Running WebAssembly applications

The use case shown in this article is to run a WebAssembly application on Kubernetes, but there are several ways to do this which will be explained in detail in the next chapter.

2. Extension of Kubernetes components

Taking advantage of free WebAssembly platform, one of the ways is to create the extension part of the components as WebAssembly and then incorporate. As popular examples, processes such as filters for "Envoy", a proxy used in the "Istio" service mesh, can be written in WebAssembly.

3. Tool expansion

Another way is to extend the functionality of the tool using WebAssembly. The usage may look similar to "2. Extension of Kubernetes components" above. Though, since this method is not to integrate the extended parts into Kubernetes directly as an external tool, we purposely separate this from the above. For example, the vulnerability scanning tool "Trivy" uses WebAssembly to extend scan policy definitions.

Running WebAssembly applications

There are four main ways to run WebAssembly applications on Kubernetes.

WebAssembly support method with Kubernetes

1. Running on containers

This is a method of running an application by packing the WebAssembly runtime into a container image. This is the easiest way as it does not require any changes to the Kubernetes configuration.
WebAssembly runtime must be incorporated into the container image, and if different architectures are used, a dedicated image must be created with incorporated WebAssembly runtime per architecture, so container portability is lost. For example, an image created for x86 Linux cannot be used on Arm or Windows.

2. Executing with container runtime (OCI Runtime)

This method is to use "crun" and "youki", which support WebAssembly, instead of "runc", which is used as an OCI (Open Container Initiative) compliant container runtime (OCI runtime).

Using crun or youki, you can directly run WebAssembly binaries in the Wasm container.

With this, container portability increases as it eliminates the need of architecture-dependent runtimes deployment in the Wasm container. crun and youki are compatible with runc, and the architecture is simpler than using the Krustlet method below since the entire runtime can be replaced.

Since crun and youki can also run regular container images, you can run Wasm containers and regular containers on the same node. Note that as of December 2022, to be able to use Wasm containers with crun, it needs to be built always with the Wasm runtime-compatible option.

3. Executing with containerd shim

The "containerd shim" is a process between containerd and the OCI runtime that is launched for each container and serves as a bridge between containerd and the OCI runtime.
This method allows WebAssembly applications to run without the OCI runtime by using a containerd wasm shim that supports the WebAssembly runtime.

Docker Desktop has supported WebAssembly in this method since Docker Desktop 4.15 in the WebAssemply Technology Preview.
When using this method with Kubernetes, it is necessary to define a "RuntimeClass" to switch between the OCI runtime execution and the WebAssembly runtime execution, to be able to maintain and utilize both regular container and Wasm container at the same time.

4. Executing on the worker node

This method is to replace Kubelet with a Krustlet that runs WebAssembly. Since regular containers cannot be run on Krustlet, WebAssembly need to be run on a dedicated WebAssembly node. This is therefore a less efficient method in use of resources than "2. Execute with container runtime (OCI Runtime)" above, as that dedicated node must be built.

Just as a note, development of Krustlet itself seems to have been put on hold as of December 2022.

Here in this article, we explain how to use WebAssembly with Kubernetes using crun, as described at "2. Execute with container runtime (OCI Runtime)" in above.

 

Column: Next generation OCI runtime crun and youki

The runc is written in Go language and has long been used as the standard OCI runtime, however recently, OCI runtimes implemented in C language or Rust have been gaining attention. The C language runtime is crun, developed by Giuseppe Scrivano. It has been used as the default OCI runtime since RHEL (Red Hat Enterprise Linux) 9.0.
Youki, named after the Japanese word "youki" which means the container, is developed mainly by Toru Komatsu (Utamoku), who is Japanese, as the name may imply. Youki is implemented in Rust, a language recently adopted by Linux. Although not yet officially released, youki has already been reaching to a very practical level, passing all the integration tests provided by OCI.
New OCI container runtimes such as crun and youki have the following characteristics compared to runc:

  • Fast container execution speed
  • Low memory consumption
  • Supports Wasm containers

In this article, we use crun for Wasm containers, but youki also supports Wasm containers, so if you are interested in, please give it a try.

Building a WebAssembly execution environment

From here, we are going to build an environment to run WebAssembly on Kubernetes. In this article, we will use "kind" to build a WebAssembly execution environment using WasmEdge, one of the Wasm runtimes. This time, we use "Ubuntu 22.04". If Docker works, other environments will also work, so please challenge with other alternative options as well.

Installing Docker

Let's install Docker for the first step. Please note that in this article, everything is run as root user to avoid extra steps.

Install Kind, build Kubernetes cluster

Build a Kubernetes cluster in kind. As we use Wasm container, specify the node image for it.

Install the kubectl command and check the cluster processing.

The cluster processing was verified.

Execution check

Once you have a Wasm container-enabled Kubernetes cluster, you can perform execution check on your WebAssembly application. Execute the following command.

The "wasmedge/example-wasi:latest" specified above looks like an normal container image, but it is a Wasm container with a Wasm binary specified as the binary for container execution (for details, see "How to Create a Wasm Container Image" below). Or, by adding annotations "module.wasm.image/variant=compat-smart" to the container, it can be executed as a Wasm container.

If you try starting the program without annotations as you do normally, you will get an error message telling the Wasm binary specified as container executable file "is not an executable".

Message appearing when you try starting Wasm container without annotations

Column: How to run Wasm container with kind

Since kind builds a Kubernetes cluster on Docker, it is often thought that Docker is used for container runtime, but it launches containerd in Docker and uses that containerd as container runtime.
Kind's WasmEdge-enabled node image uses crun which incorporates WasmEdge, instead of runc, so that the host Docker can run Wasm binaries without any configuration changes as shown in the picture below.

How Wasm container run in kind

As for your troubleshooting if Wasm containers did not work well, when I did it for the first time I did not understand this architecture and so took long time to resolve the problem. Note that the green-colored block in the above picture and containerd (on the container) and also the blue-colored containerd (on the host) are all different binaries.
Also note that the contents of the runc file on the container is crun. This could be because crun and runc are compatible, therefore applying crun by overwriting runc, to make configuration of containerd is not required.

Creating Wasm container images

Once you verify that the Wasm container works, you can create and run your own Wasm container image. Here, we will use Rust as the language, but you can also use C/C++ or Go language, so if you are interested, please try with other languages as well.
First, use the multi-stage build function to build Wasm binaries in a container, and create Wasm container image with copied Wasm binary only.
Create a directory and copy Dockefile, Cargo.toml and main.rs with the following directory structure. You can get the complete set of files from GitHub.

list of files

First, let's look at the code: below is a simple Rust sample code to display hello.

main.rs

Next, create a configuration file for the Rust build system "Cargo".

Cargo.toml

Lastly, create a Dockerfile to create a container. Using multi-stage build, build the Wasm binary in the "builder" stage, then copy the "hello.wasm" generated in the empty (scratch) container image. Specify hello.wasm as the entry point when starting the container.

Dockerfile

Build the image and upload the image to the container registry. Here we upload to "Docker Hub" (docker.io). If you do not have a Docker Hub account, get an account beforehand or specify the URL of anotherimage registry.

Once uploaded to the container registry, verify that it works.

Running Wasm containers in non-kind environments

To run Wasm containers in other environment, if "containerd/cri-o" is used as the container runtime, you can specify crun or youki as the runc of the internally used OCI runtime.

The way how to use WasmEdge in containerd by incorporating it in crun as a WebAssembly runtime is introduced at "WasmEdge Runtime - containerd", so if you are interested in, visit the site and try. Please note that to perform that, crun needs to be built and the steps explained in the URL reference above also need to be executed.

Conclusion

In this article, we reviewed WebAssembly and how to run WebAssembly applications on Kubernetes.
kind makes Wasm container execution easy, though, the way of running Wasm containers with Kubernetes is still unstable and chaotic. For example, the current situation is such that the development of Krustlet which attracted attention around 2021 has been stopped, that building crun binary is needed when you try to run Wasm container on your own Kubernetes environment you built, and also we see new ways getting implemented to run Wasm containers with Docker Desktop, etc. We need to keep a close eye on how future trends will be.

Original Article

This article is a translation and adaptation of a December 2022 ITmedia@IT article
「WebAssembly」は次世代のJava、Node.jsになる?――「Wasmコンテナ」をKubernetesで動かす:Cloud Nativeチートシート(23) - @IT (itmedia.co.jp)