Conf42 Golang 2024 - Online

Go-ing Serverless with WebAssembly

Abstract

Small, Secure & Fast: WebAssembly (Wasm) transforms distributed architectures, expanding from browsers to servers and the cloud. Learn how TinyGo and Spin create high-speed serverless apps, empowering Go developers for future-proven development. Explore serverless Wasm and stay ahead in software.

Summary

  • Torsten is a cloud advocate with Fermion Technologies. In the upcoming 30 minutes we will take a look at what webassembly is, why it is so important, and then dive into the open source project spin. If you have any further questions, don't hesitate, just shoot me a mail after the talk.
  • Webassembly specifies a binary instruction format. Developers take or write code in any language that supports Webassembly. Runtimes are stack based virtual machines that can run our webassembly binaries. With webassembly we get near native performance, so apps are freaking fast.
  • Open source framework spin allows you to create new projects using different templates, to compile your code down to webassembly and to run apps locally. Instead of relying on core web assembly APIs, spin provides higher level APIs that make you more productive.
  • The spin SDK allows us to build more sophisticated apps. You can use all the goodness from the Go standard library, you can also bring in third party dependencies. We can create an encoder using JSON new encoder, and then use spin up to run the app.
  • A full fledged crud API is using a SqLite database to manage persisting items. The actual implementation for all the crud capabilities is encapsulated in the persistence model. We get better the dog mode which is active, full self driving capabilities and sentry mode as well.
  • Besides using spin cli to run your apps locally, you can also run those apps in Fermion Cloud. You can run it on Spincube which is an open source stack. Or if you want to go high density, then you can take Fermions platform for Kubernetes.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hi and thanks for tuning in here at Con 42 Golang and especially for joining my talk going so serverless with Webassembly. So without further ado, let me quickly bring up my slides and let's get started right, alright, who am I? My name is Torsten. I work as a cloud advocate with Fermion Technologies. The most important thing on this slide is my mail address. So if you have any further questions, don't hesitate, just shoot me a mail after the talk, after a conference, just reach out and we will sort things out. So in the upcoming 30 minutes we will take a look at what webassembly is, why it is so important, and then we will dive into the open source project spin and we'll do some hands on, right? So we'll start with a simple hello world, then we will extend that a little bit, and from there we will take a look at a full fledged crud API implementation. And finally we will take a look at now that we end up with a serverless app in webassembly that we've written in tiny go where can we run it and how do we do that? All right, so let's jump right into Webassembly. So what is webassembly? Maybe you've already heard the term web waSm, which is just an abbreviation for Webassembly, which ultimately specifies a binary instruction format. This means that we as developers take or write code, right, in any language that supports Webassembly, and we compile it down to that binary format, right? And then there are runtimes. Runtimes are stack based virtual machines that can run our webassembly binaries. There are many runtimes available. There are runtimes optimized for edge computing, optimized for cloud computing, general purpose runtimes. So there's a whole ecosystem when it comes to webassembly compatible runtime. So to say, we have a lot, really a lot of popular programming languages already supporting Webassembly. However, it's still a growing list of languages, and we hope to check more boxes in the upcoming months or so. Initially, WebAssembly has been invented for the browser, right? Because all our compute devices became beefier, they had more power, they could achieve more, right? So Webassembly allowed software vendors to take existing code, compile it instead to a processor architecture like X 64. They could compile it down to Webassembly and move that piece of code closer to the user and leverage their compute capabilities, right? So a popular example is, for example, Adobe Photoshop where a whole lot of algorithms were moved into the browser, which allows us as users to do photo editing right inside of the browser by leveraging webassembly under the covers. So that's why Webassembly was invented. However, the strict sandbox and the near native performance that we as developers got from adopting Webassembly was the key for Webassembly, you know, growing beyond the browser and becoming popular on the server and also in the cloud. Ultimately, at some point in time, Webassembly becomes for us as developers just a compilation target. And to illustrate that, let's have a look at a simple diagram, right? So we write code in any language, let's say tinygo, right? It supports webassembly. So we can instruct a tiny go compiler to compile our code down to webassembly, and we end up with a webassembly module or file with a WASM extension. So from there with our module, which is our executable or maybe shared library, whatever, right? We can take that wasm file and hand it over to a webassembly runtime, which is then responsible for loading the webassembly module, instantiating it and invoking its entry point or its exposed API, that is, you know, the desired one. Ultimately, webassembly gives us four main capabilities. So especially if you compare webassembly applications to traditional applications that we may be distribute in containers, our applications are way smaller, right? Because we have a binary instruction format. So in our distributable is just the app. So we don't have to ship a web server in order to get a web app from our, let's say Ci CD system into production, right? The module just consists of the app, which reduces the distributable size a lot, secure. So every webassembly application is instantiated and executed in a strict sandbox environment. We must specify which permissions or which capabilities our app is allowed to use. So by default, for example, a webassembly module is not able to read files from the local file system, right? We have to explicitly grant the permission to that app that it may read from the temp folder or that it may write to the user's home directory. So we have to explicitly define or specify the permissions or the intent for that app. With webassembly we get near native performance, right? So apps are freaking fast. And to me one of the most, besides, I would say besides secure, secure, it's the most interesting capability is the portability, right? We don't have to care about different operating systems or different processor architectures anymore. We write code, we compile it to webassembly, and it does not matter if we execute it on an x 64 or an ARM 64 architecture. We compile it once and we run it everywhere. So now that we have a common understanding of what webassembly is and why it is so important, I will, you know, introduce you to the open source framework or project spin. So spin, you can find it on GitHub@GitHub.com fermion spin. It mainly consists of three pillars, right? So we have developer tooling which ensures that you remain productive. It's a super simple yet powerful CLI that allows you to create new projects using different templates, to compile your code down to webassembly and to run apps locally. So it addresses all the concerns of the inner loop experience. The second pillar is a set of lightweight, language specific SDKs. So instead of relying on core web assembly APIs, spin provides higher level APIs that make you more productive at the end of the day, right? And last but not least, the spin itself is built on top on wasn't time. So the spin CLi, right? So it allows you to run your apps locally. So it's also a webassembly runtime, so to say, okay, so let's jump right into the terminal and let's get started. Started. So where's my terminal? There we go. So first let's do a spin version. So I'm right now on spin 2.42 and I have spin comes with a built in template engine. So I have installed some templates. There are multiple templates available. You can roll your own templates that the line bit more with your preferences if the default templates doesn't address your needs, right. What we will use right now is the HTTP go template to build a simple hello world so we can do a spin new set our template HTTP go. We accept the defaults because every template can ask you some questions to get additional information or metadata. But we will stick with defaults right now and we will call our app helloconf 42. Okay, so what do we get from that? Let's go. No, let's go into helloconf 42 and let's do a code dot. So we get. That looks familiar, right? So we get a go mod sum file, we get a main go file, but we also get a spin Tamil. And the spin toml is like the application manifest where we have all those application wide metadata where we can configure triggers. So triggers are like they specify the event that makes the runtime invoke our code. So in this case we have a simple HTTP trigger, listening to forward root route and everything behind that. And if a request hits that endpoint, we want to invoke the Helocon 42 component. But how do we get from source code to that component? So spin takes care of that. It doesn't matter if you do, if you use go, if you use JavaScript for different scenario, maybe typescript, maybe rust, whatever, right? You always follow the spin workflow and one command of the spin workflow. We just saw spin new, another one is spin build to compile the source code down to webassembly in order to be language agnostic. You can see the build command which is executed if you run spin go behind a certain, right, so we do a tiny go build, we target wasi, which is the webassembly system interface, and provide additional flags. We also have a spin, you also have a spin watch command so that you get hot reload if you're coding. But for now let's have a look at the implementation, right? So the skeleton looks like we have an init function. And yes, by convention you have to place your handler inside of the init func. And we use the primitives provided by go, right? So you have HTTP response writer, HTTP request, like all those primitives that you've been using for decade, for a decade right now. And we use that to set a response content, type HTTP header on the response and to write out hello Fermion. So let's write out hello Conf 42 in our case, and let's bring up the terminal inside of versus code, and let's do it the spin build. So this takes our source code uses tiny go to compile it down to Webassembly, and we end up with a waSm file over here. And if we open that anyway with the text editor, we see that is a binary format. So as spin is already a runtime, by leveraging wasm time, we can also do like a spin up, which starts the app on the local system. As you can see, the app is now available on port port 3000. So let's copy that URL. Let's open up a new terminal and let's curl this thing curl. There we go. And we get back hello Conf 42. So it's fairly easy to get started with webassembly using spin. Let's move back to the slides again. But I mean, we've seen hello world, right? So in the next step we will just enhance it a little bit. But then instead of you seeing me typing, we will explore a full fledged crud application and take it from there. So let's do some hands on, right? So instead of, you know, let me, let me quickly start the app again. Let's say spin up. There we go. So it does not matter what I, you know, do right here I can do a get with another path. I can do a post post over here. I always, I always see the same result. That's because in the spin toml we specified that we wanna listen to all routes, which is good. However, we just have that simple spin HTTP handle func over here. However, the spin SDK allows us to build more sophisticated apps. So for example, let's take the router, which is spin HTTP new router, and then we can say router get. We want to listen to a route which has a parameter, let's call that kind and we want to handle the get request by providing a dedicated handle func. So let's do a func handle get. And this quickly copied the parameters over there we get again the response writer request request. But in addition we get params which comes from the spin HTTP module. And finally, so we can register more routes over here. Register more routes, right, routes. But ultimately we must tell the spin or the, because the requests goes into that handle func, we must forward it to the router. So we can simply say hey router server, and we just forward the response writer and a request. So taking a look at our get handle get function, let's quickly come up with a new type say response model which is a struct struct. We have a message string which we want to represent in JSON as lowercase message. So what we now want to do is we want to extract the kind parameter specified in the route over here. So let's say k equals params by name and we say it's kind. So if the len of k is zero, then we can use the other primitives from, from the go as the standard library, right? We can say HTTP error and say bad request request and return a 400 just be safe. Or here, however, if we receive a kind, we want to construct a result which is a response model instance, and we set the message to k. So from here, what do we want to do? Well, we want to return JSON instead of plain text, which we did before, right? We can set, we can add a simple header called content type, set that one to application JSON. And now we have to take care about encoding the body right, so we can create an encoder using JSON new encoder, new encoder, forward the I o writer over here and finally say hey, please go encoder, encode the result in the case of any error, if error not equal nil, we want to HTTP error, let's say error while encoding message, payload or response message. And let's fail within HTTP 500. Let's return this thing. Ultimately we wanna keep it as it is. All right, so let's quickly double check the implementation. That looks good. We have used serv HTTP over here. So let's bring up the terminal and again cycle through the spin workflow. So spin new, create the app, spin build to compile it down to webassembly and then we will use spin up to run the app. So spin up, there we go. And let's clean this thing up and let's do a curl ix get HTTP. Oh, have I constructed a response message? I just put that there. No, I don't want that. Let's change that quickly. Let's say format as printf hello conf 42s like this and provide k as an argument over there. And let's do quickly. We can also simplify that. So we can do a spin up build over here so that compiles the app and immediately starts it on using spin up so we can say HTTP localhost 3000 Golang and we get back JSOn with a message property and its value set. Hello conf 42 golang. Great. So just demonstrating that you can use all the goodness from the Go standard library, you can also bring in third party dependencies. And to demonstrate that, let me quickly kill this one. And let's have a look at the crud sample that I wrong folder. Let's have a look at the crud sample crud code dot. So what do we have over here? So we have a full fledged crud API which is using a SQLite database to, you know, for persisting items that we can manage with that API. So we have to explicitly grant the permission to that particular app to use a sQlite database called default. And if we take a look at the go module, you can see I used external dependencies over here like the UUID module from, from Google and SQL to streamline my experience when interacting with Sqlite. So domain go file just takes the API package that I created over here. So there you can see that we use the spin HTTP router to create all the routes that we need to build full fledged crud API with all the handlers taking care about HTTP requests and translating or encoding maybe structs or slices of structs into JSON again. So the handlers take care about validating incoming messages and producing response responses. The actual implementation for all the crud APIs or crud capabilities is encapsulated in the persistence model over here module over here. So for example, if we look at creating a new item, we first create a new random, a new guid, it's a v four. In that case then we take the incoming item create model, right? Because we want to build a robust API. So we have dedicated models. So upon creating a new item, users may only specify the name and a boolean indicating if that particular item is active or not. However, in contrast, if you ask for a particular item, you will also get back an id. But it's our, let's say business logic, right to roll a new id for an item. So we take the incoming payload, we transform that into an item type and ultimately we use the spin SDK in the DB method over a function over here to open up the SQL lite connection. And then we use SQlex and say, hey, sql X, here's the connection. Please let or simplify or streamline the experience for interacting with that database in SQlite. And finally, if things went well, we close the connect, we defer closing the connection and we execute the query by providing our SQL statement and you know, using, providing all those different values for the different columns in our table. So the same is for update. And maybe we can have a look at the, reading the list of items, read items over there. So we do a simple query X, provide a select id name and query if the item is active or not. And then we use a struct scan from the SQL X from the SQL X package to construct structs from result rows. All right, how can we run this? So this leads to the no, first let's run it locally. Come on, let's do that. So we can do a spin build over here again that compiles our code down to webassembly. There we go. So from here we can simply run instead of running it just with spin up. We want to use our SqLite database and I have created a file called Migrations SQl which you know, create a table if not exists and pre populates some items. So with spin Clr you can simply say, hey, use that migrationstore file in order to see the database or to prepare the database. So let's invoke that. And as you can see we are running on localhost 3000 and let's, instead of curling everything, let's simply take a look at localhost 3000 items. And as you can see, we get better the dog mode which is active, full self driving capabilities that are active and sentry mode which is active as well. So we can take a look at sentry mode. It's active. Yes. So we can put, and instead of having this one activated, we can deactivate it. We just have to align with the schema which is that let's send it and let's read it again. So we say get. Give me that item again. You see, we get back false. And finally we can delete sentry mode from the list of all items we get back and no content. And we can look for the list of items again and we expect just two items being returned. Great. So let's quickly go back to the slides because we want to take a look at different contexts for running solus apps. So besides using spin cli to run your apps locally, you can also run those apps in Fermion Cloud, which is a fully managed cloud following a no ops pattern that you can use to run your apps. You can run it on Spincube which is an open source stack that allows you to run your spin amp next to your containers in any Kubernetes cluster. Or if you want to go high density, then you can also take Fermion platform for Kubernetes which is our commercial offering that you can use to run inside of Kubernetes and in a way denser mode as Spincube allows you to do. Okay, so let's have a look at Fermion cloud and open source spincube and let's move back to versus code for that. So I have already logged in into Fermi cloud. So basically that's a spin cloud login that I already did. But I can simply do a spin cloud deploy in the folder of the crud sample. Right? So now the app is packaged and uploaded to fermion cloud and Fermion cloud recognizes. Hey, you're using a database called default. Do you want to use an existing one in your cloud account or should we create a new one for you? Well, let's go with a new one. We can provide a name or accept a generated name which is affectionate melon. So let's use that one. And within a matter of seconds both the database is provisioned for us and the app is deployed into fermion cloud. There we go. So now that the app is deployed, we still have to, you know, provision or create the database and seed some data. For that you can use spin cloud SQlite list again to get the list of databases. And the affectionate melon is still there. So we can say spin cloud Sqlite execute database, provide a name for the database and provide the file migrations sql there we go. So we have executed everything. So we can take this URL, go back to Postman and just swap localhost 4000 with that one. And we should see full self driving and sentry mode being there. So we can grab sentry mode again and play around with that one next. So besides fermion cloud there is spincube. So let's create like a variable for representing the image. So we will use an OCI artifact for distributing our app. So the spin CLI allows us to do a spin registry push using our OCI artifact name. In that case I'm using TTL sh. So basically this OCi artifact will remain available for 24 hours. And in my Kubernetes cluster I have a SQL D running. So let's say get PO and SVC in the default namespace. And as you can see there's a SQL D running over there which is exposed at 8080, right with the name SQL D. So we can say we can change the runtime behavior of a spin app by providing runtime configuration file. And I said hey, with this config I can tell spin, hey if you want to use the default SQlite data, use LibSQL and you can find the database at HTTP sql D 8080, right? So we can use spin cube scaffold, say from the following OCi artifact. And by the way use the config file called Cube Toml. So right now we scaffold the Yaml Kubernetes deployment manifest to standard out. Because our intention is at this point our story should end. If you want to deploy your kubernetes workloads with kubectl, go for it. If you want to build handshots, go for it. If you want to use Gitops, go for it. So for demonstration purpose, let's say kget po again and kget spin app. So there's nothing in the cluster yet. So let's do the scaffold and this time we pipe it to kubectl and apply it immediately. Oops. Apply, apply f that one. And you see the spin app has been created. So if we do get spin up again, we see the app running in our cluster using container d shim for spin. And if we do a kget pod, you see we have two instances of the Golang crud running and we also have a service sitting in front of it, Golang crud over here. So what we will do next is Kubectl port forward. There we go. From my local machine port 8080 into the cluster port 80. There we go. And now let's go to this one, get rid of the ID and let's say HTTP localhost 8080 items. And you see there's just the full self driving so we can create a new one. Let's go there, there, let's say name, oops, name is the dog mode. And let's say active is obviously true. Now there's a column missing over there and let's hit send and we get back the iD. But if we just go back and say hey, give me all the items, we now have two items. And if we go finally once back to this, we saw two connections have been handled on localhost 8080 and forwarded to the service running inside of Kubernetes. So that being said, we want to wrap up this thing. So a couple of key takeaways, right? Webassembly will definitely change the way we build distributed apps because we can now build serverless applications in a reactive manner, right? No matter which language you use, as long as the language compiles down to Webassembly. And those apps are way more efficient as regular containers could be. So apps are fast, secure and portable by default. So we can get rid of, you know, all the pains and pitfalls that we may have with cross compilation, Fermion Cloud, Spincube and Fermion platform for Kubernetes allow us to, you know, run our serverless apps in different contexts. So maybe you have already Kubernetes in place then Spintube or Firmin platform for Kubernetes could be a great fit or great addition to your overall application landscape. So you can roll your own serverless platform. And once again we can use the languages that we love and know. And obviously we can also bring in all the goodness that others created to make that language so successful. With that being said, thank you very much for joining me on that journey to build serverless apps using Webassembly. You can find all the code online on GitHub. Thank you and enjoy the rest of conference.
...

Thorsten Hans

Senior Cloud Advocate @ Fermyon

Thorsten Hans's LinkedIn account Thorsten Hans's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways