Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ supported VM/Sandbox monitors and unikernels:
| Rumprun | Solo5-hvt, Solo5-spt | x86,aarch64 | Block/Devmapper |
| Unikraft | QEMU, Firecracker | x86 | Initrd, 9pfs |
| MirageOS | QEMU, Solo5-hvt, Solo5-spt | x86,aarch64 | Block/Devmapper |
| IncludeOS | QEMU, Solo5-hvt, Solo5-spt | x86,aarch64 | Block/Devmapper |
| Mewz | QEMU | x86 | In-memory |
| Linux | QEMU, Firecracker | x86 | Initrd, Block/Devmapper, 9pfs, Virtiofs |

Expand Down
64 changes: 62 additions & 2 deletions docs/unikernel-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ the cloud-native ecosystem. For that reason, `urunc` aims to support all the
available unikernel frameworks and similar technologies.

For the time being, `urunc` provides support for
[Unikraft](https://unikraft.org/) and
[Rumprun](https://github.com/cloudkernels/rumprun) unikernels.
[Unikraft](https://unikraft.org/),
[Rumprun](https://github.com/cloudkernels/rumprun) and
[IncludeOS](https://github.com/includeos/IncludeOS)
unikernels.

## Unikraft

Expand Down Expand Up @@ -275,6 +277,64 @@ sudo nerdctl run -m 512M --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.i

> Note: As far as we understand, Mewz requires at least 512M of memory to properly boot.

## IncludeOS

[IncludeOS](https://github.com/includeos/IncludeOS) is a unikernel framework
written in C++, designed for building fast, secure, and resource-efficient
cloud applications. [IncludeOS](https://github.com/includeos/IncludeOS) enables
developers to write C++ applications that compile directly into bootable
unikernels, eliminating the need for a traditional operating system.
[IncludeOS](https://github.com/includeos/IncludeOS) is particularly well-suited
for web services and cloud-native applications, providing a minimal attack
surface and excellent performance characteristics.

[IncludeOS](https://github.com/includeos/IncludeOS) provides a modern C++
standard library implementation and includes networking capabilities with TCP/IP
stack support. The framework focuses on simplicity and performance, making it
ideal for microservices and cloud functions.

### VMMs and other sandbox monitors

[IncludeOS](https://github.com/includeos/IncludeOS) can execute on top of
various hypervisors and monitors. It supports execution on
[Qemu](https://www.qemu.org/) and can also run on top of
[Solo5](https://github.com/Solo5/solo5), which provides portability across
different virtualization backends. [IncludeOS](https://github.com/includeos/IncludeOS)
can access the network through virtio-net in the case of
[Qemu](https://qemu.org) and using [Solo5](https://github.com/Solo5/solo5)'s
I/O interface in the case of [Solo5](https://github.com/Solo5/solo5). For
storage, [IncludeOS](https://github.com/includeos/IncludeOS) supports
block-based storage through virtio-block and
[Solo5](https://github.com/Solo5/solo5)'s I/O interface.

### IncludeOS and `urunc`

In the case of [IncludeOS](https://github.com/includeos/IncludeOS), `urunc`
provides support for both [Qemu](https://www.qemu.org/) and
[Solo5](https://github.com/Solo5/solo5) monitors (including
[Solo5-hvt](https://github.com/Solo5/solo5) and
[Solo5-spt](https://github.com/Solo5/solo5)). For all monitors, `urunc` allows
access to both network and block storage through the respective monitor's I/O
interface.

For more information on packaging
[IncludeOS](https://github.com/includeos/IncludeOS) unikernels for `urunc`,
take a look at our [packaging](../package/) page.

An example of [IncludeOS](https://github.com/includeos/IncludeOS) on top of
[Solo5-hvt](https://github.com/Solo5/solo5) with `urunc`:

```bash
sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/includeos-hvt:latest
```

An example of [IncludeOS](https://github.com/includeos/IncludeOS) on top of
[Qemu](https://qemu.org) with `urunc`:

```bash
sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/includeos-qemu:latest
```

## Linux

[Linux](https://github.com/torvalds/linux) is maybe the most widely used kernel
Expand Down
171 changes: 171 additions & 0 deletions pkg/unikontainers/unikernels/includeos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// Copyright (c) 2023-2025, Nubificus LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package unikernels

import (
"fmt"
"strings"

"github.com/urunc-dev/urunc/pkg/unikontainers/types"
)

const IncludeOSUnikernel string = "includeos"

type IncludeOS struct {
Command string
Monitor string
Envs []string
Net IncludeOSNet
Block []IncludeOSBlock
}

type IncludeOSNet struct {
Address string
Gateway string
Mask string
}

type IncludeOSBlock struct {
ID string
HostPath string
}

func (i *IncludeOS) CommandString() (string, error) {
cmdParts := []string{}

// IncludeOS expects network config as a JSON string argument.
// We construct this manually to ensure it matches the specific schema required by the OS.
if i.Net.Address != "" {
// Default to iface 0.
// Constructing JSON: {"net":[{"iface":0,"address":"...","netmask":"...","gateway":"..."}]}
gwPart := ""
if i.Net.Gateway != "" {
gwPart = fmt.Sprintf(`,"gateway":"%s"`, i.Net.Gateway)
}

// We assume i.Net.Mask is in dotted-decimal format (e.g 255.255.255.0)
// If urunc provides CIDR conversion logic would be needed here
jsonConfig := fmt.Sprintf(
`{"net":[{"iface":0,"address":"%s","netmask":"%s"%s}]}`,
i.Net.Address,
i.Net.Mask,
gwPart,
)
cmdParts = append(cmdParts, jsonConfig)
}

if len(i.Envs) > 0 {
cmdParts = append(cmdParts, i.Envs...)
}

if i.Command != "" {
cmdParts = append(cmdParts, i.Command)
}

return strings.Join(cmdParts, " "), nil
}

func (i *IncludeOS) SupportsBlock() bool {
return true
}

func (i *IncludeOS) SupportsFS(fsType string) bool {
switch fsType {
case "ext2", "ext3", "ext4":
return true
default:
return false
}
}

func (i *IncludeOS) MonitorNetCli(ifName string, mac string) string {
switch i.Monitor {
case "hvt", "spt":
// Solo5 monitor options for networking
netOption := "--net:service=" + ifName
netOption += " --net-mac:service=" + mac
return netOption
case "qemu":
// QEMU handles networking through its own options in the hypervisor layer
return ""
default:
return ""
}
}

func (i *IncludeOS) MonitorBlockCli() []types.MonitorBlockArgs {
if len(i.Block) == 0 {
return nil
}

switch i.Monitor {
case "hvt", "spt":
// Solo5 monitors support block devices with specific IDs.
// Note: Solo5 typically supports a single block device.
blockArgs := make([]types.MonitorBlockArgs, 0, len(i.Block))
for _, blk := range i.Block {
id := blk.ID
if id == "" {
id = "storage"
}
blockArgs = append(blockArgs, types.MonitorBlockArgs{
ID: id,
Path: blk.HostPath,
})
}
// Return only the first block device to ensure compatibility with Solo5
if len(blockArgs) > 0 {
return blockArgs[:1]
}
return blockArgs
case "qemu":
// QEMU handles block devices through its own options
return nil
default:
return nil
}
}

func (i *IncludeOS) MonitorCli() types.MonitorCliArgs {
// IncludeOS does not require any generic monitor-specific arguments
return types.MonitorCliArgs{}
}

func (i *IncludeOS) Init(data types.UnikernelParams) error {
if data.Net.Mask != "" {
i.Net.Address = data.Net.IP
i.Net.Gateway = data.Net.Gateway
i.Net.Mask = data.Net.Mask
}

i.Block = make([]IncludeOSBlock, 0, len(data.Block))
for _, blk := range data.Block {
newBlk := IncludeOSBlock{
ID: blk.ID,
HostPath: blk.Source,
}
i.Block = append(i.Block, newBlk)
}

i.Command = strings.Join(data.CmdLine, " ")
i.Monitor = data.Monitor
i.Envs = data.EnvVars

return nil
}

func newIncludeOS() *IncludeOS {
return &IncludeOS{}
}
88 changes: 88 additions & 0 deletions pkg/unikontainers/unikernels/includeos_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2023-2025, Nubificus LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package unikernels

import (
"strings"
"testing"
)

func TestIncludeOS_CommandString(t *testing.T) {
tests := []struct {
name string
unikernel *IncludeOS
expected []string
}{
{
name: "Full Configuration",
unikernel: &IncludeOS{
Command: "my_app_arg",
Envs: []string{"FOO=bar", "BAZ=qux"},
Net: IncludeOSNet{
Address: "192.168.1.5",
Mask: "255.255.255.0",
Gateway: "192.168.1.1",
},
},
expected: []string{
`{"net":[{"iface":0,"address":"192.168.1.5","netmask":"255.255.255.0","gateway":"192.168.1.1"}]}`,
"FOO=bar",
"BAZ=qux",
"my_app_arg",
},
},
{
name: "Network Only (No Gateway)",
unikernel: &IncludeOS{
Net: IncludeOSNet{
Address: "10.0.0.2",
Mask: "255.255.0.0",
},
},
expected: []string{
`{"net":[{"iface":0,"address":"10.0.0.2","netmask":"255.255.0.0"}]}`,
},
},
{
name: "No Network (Command Only)",
unikernel: &IncludeOS{
Command: "just_running",
},
expected: []string{
"just_running",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.unikernel.CommandString()
if err != nil {
t.Fatalf("CommandString() error = %v", err)
}

for _, part := range tt.expected {
if !strings.Contains(got, part) {
t.Errorf("CommandString() = %v\nMissing expected part: %v", got, part)
}
}

if tt.name == "No Network (Command Only)" {
if strings.Contains(got, "net") {
t.Errorf("CommandString() should not contain network config, got: %v", got)
}
}
})
}
}
3 changes: 3 additions & 0 deletions pkg/unikontainers/unikernels/unikernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ func New(unikernelType string) (types.Unikernel, error) {
case LinuxUnikernel:
unikernel := newLinux()
return unikernel, nil
case IncludeOSUnikernel:
unikernel := newIncludeOS()
return unikernel, nil
default:
return nil, ErrNotSupportedUnikernel
}
Expand Down
Loading