Running Go Unikernels

After running my first c example in rumprun a few years ago the next language I wanted to try out was Go. Unfortunately there were no Go unikernels at the time so we sponsored one for rumprun. That was 3 years ago.

When we decided to start writing our own unikernel implementation a while back ago Go was again the very first thing after simple C programs we wanted to support. Not only do we use the latest Go releases but we've spent a lot of time ensuring we play nicely with the Go codebase as it has design characteristics that are not as common in other languages. Not only that but the Go team is continuously refactoring the underlying code from release to release to the result that if all you are touching is the API it exposes than you are fine but if you are mucking around at the syscall barrier like we are things can easily and often break. So it's a great bellweather on how the underlying kernel is doing.

Anyways, enough with history - let's run your first Go unikernel!

I'm assuming that you already have Go installed and working and that you've written some Go code before - if not I'd encourage you to check out this first. First thing you need to do is download OPS:
curl https://ops.city/get.sh -sSfL | sh
You can also build from the source if you prefer. This will install OPS the unikernel builder and orchestator. Now let's create a working directory:
mkdir testing && cd testing
Put this int a file called main.go:
package main

  import "fmt"

  func main() {
      fmt.Println("Hello World!")
  }
If you are on mac you'll want to specify the GOOS:
GOOS=linux go build main.go
but if you're on linux you can just invoke go manually:
go build main.go
The reason we do this is because we want an ELF file which is the native Linux binary format. You can verify this via:
➜  testing  ls -lh testing
-rwxr-xr-x  1 eyberg  staff   1.8M Feb 25 10:18 testing
➜  testing  file testing
testing: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically
linked, not stripped
Now let's run it!
➜  testing  ops run testing
Finding dependent shared libs
booting /Users/eyberg/.ops/images/testing.img ...
assigned: 10.0.2.15
Hello World!
exit_group
exit status 1
Hooray! It worked but want to see something cool?
➜  testing  ls -lh ~/.ops/images/testing.img
-rw-r--r--  1 eyberg  staff   4.4M Feb 25 10:21
/Users/eyberg/.ops/images/testing.img
That's a 4.4Mb virtual machine with nothing in it except for your Go program. This isn't just alpine linux in a docker container - this is *only* your go application with the barest essentials to get it working - built for you to run in the cloud. Ok - ready for a larger example? Let's build a basic Go webserver as a unikernel:

  package main

  import (
      "log"
      "net/http"
  )

  func main() {
      fs := http.FileServer(http.Dir("static"))
      http.Handle("/", fs)

      log.Println("Listening...on 8080")
      http.ListenAndServe(":8080", nil)
  }
Build it the same way:
GOOS=linux go build main.go
Now create a directory called static and throw this sample html in it:
hello from inside some html
Now let's put some declarations inside a config.json:
{  "Dirs" : ["static"] }
Now let's run it:
➜  testing  ops run -p 8080 -c config.json testing
Finding dependent shared libs
booting /Users/eyberg/.ops/images/testing.img ...
assigned: 10.0.2.15
2019/02/25 18:49:49 Listening...on 8080
➜  nvm-web git:(master) ✗ curl -XGET http://127.0.0.1:8080/
hello from inside some html
Alright - cool! You just ran your first go webserver as a unikernel!

Deploy Your First Open Source Unikernel In Seconds

Get Started Now.