DockerCon
WebAssembly and Cloud Computing
Djordje Lukic, Staff Software Engineer, Docker
Transcript
Welcome to my talk about WebAssembly and cloud computing. My name is Djordje Lukic. I’m a software engineer at Docker. I work in the runtime team, so I work on Docker, on the demon, and on the CLI. And since last year, I’ve worked on the WebAssembly stuff. My co-speaker, Jorge Prendes, he’s unfortunately not here today, but he is also in the runtime team. He’s the Rust wizard, and he’s responsible for all the new runtimes we added recently in Docker Desktop.
Table of Contents
What is WebAssembly?
Let’s start simple. What is WebAssembly? I think it’s important to understand what WebAssembly is, where it came from, and why it’s so interesting these days. WebAssembly — or Wasm — I prefer to say WebAssembly because Wasm is kind of weird to say to me. So WebAssembly is a binary bytecode. Imagine the Java binary bytecode with the same “write once, run everywhere” promise, but then add 20 years of experience on top of it.
WebAssembly actually started as an experiment to compile C code to JavaScript so that you can run your C code inside the browser. So that kind of explains the web part of the name. As time went on, it evolved to be a binary bytecode that’s language agnostic. Today, you have a lot of languages that you can use that can compile to WebAssembly. WebAssembly is not a language; it’s not designed to be written or read by humans, but by compilers, so that’s kind of why we have the assembly name in the WebAssembly. WebAssembly was born for the web, and that gives it some interesting qualities that I think are quite nice.
Security
First, of course, we have security. WebAssembly runs in the browser. And, in the browser, we run a lot of untrusted third-party code, so you have to be able to run it safely. Also, basically, WebAssembly is basically like a glorified calculator. It can crunch numbers really well, and it can only do computations.
However, WebAssembly can’t interact with the external world. On its own, that wouldn’t be so interesting, so there are ways that you can add functions, host functions, that the runtime can give to the assembly module, so that the module can talk to the outside world. And, of course, the runtime, with every WebAssembly runtime, you can have very granular control over what you give to the module so that it can interact with the world.
Performance
The second one is performance. Performance is really also important on the web, because browsers are pretty much everywhere in TVs, in low-end phones. So, yeah, performance is paramount, and you always want things to be running smoothly.
WebAssembly is a quite compact binary format, and one cool feature about WebAssembly format is that you can start parsing and compiling it while you’re downloading it. You don’t need to wait to download the whole thing to start to compile it. Most of the runtimes will compile that to native code, so you will get near native speeds.
Size
Finally, size, no one likes 40-megabyte websites. The WebAssembly bytecode is made to be a very compact binary format, and you can compress it really well. So, if you look at all those things, you can say, hey, I want to have the same in my cloud or for any of my workloads. You want them to be fast, you want them to load quickly, and you want them to be secure. Does that mean that you need to throw out everything you have and start from scratch?
WebAssembly vs. containers
Basically the question is, are we in a big WebAssembly against containers battle? I don’t think so. I work at Docker, so I don’t think WebAssembly is here to kill the containers. What you should think about is when containers came in, we had VMs, back in the day. Also everyone loves drama, right? So, they were saying, oh, containers will kill VMs, and we can see today that’s actually not the case. Today, you use VMs to run containers in, and you can also use container tools that will help you run a VM, in which you will run a container. So, they coexist and work really well. I think it will be the same with WebAssembly. All the knowledge from the infrastructure parts and the distribution parts will help WebAssembly to be bigger. So, I don’t think WebAssembly will replace containers, but it will enable new workflows for everyone.
When to use WebAssembly?
I guess then the question is, when should you use WebAssembly, or when should you use containers? We tried to give some tips about when you choose one or the other. WebAssembly is kind of nice for short-lived, self-container logic. So, if you have some simple function that you can run, especially because most of the runtimes, their marketing will say, oh, sub-millisecond, startup times. So, if you have a cloud function, you can try out WebAssembly and see if that works for you.
One neat thing about WebAssembly is that it’s platform-independent. So, in the cloud today, you have AMD machines, you have Arm machines, and also RISC-V, that’s gaining popularity. So, if you want to run containers on all of those platforms, you need to compile for each of them. Whereas, with WebAssembly you can compile once, and you can run on all of those platforms. So, this is true, but not always true. I was talking with someone who works on WebAssembly, and they told me there are issues with some floating numbers, but it’s mostly true. So, at least I guess they will fix it in the future.
Next, WebAssembly at the core is very secure. It has a very secure sandbox, so if you need to run third-party code, maybe use WebAssembly. For example, Shopify recently made it so that you can run your plugins inside Shopify, and they run it as a WebAssembly module. And there’s also what’s the name of the game? Microsoft, airplane, what’s the flight simulator? Microsoft Flight Simulator also switched to WebAssembly to run the extensions for the game. And also, yeah, finally, if you look at Spin, and if you see that they have a feature that you want to try out, that you need, just use them.
And then containers — we know containers, so if you have a long-running process, maybe use a container. If you have a lot of interactions with the environment, like I said, WebAssembly on its own can’t talk to anyone, so if you need to talk to a lot of different services, databases, file systems, it’s still better to choose containers over WebAssembly. In the WebAssembly world, they’re working on it, but it’s not there yet, I think. And, if you already have a solution that works for you, just continue using it, right?
WebAssembly support in Docker Desktop
How does this all fit with Docker and Docker Desktop? So, I did talk earlier this year in Barcelona, where I deep-dived around that. Basically, what we’ve done is, yeah, you have Docker Hub, you have your container image that you can then use Docker and run your container. And with the work we’ve done, you can now do the same with WebAssembly modules. You can package a WebAssembly module in a WebAssembly image, I guess. And then Docker can pull it and run your WebAssembly module. That’s kind of an overly simplified overview of how it works, but it’s not that simple.
So, how can you get started today with it? Well, with Docker Desktop, it’s quite simple. You go into the settings, you have two checkboxes that you need to enable. One is for using containerd, for pulling in story images, and the other one is to enable WebAssembly.
Runtimes
If you enable WebAssembly, it will download all of these runtimes. These are, as of today, the only runtimes that exist and that have shims for it. A shim is like a program that Docker uses and gives the image, and then the shim will run the image. So, basically, we have those seven: Spin from Fermyon, Slight, and you can read all of those. And, last week, we released the 4.24 version of Docker Desktop, where we added the Lunatic one, and the Wasm Worker Servers one.
Why do we have that many different runtimes? Well, first, because having a choice is nice. And also, they are all not really the same. I can split them into two. There are the bare runtimes — a bare runtime, you give it to a module and it can run it, and then that’s it. So those are like Wasmtime, Wasmedge, and Wasmer. And then on the other side, you have things like Spin and Slight, which are more like framework runtimes. They do run your WebAssembly module, but then they add things on top of it. In Spin, for example, they have HTTP triggers. And, also recently, I think there’s some AI goodness in there. Then there’s Slight, which is kind of the same.
The Wasm Worker Servers (WWS) is a cool one. What they did is they compiled the language itself into a module, so they compiled Python into WebAssembly. And then they give to that module your Python code and then you can run. I’ll show it to you. It’s kind of nice. Finally, there’s Lunatic. We put it in a framework runtime list, but it’s kind of in the middle. It can run WebAssembly modules, but they added a bunch of concurrency model things. Check it out. I didn’t really have enough time to play with it, but it looks cool.
Demos
We are off to the demos now. All right. The first demo I want to show is a simple golang one. If you look at what I have here, it’s really a small demo. I don’t want to overcomplicate it. So it’s a simple golang program. We say, hey, DockerCon. And I just print the current OS and the current architecture. I can run this, for example, with Go. And, it says, hello, DockerCon. And of course, my OS is Darwin, because I’m on an OS on an Mac. And it’s Arm64 because I have an M1 CPU. And I think it was in Go 120 that they added the support for WebAssembly. Before that you had to use tiny Go. But today you can also use Go to compile your program into WebAssembly, so let’s see how to do that.
The way you cross-compile in Go is that you set, you define the GOOS and GOARCH when you build. So GOOS, in this case will be wasip1. And GOARCH will be wasip1. Maybe it’s the other way around. And then you say, go build. Let’s see, I will call it hello.wasm. Okay.
We have a WebAssembly module now. And I think, yeah, it works. We can, of course, run this. Yeah. The thing that changed here is that now it says, okay, my OS is Wasip1. Wasip1 means Wasi preview 1. And Wasi is the WebAssembly system interface, which is like the thing that gives WebAssembly the ability to read from the file system, from the environment, and a bunch of other stuff. And the architecture is Wasm. Let’s see how we can build the image with this module. Yeah. Nice.
Build a Wasm image
So, the way you build a WebAssembly image, this is one of the ways. You start from scratch, because the scratch image is an image that doesn’t contain anything. So we start from nothing, and then you can copy your Hello WebAssembly module inside the image. Then you say, okay, the entry point will be the module that I just put inside. Then to build, you just need to give it a platform, which is wasi/wasm, and give it a tag. And that will be it. But this isn’t really fun, because I mean, you’re building WebAssembly outside, and then you’re just putting them inside the image.
I think it’s better to use Docker to build everything. And the way you do that, it’s kind of more involved, but it’s not really that complicated. The first thing you need in order to compile a golang program, I need the golang image. The platform flag here is kind of new. What it means is that since I’m, as you saw, I’m building for the wasi/wasm platform, I need to tell Docker, okay, when you pull the golang image, pull the one that is for the current platform and not for the target platform, because golang wasi/wasm image doesn’t exist. So, you tell it to pull the Linux golang image, and then the normal, I need the work/dir, because golang always needs the work/dir. I copy all my code, and the same build command that I just did before in the terminal. And finally, again, start from scratch, pull in the module, and say, okay, this is my entry point. Let’s see how that works.
I have here my Dockerfile, and it’s the same thing that you saw here. We’re just kind of building it. Let’s see: “docker build — platform wasi/wasm -t dockercon”. Nice, it builds. I built my DockerCon image, and let’s see if we can run this. So, if I say “docker run dockercon”, it will say, oh, I don’t know what you’re talking about. It tried to pull the image but it didn’t find it. It didn’t find it, because when you just say “docker run” like this, Docker will use the current platform. But my image doesn’t exist for the current platform, because I made one for the wasi platform. So I need to say, okay, give me the dockercon, but I want the platform to be wasi/wasm.
Now it found the image, but there’s an error. The error is that Docker doesn’t know how to run a WebAssembly module. Because by default Docker is using runc, and runc only knows how to run Linux binaries. What you need to do is to tell it I want you to use another runtime when you run this module. So you do that with the “– runtime” flag and the image. So, in my case, this would be “docker run — platform wasi/wasm — runtime io.containerd.wasmtime.v1 dockercon”. And now if I run this, it works.
We’ve built a Docker image that contains a WebAssembly module and gave that to Docker and managed to run. This one is a mouthful. We know; we are trying to find different ways to make it easier for you to just run without having to know like io.containerd that I don’t know what some, I mean, it’s just insane to have this huge. We’re trying to find better ways. We’re not there yet. And just in case you’re wondering, these are all the names of the different runtimes that you can use. All of all the seven different shims you have. So, yeah, I can run it with wasmtime. I can run it with Lunatic, I think. Yeah, what’s the same. And, like I said, there are the bare runtimes and the framework run times, which means that if I try, for example, to run this one with Spin, nothing happens because Spin needs some configuration files and the Spin image will be a little bit different. I’ll show one example just after.
Other examples
I have other examples here. The golang one is an easy one because golang, on its own, has everything you need for it to compile into WebAssembly. But in C, and I guess other languages, I’m not sure. I know that Rust is also kind of easy to compile to WebAssembly. But in C, it’s kind of more involved. The way you would do it is the bytecode alliance created an image, the wasi SDK that contains everything you need to compile C code into WebAssembly.
When you have this image, there’s this $CC, which is C lang compiler that knows how to compile to WebAssembly. And then there’s the magic C flags that will add all the flags you need to compile to WebAssembly. And then you just say, okay, compile my main file. And I want the WebAssembly one. So let’s see if that works. It’s just a simple hello world, right? So it will be the same, it will be “docker build — platform wasi/wasm -t dockercon”. Let’s rebuild and, yeah, we can have the same name. OK, it builds. And let’s see if I can also run it with wasmedge this time. It worked! Sometimes, it’s not really straightforward, but there are a bunch of tools out there and more and more each day to help you to compile things to WebAssembly.
This is cute and all, but let’s see something a bit more complex. I have here a multiple service application. So, I’ll open this one real quick. In here, we have a Compose file, and our application contains three services. One is the client where we run an Nginx image. The server is like microservice, and this one will be WebAssembly container or workload. And then we have the database. If I say “docker compose up” here, it manages to open all of them. And, let’s see if this really works. If I come back here, localhost 8090, hey, I have my application working. So, we have three different containers here running. There’s a database one, WebAssembly one, which is the server, and then the frontend that you see here. Let’s see if it works. So I have to order: ID 12, 13, quantity, a lot, amount, no taxes, I don’t know, 12, shipping address, home. And we order, and, if I refresh, then, yeah, the frontend called the backend, which is WebAssembly module, and then we stored that in a database.
As of today, you can start to use WebAssembly things and put them inside your existing applications. You don’t need to reinvent the wheel. You can use the tools that you know and love. Awesome.
WWS demo
I also want to show you the WWS demo. This runtime is one of the more interesting ones. So like I said, the WWS, I think they compile Ruby, Python, JavaScript, and a bunch of other languages to WebAssembly. This image is kind of different. In there, you don’t put your module, but you put your source code, Python, and JS source code. And then you can use the WWS to install runtime.
Here I’m installing the Python runtime. There’s no way to install the JavaScript one, because it’s already pre-bundled with the WWS. So, we’re copying WWS, we’re copying some configuration files, and our source code. And, let’s just see quickly. When you use the WWS, you have listeners, like, they create HTTP responders. So, here basically we’re saying, okay, when someone calls us, I want to reply with something. In this case, we’ll just return some HTML. And the same for Python, you define a worker that will receive requests, and you return some HTML.
Let’s see how this works. I really think it’s interesting what I did. It’s kind of awesome to say, oh, hey, my language is compiled to WebAssembly. I’m going to build this one first. Oh, it’s downloading. Let’s wait a bit and hope that the Wi-Fi works. It does. Now if I run this. Okay. So it’s loading the two routes — the Python one and the JS one. Everything is loaded now. And, the port is 3000, and then hello JS, for example. So, if you call the hello JS route, we have the JavaScript file that runs in WebAssembly that responds. And if I say hello Python, we have the Python script that responds. So, your code isn’t compiled to WebAssembly, but your language is compiled to WebAssembly. Why not, right?
Fermyon demo
Okay, one last small demo, which is this one from our friends at Fermyon. They made the CMS for WebAssembly in Rust. Basically, if you go to any Fermyon page, it’s Bartholomew that responds. So, I took their starter and created a small little Dockerfile. I’m taking the Bartholomew WebAssembly, which is like the whole CMS, and giving it the same entry point. Like I said, Spin is like a framework more than just the WebAssembly runtime, so it needs some configuration.
Let’s see quickly in the configuration what it says. It says that this application has one component. This is the source, some ID, and it’s a CMS, so it needs some files in there. The WebAssembly module will be triggered for, I guess this means any route. So, it needs all those files. That’s why I’m copying all the files inside my image and also the WebAssembly module. For this one, we’re not going to use Docker. We are going to use Kubernetes to deploy it. Now, I am not at all an expert on Kubernetes, but I came up with this. It works. So, I’m creating a deployment and giving it a name. I have absolutely no idea what this does. Same for here. But, okay, I do understand this one. I want one container. I’m giving it a name. I want it to run my image that I built. I won’t rebuild this one, but this one is already on Hub, so you can also try to use it on your own machine. And this is the different thing you need.
When you want to run WebAssembly modules inside your Kubernetes, you need a special runtime class. It’s the same as with Docker when you say, oh, that’s a runtime, I want you to use this one. Here, I’m telling it, for this container, I want this runtime. And the way you install a runtime is another YAML file, I guess, with the runtime class kind. And it’s saying, when someone asks for a wasmtime spin, I want you to use the handler spin. I think I understand what is going on. Basically, the handler’s name is spin. And I guess containerd then will look for the io.containerd.spin.v1 runtime. I think that’s how containerd works, but again, I’m really not a Kubernetes expert.
Right, so let’s go inside. Before we started, I started a cluster. I have one cluster running locally, and you need some preset up also. If you want to test things out, this is an awesome tool that people created. It lets you install everything you need inside your Kubernetes cluster so that you can run WebAssembly things. You can use it pretty much anywhere.
So I have a Kind cluster already running, and I need to install the kwasm operator. So we can then apply my deployment here. Okay, it’s unchanged because I already added it. We can see that we have a deployment here. Our “bart”, and there’s one ready for us to test. Normally, I guess in Kubernetes, you also have to have a service or a load balancer, or I don’t know what, but I’m just going to use this “port-forward”. Yeah, the magical command line where I ask, okay, I want you to port-forward for the deployment bart, and I want you to deploy the port 80 where Bartholomew is listening to my local host on port 8080. We start the port forwarding, and we have our WebAssembly CMS. Thank you. We’re running in Kubernetes. Okay, we’re nearly there.
Join us
So, join us. If you see a runtime that you don’t see inside Docker Desktop, and you want it, come talk to us. If you want any feature about WebAssembly, we have a Docker roadmap repository on GitHub, where you can add issues. Also, there’s the runwasi project that we are working on with Microsoft and Intel and other companies. The runwasi project is actually the project that helps all the runtimes create the shims so that we can use them. So we are actively working on that, helping everyone. And also, we have a WebAssembly channel in our community Slack. Come and say hi.
Q&A
Thank you, everyone, for listening to me. We have some time for questions.
Thank you very much for the demo. One thing that I like about Docker is the layers. You have a huge project and you change an HTML file, only one line. It won’t build an entire Docker image. It will use just a layer. Here it seems like you are building a binary every time you build an application. Is that binary going to use layers in any way, or is it always going to be a huge binary every time you build an entire application?
That’s a great question. It’s something I didn’t talk about at all. If you look at this here, each of the copy commands creates a layer, and only the last layer has my module. So the way we create WebAssembly images today is the same as the normal container images. But there are some people who are working on something else. In the future, I think, maybe this year or maybe next year, there are WebAssembly components that are coming.
We are thinking about different ways we can package all of that. Maybe, instead of having layers for the WebAssembly components, maybe we’ll have them aside and then also have layers for like the actual code, be a different layer. Yeah, a special layer so that we can also remove it completely from the root filesystem of your image. Also, I saw there’s a bunch of things where you can virtualize your filesystem for WebAssembly. So you don’t even need your root filesystem. It can also be like another component that you run that gives you like a VFS. But it’s all pretty new, experimental, and we’re kind of working on that. But, today, you have the same things that you will know about layers and works also for WebAssembly.
Does compiling unsafe languages like C to WebAssembly solve some of the safety problems that they have through the security layer of WebAssembly? So, I would say, yes, because WebAssembly, it’s a sandbox. It’s just a calculator. It doesn’t do anything on its own. But then, of course, if you have a code that talks to your filesystem or to the network, well, there might be some issues. But, the cool thing is that you can really granularly say, okay, I want it to be able to access this and that. But, if you only have WebAssembly that, I don’t know, crunches some code, I think you’re way more secure than now.
Learn more
- Wasm workloads
- Get the latest release of Docker Desktop.
- Vote on what’s next! Check out our public roadmap.
- Have questions? The Docker community is here to help.
- New to Docker? Get started.
- Subscribe to the Docker Newsletter.
Find a subscription that’s right for you
Contact an expert today to find the perfect balance of collaboration, security, and support with a Docker subscription.