Back in October 2022, the Docker organization had some pretty exciting news. They released a technical preview of Docker Desktop that had a new ability: to run Wasm bytecode. At the end of March 2023, we got another interesting release: Docker + Wasm Technical Preview 2. Let's see what new possibilities this brings to us.
But first, just in case you are unfamiliar with Wasm, we've got you covered! You can read our previous blog post where we discussed in-depth what WebAssembly (Wasm) is and why it might be a game changer in the future. If you want the short version, here is what Wasm brings to the table.
Advantages of Using WebAssembly
- Run anywhere (Portability): WebAssembly lets us write our code, compile it to Wasm bytecode then run it virtually anywhere. If you're a developer, you know what a hassle it can be to compile your app for multiple operating systems and CPU architectures. Now you don't have to worry about that. Write your code, compile to Wasm bytecode, get a .wasm file, run it anywhere.
- Security and isolation by default: Wasm bytecode runs in a virtual stack machine. This is something vaguely similar to a virtual machine. So you get isolation out-of-the-box. And there are a few awesome security features baked in as well.
- Super small Wasm images: When code is compiled, the resulting .wasm file containing the bytecode is very small. Much smaller than a typical container image.
- Fast startup times: Reduced file size, simplicity, lead to much faster startup times.
- Reduced resource usage: Since WebAssembly bytecode is usually contained in a very small file, executing that code requires less RAM, less disk space, and even less CPU time in some cases. This can significantly reduce infrastructure costs. You can simply run more stuff on the same infrastructure.
Will Wasm Explode in Popularity Now?
Wasm is very interesting from a technical perspective. But some people are still skeptical. They think WebAssembly has been around for a long time and nothing really changed dramatically. But that was the case for containers as well. You see, containers existed for a very long time, even before Docker. But only after Docker came along, container tech exploded in popularity. And this will probably be the case for Wasm as well. Something will come along, simplify things, and bring in a lot of new possibilities. And Docker seems to want to be that "something" again, but they just got started.
Will Wasm revolutionize things in the future? Time will tell. Now let's get to the main theme of this blog post.
What's New in Docker + Wasm Technical Preview 2?
The first technical preview paved the way for Wasm to enter the Docker scene. This adds some benefits to standalone WebAssembly. It is now much easier to package "Wasm container" images and then ship and run them on servers. An interesting use case is shipping Wasm workloads to Kubernetes clusters.
So what does the second technical preview add to the table?
If you've worked with Linux infrastructure and open-source for a long time, you'll know one great advantage of this world: freedom of choice! Everything is modular. You can replace components in your software infrastructure as you see fit. The default container runtime in Kubernetes not working well for your specific use case? You can pull out the old runtime and replace it with another one. The default filesystem in Ubuntu is not performing well enough with your workload? You can switch to another filesystem. Nothing is fixed. Everything can be modified, adapted, or replaced.
Three Additional Wasm Runtimes Supported
This brings us to a small problem with the first technical preview. As a way to introduce Wasm to Docker, it was an excellent software solution. But it had a small lock-in. You see, WebAssembly bytecode needs a runtime to be able to run. It's a bit like a "virtual computer" that is able to read Wasm bytecode and execute it.
The first technical preview supported a single runtime: WasmEdge. Is anything wrong with it? Not really. But we had no easy way to switch to something else if we needed to. And this is the biggest change in Docker + Wasm Technical Preview 2: the ability to use other runtimes. Support was added for three alternative runtimes:
If we count the initially supported WasmEdge, we now have four runtimes to choose from.
Effectively, Docker is making the runtime modular. This is similar to how you can replace the SSD in your laptop. If you find a model from another company that is faster, you can just remove the old SSD, and insert the new SSD in. So it seems that Docker wants to allow users to do the same thing with Wasm runtimes.
The runwasi Library
To have this ability to just plug in any Wasm runtime into Docker, the runwasi library was used. This introduces a few additional advantages, not just to Docker + Wasm, but also to running Wasm workloads on Kubernetes clusters. Let's see how.
First of all, let's go through "the politics" so to speak. The runwasi library was donated to the CNCF organization. As mentioned before, this helps a lot with neutrality, among other things. They do their best to guide the project in a direction that can benefit everyone. And since the organization's name includes the words "Cloud-Native" it also makes sure this remains… cloud native. What does this mean? Basically, this tool is kept as modular as possible. It's kept in a state that allows it to be easily interconnected with other pieces. So that we can plug it into any type of cloud infrastructure we may have. Since runwasi is modular/cloud native, it is compatible with a wide array of other cloud native tools, Kubernetes included. Now let's get to the technical part.
Why is runwasi needed in Docker? Well, think about the traditional way of running things. You type a docker command to start a container. It's now time for a software component to execute this job.
What Is a containerd shim and Why Is It Needed?
Docker delegates this task (starting the container) to something called a container runtime. In this case, it's containerd. But this is a high-level container runtime. It has to delegate the task of starting a container further along the chain. So it will contact a low-level container runtime called runc. Finally, runc will actually create and start the container. But did you notice an additional thing in the diagram above? That is, the containerd-shim. This does many things, but for the sake of simplicity we'll focus on just one aspect. It's a sort of "translation layer" that sits between containerd and runc in this case. In a nutshell, it's what connects containerd to runc. It sits in the middle, acting as the translator and the glue between these two components. It allows them to communicate and understand each other.
Why is such a shim needed? To allow things to be kept modular. For example imagine we'd want to use a different low-level container runtime. We could just pull runc out and replace it with something else. Notice the problem though? containerd has to know how to work with other runtimes. What commands to send them, how to communicate with them. But with the help of this shim, it does not matter. If we want to put something else "below" containerd, we can just add a shim in the middle. And the shim will make containerd and other runtimes below it understand each other. Just like a translator can help an American and a Chinese person communicate, even if they don't know each other's languages.
Phew! Long explanation. But it helps us understand the next part with ease.
Take a look at this image again:
Notice the highlighted part on the right. This time, containerd sends its commands to a different chain of software components. A different shim, and a different runtime, wasmedge. And a different result is achieved as well. Instead of starting a container, a Wasm module (program) is executed.
See how easy it is to replace runc because of this modular design? And the containerd-wasm-shim helps us glue the wasmedge runtime with containerd in this case. We don't even use a container runtime anymore, but rather, a WebAssembly runtime. Even though they're rather different, the containerd-wasm-shim sitting in the middle makes the integration possible. And runwasi is a library that helps with the programming side for such integrations between containerd, shims, and WebAssembly/Wasm runtimes. More specifically, it helps people program shims that sit between containerd and a Wasm runtime.
Runwasi Helps Wasm "Containers" Run on Kubernetes Too
Currently, the GitHub page for runwasi contains this text:
"This is a project to facilitate running wasm workloads managed by containerd either directly (ie. through ctr) or as directed by Kubelet via the CRI plugin."
Kubelet is a component in Kubernetes. And Kubelet can communicate with containerd to start containers. And since containerd can now tell WebAssembly runtimes to run Wasm bytecode instead, it means Kubernetes gets this ability as well.
Once you package a Wasm image for Docker, you can also run it in Kubernetes. So you get the same benefits you get with traditional containers. Package once, ship anywhere, deploy at massive scale.
How to Use Wasm Runtimes Under Docker
In this previous blog post we discussed how to use Wasm with Docker. This covered a complete scenario, from start to finish. In that blog you will find all of these things:
- How to Install Docker Desktop
- How to Compile C++ Code into Wasm Bytecode
- How to Package Wasm Bytecode into an OCI-Compliant Image
- And, what interests us here, How to Run Wasm Under Docker
If you want to use the commands below, make sure to download the second technical preview build of Docker from this page (if you cannot download build for Windows, see notes below). This is currently available ONLY in that build. But if you read this blog at a later date (later than summer of 2023 possibly), a normal build of Docker Desktop might already include these features. So there will be no need to download the technical preview anymore.
Possible Bugs You May Encounter in Docker + Wasm Technical Preview 2
The current technical build might have some bugs. It's technically "beta software". Which means it's very new, so it's not entirely polished. It will take time to fix all the bugs.
Bugs I've encountered when running this on a Linux operating system:
- First of all, the link provided on the Docker + Wasm Technical Preview 2 page seems to be broken for the Windows build. At the time of writing this blog, nothing happened when I clicked on that link. If you encounter the same issue, here's the fix: use this link instead to download Docker + Wasm Technical Preview 2 for Windows.
- Sometimes, after starting a Wasm container, it refused to exit normally. So the command hanged. I wasn't able to type anything else. Couldn't exit with CTRL+C either. Docker Desktop was not able to stop the container either.
- When trying to run Wasm under the spin runtime, I got an error. Seems to be a misconfiguration in Docker Desktop somewhere.
- When trying to run Wasm under the slight runtime, I got no result. Nothing happened, no text was displayed.
It's possible you might encounter none of these bugs in the technical build you get. But just in case you do, I've mentioned these so you know it's not an issue on your side. It's just a problem with this current technical preview, and most bugs will certainly be fixed in future builds.
Now let's continue.
To get started, first activate this feature to "Use containerd for pulling and storing images" then click on the "Apply & restart" button.
In the blog about how to use Wasm with Docker, we covered how to use the WasmEdge runtime included in the first Docker + Wasm technical preview. The command used was:
docker run --rm --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm32 sl1ck/wasm-test
But now that we have three additional runtimes available, how do we use those?
To run Wasm with the wasmtime runtime, we use this command:
docker run --rm --runtime=io.containerd.wasmtime.v1 --platform=wasi/wasm32 sl1ck/wasm-test
We can see that the key parameter here is:
So if we replace that highlighted keyword above, we can figure out how to use the other two runtimes.
To run Wasm bytecode under spin:
docker run --rm --runtime=io.containerd.spin.v1 --platform=wasi/wasm32 sl1ck/wasm-test
And to run Wasm bytecode under slight:
docker run --rm --runtime=io.containerd.slight.v1 --platform=wasi/wasm32 sl1ck/wasm-test
We hope this blog was helpful in understanding these latest changes. What do you think about the Docker + Wasm combo? Are you using Wasm in any way? Do you see any interesting use-cases for running WebAssembly workloads in Kubernetes clusters?