Unikernels in OCaml

immuable

A static website

Kind
unikernel
Source
https://github.com/dinosaure/immuable
Homepage
https://robur.coop
Authors
Romain Calascibetta
Tags
webhttpswebsite

Immuable is one of the first unikernels we developed using our new workflow. The idea behind immuable is to offer the ability to serve content via the HTTP protocol at very little cost. It is therefore an easy-to-understand unikernel.

The unikernel is being developed in this GitHub repository. Users who are already familiar with OPAM can install it using this command:

$ opam pin add https://github.com/dinosaure/immuable.git

The project includes not only the unikernel that you can deploy, but also an executable that allows you to generate the archive required by the unikernel from a folder containing your static website.

Immuable ☓ YOCaml

To have a bit more fun, we're going to use the YOCaml project: it's a static HTML content generator. In other words, for those of you who dream of developing your own static blog system, YOCaml is a good solution. The aim here will therefore be not only to generate our website in OCaml, but also to deploy it in OCaml!

A little website

To put it quite simply, our aim will be to start with a CommonMark document and then generate HTML pages. YOCaml allows you to read files, transform them, and then generate HTML files into a specific directory (like _www).

open Yocaml

let www = Path.rel [ "_www" ]
let pages = Path.rel [ "pages" ]
let templates = Path.rel [ "templates" ]

let track_binary = 
  Sys.executable_name
  |> Yocaml.Path.from_string
  |> Pipeline.track_file

let with_ext exts file =
  List.exists (fun ext -> Path.has_extension ext file) exts

This is where we carry out our main transformation, in which we take a source and generate HTML, applying a template file (Jingoo).

let create_page source =
  let page_path =
    source
    |> Path.move ~into:www
    |> Path.change_extension "html"
  in
  let open Task in
  let read =
    let open Task in
    let+ () = track_binary

We are preparing our template, which is a transformation function based on information relating to an Archetype.Page.

    and+ apply = Yocaml_jingoo.read_templates
      Path.[ templates / "page.html" ]
    and+ metadata, content = Yocaml_yaml.Pipeline.read_file_with_metadata
      (module Archetype.Page) source in
    (apply (module Archetype.Page) ~metadata, content)
  in
  read

Here, we convert the CommonMark file to HTML.

  >>> Yocaml_cmarkit.content_to_html ()

We apply our template (the content will be available via yocaml_body).

  >>> rmap (fun (fn, content) -> fn content) id

And we save this to an HTML file.

  |> Action.Static.write_file page_path

Finally, we apply this transformation to all the *.md files in our pages folder.

let create_pages =
  let where = with_ext [ "md" ] in
  Batch.iter_files ~where pages create_page

let gen () =
  let open Eff in
  let cache = Path.(www / ".cache") in
  Action.restore_cache cache
  >>= create_pages
  >>= Action.store_cache cache

let () = Yocaml_unix.run ~level:`Debug gen

You can supplement this small programme with a dune file to compile it, as well as a templates/page.html file and a pages/index.md file:

dune:

(executable
 (name gen)
 (libraries yocaml yocaml_unix yocaml_yaml yocaml_cmarkit yocaml_jingoo))

pages/index.md:

---
page_title: My super website
description: My homepage
tags: [index]
---

# Hello World!

This is my super website as an unikernel!

templates/page.html:

<html>
<head>
<title>{{ page_title }}</title>
</head>
<body>
{{safe (yocaml_body) }} 
</body>
</html>

Generate & pack!

At this stage, our little executable gen.exe can now generate our website in the _www folder. If you’d like more details about YOCaml (how to generate pages, include static content, etc.), you can refer to this excellent tutorial. We'll now simply use immuable (which we've just installed via OPAM) to produce the archive required for our unikernel and launch our unikernel:

We generate our website using YOCaml.

$ dune exec ./gen.exe

We generate the archive from what YOCaml has generated.

$ immuable make -o pack.pack _www

We'll set up everything we need for our immuable unikernel (if you're unsure, we recommend reading our tutorial).

$ sudo ip tuntap add name tap0 mode tap
$ sudo ip link set tap0 up
$ sudo ip link add name service type bridge
$ sudo ip link set service up
$ sudo ip link set tap0 master service
$ sudo ip addr add 10.0.0.1/24 dev service

And we're launching our unikernel!

$ solo5-hvt --net:service=tap0 --block:immuable=pack.pack -- \
  $(which immuable.hvt) --ipv4=10.0.0.2/24 &
$ PID=$!
$ curl http://10.0.0.2/
<html>
<head>
<title>My super website</title>
</head>
<body>
<h1 id="hello-world">
  <a class="anchor" aria-hidden="true" href="#hello-world"></a>Hello World!
</h1>
<p>This is my super website as an unikernel!</p>
 
</body>
</html>
$ kill -9 $PID

There you go! As you can see, Immuable is quite simple because it provides both the unikernel and the tool for generating the archive. In this case, immuable does much more than simply pack the files; it also attempts to recognise the MIME type of the files so that the unikernel does not have to do this work: in other words, the MIME types of the files are pre-calculated so that the unikernel can send the correct Content-Type.

You can, in fact, test this recognition using immuable mime <file>. This recognition is also carried out in OCaml using the conan library.

$ immuable mime _www/index.html
text/html

Finally, to demonstrate just how powerful this approach is: the website is currently generated by YOCaml and is available via immuable!