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.