Debugging Unikernels using New Native TFS Tools

OPS recently gained native TFS dump support. TFS is the default filesystem for the Nanos unikernel. The new support allows you to run simple commands such as ls, cp, and tree directly on a Nanos image. You don't need FUSE and you can run this on a Mac too.

We had earlier made a TFS driver for Nanos so you can mount a disk image and work on it just like you would normally in a shell. This works well for both Mac and Linux.

Nanos ships with some tooling for image management namely, mkfs, and dump and they correspond more or less to the same sort of tools you'll find on Linux and the BSDs. Earlier this year we ported mkfs to Go from C so we could generate Nanos disk images directly in Go. This allowed some users to create Nanos unikernels even inside of another unikernel which is an interesting use-case. This recent support does the same thing but for dump. So it is essentially the reverse.

No Shelling out because There Isn't a Shell

It is important to understand how this is implemented. When you use 'ops image ls' or 'ops image cp' these commands aren't being ran in some sub-shell inside the vm of the image, because Nanos has no support for running multiple processes inside the vm by design. This precludes the possibility of a 'shell' that isn't loaded with pre-set commands. What is happening is that we've implemented versions of ls, cp, and tree to use directly on the disk image itself. Again - we didn't create programs of ls and cp as we can't run other processes inside Nanos and this tool doesn't need Nanos running regardless. What is going on is that there is now native TFS support to operate on the binary disk image itself -- something that would be normally found at the os level. This is fairly impressive and offhand I don't know of other tools that provide this style of functionality. If you do let me know in the comments of where this post gets posted.

We can play around with this functionality to see how it works. Here is a sample Go program:

package main

import "fmt"

func main() {
	fmt.Println("yo")
}

Now after we create it using 'ops run' we can see what the filesystem looks like after the run. If your application was creating log files and not shipping them to a syslog server or not throwing it out via stdout/stderr then it might be hard to obtain these logs but with these tools it's a piece of cake. You could imagine with live migration introspection can get very interesting very fast.

➜  g ops image ls g
proc lib etc g
➜  g ops image ls g lib
x86_64-linux-gnu
➜  g ops image ls g lib/x86_64-linux-gnu
libnss_dns.so.2

We could also tree the output form the redis package itself. There is a similar command called 'ops pkg contents' that would show similiar output for the package but this shows it for the image after it was built. A lot of applications will create temporary files and this is easy to see with this tooling.

➜  g ops image tree redis-server
/
|   lib64
|   |   ld-linux-x86-64.so.2
|   redis.conf
|   redis_5.0.5
|   |   package.manifest
|   |   redis-server
|   etc
|   |   passwd
|   |   resolv.conf
|   proc
|   |   sys
|   |   |   kernel
|   |   |   |   hostname
|   |   self
|   |   |   exe -> /redis_5.0.5/redis-server
|   lib
|   |   x86_64-linux-gnu
|   |   |   librt.so.1
|   |   |   libc.so.6
|   |   |   libdl.so.2
|   |   |   libm.so.6
|   |   |   libpthread.so.0

How about if we wanted to grab all those libraries in /lib/x86_64-linux-gnu ? Simple:


➜  g ops image cp -r redis-server lib .
➜  g ls
g       lib     main.go
➜  g ls lib
x86_64-linux-gnu
➜  g ls lib/x86_64-linux-gnu
libc.so.6       libdl.so.2      libm.so.6       libpthread.so.0
librt.so.1

This new suite of tooling is extremely powerful and versatile and will help you debug your unikernels in production when combined with vm image export apis found in cloud providers such as Google.

Deploy Your First Open Source Unikernel In Seconds

Get Started Now.