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
15 changes: 15 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ jobs:
run: |
go version
- name: Install libkrun dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential git autoconf automake libtool pkg-config
- name: Build and install libkrun
run: |
git clone --depth 1 --branch v1.17.0 https://github.com/containers/libkrun.git
cd libkrun
./autogen.sh
./configure --prefix=/usr/local
make
sudo make install
cd ..
- name: Get revision SHA and branch (safe)
id: get-rev
env:
Expand Down
16 changes: 16 additions & 0 deletions deployment/urunc-deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
FROM debian:bullseye@sha256:25c0cab214b810db1b3c8adef5a12a92596979abddf86bb364e8d9c9d111df9f AS solo5-builder

# Remove libc-bin files to avoid segmentation fault.
FROM debian:bullseye@sha256:25c0cab214b810db1b3c8adef5a12a92596979abddf86bb364e8d9c9d111df9f AS krun-builder
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential git autoconf automake libtool pkg-config && \
rm -rf /var/lib/apt/lists/*
WORKDIR /krun
# Build and install libkrun from source
RUN git clone --depth 1 --branch v2.4.0 https://github.com/containers/libkrun.git . && \
./autogen.sh && \
./configure --prefix=/usr/local && \
make && \
make install
# See more: https://stackoverflow.com/questions/78105004/docker-build-fails-because-unable-to-install-libc-bins
RUN rm -f /var/lib/dpkg/info/libc-bin.* && \
apt-get clean && \
Expand All @@ -41,6 +53,10 @@ RUN cp /app/tenders/hvt/solo5-hvt /artifacts/ && \
FROM golang:1.24.6-alpine3.21@sha256:50f8a10a46c0c26b5b816a80314f1999196c44c3e3571f41026b061339c29db6 AS urunc-builder
RUN apk update && \
apk add --no-cache git make build-base linux-headers
# Copy libkrun headers and libraries from krun-builder
COPY --from=krun-builder /usr/local/include/libkrun.h /usr/local/include/
COPY --from=krun-builder /usr/local/lib/libkrun* /usr/local/lib/
COPY --from=krun-builder /usr/local/lib64/libkrun* /usr/local/lib64/ || true
WORKDIR /app
ARG REPO=urunc-dev/urunc
ARG BRANCH=main
Expand Down
218 changes: 218 additions & 0 deletions pkg/unikontainers/hypervisors/krun.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// 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 hypervisors

/*
#cgo LDFLAGS: -L/usr/local/lib64 -lkrun
#cgo CFLAGS: -I/usr/local/include
#include <libkrun.h>
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"os"
"strings"
"unsafe"

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

const (
KrunVmm VmmType = "libkrun"
KrunBinary string = "libkrun"
)

type Krun struct {
binaryPath string
binary string
}

// Stop kills the libkrun VM process
func (k *Krun) Stop(pid int) error {
return killProcess(pid)
}

// UsesKVM returns true as libkrun uses KVM
func (k *Krun) UsesKVM() bool {
return true
}

// SupportsSharedfs returns false as libkrun has limited device support
func (k *Krun) SupportsSharedfs(_ string) bool {
return false
}

// Path returns the path to libkrun
func (k *Krun) Path() string {
return k.binaryPath
}

// Ok checks if libkrun is available
func (k *Krun) Ok() error {
// Check if libkrun.so is loadable by attempting to create a context
ctxID := C.krun_create_ctx()
if ctxID < 0 {
return ErrVMMNotInstalled
}
C.krun_free_ctx(C.uint(ctxID))
return nil
}

func (k *Krun) Execve(args types.ExecArgs, ukernel types.Unikernel) error {
vmmLog.Debug("Starting libkrun VM configuration")

// Create libkrun context
ctxID := C.krun_create_ctx()
if ctxID < 0 {
return fmt.Errorf("krun_create_ctx failed with error code: %d", ctxID)
}
defer C.krun_free_ctx(C.uint(ctxID))

// Set VM config (memory and vCPUs)
ramMiB := C.uint(args.MemSizeB / (1024 * 1024))
if ramMiB == 0 {
ramMiB = C.uint(DefaultMemory)
}
numVCPUs := C.uchar(args.VCPUs)
if numVCPUs == 0 {
numVCPUs = 1
}

ret := C.krun_set_vm_config(C.uint(ctxID), numVCPUs, ramMiB)
if ret < 0 {
return fmt.Errorf("krun_set_vm_config failed with error code: %d", ret)
}
vmmLog.Debugf("Set VM config: %d vCPUs, %d MiB RAM", numVCPUs, ramMiB)

// Set root filesystem from sharedfs (if available)
if args.Sharedfs.Path != "" {
cRoot := C.CString(args.Sharedfs.Path)
defer C.free(unsafe.Pointer(cRoot))
ret = C.krun_set_root(C.uint(ctxID), cRoot)
if ret < 0 {
return fmt.Errorf("krun_set_root failed with error code: %d", ret)
}
vmmLog.Debugf("Set root: %s", args.Sharedfs.Path)
}

// Add kernel if provided
if args.UnikernelPath != "" {
cKernel := C.CString(args.UnikernelPath)
defer C.free(unsafe.Pointer(cKernel))

// Set kernel with optional initrd and command line
var cInitrd *C.char
if args.InitrdPath != "" {
cInitrd = C.CString(args.InitrdPath)
defer C.free(unsafe.Pointer(cInitrd))
}

// Build command line from args.Command (which is a string)
var cCmdline *C.char
if args.Command != "" {
cCmdline = C.CString(args.Command)
defer C.free(unsafe.Pointer(cCmdline))
}

// kernel_format: 0 for default/auto-detect
ret = C.krun_set_kernel(C.uint(ctxID), cKernel, 0, cInitrd, cCmdline)
if ret < 0 {
return fmt.Errorf("krun_set_kernel failed with error code: %d", ret)
}
if args.InitrdPath != "" {
vmmLog.Debugf("Set kernel: %s, initrd: %s", args.UnikernelPath, args.InitrdPath)
} else {
vmmLog.Debugf("Set kernel: %s", args.UnikernelPath)
}
}

// Add block devices
blockArgs := ukernel.MonitorBlockCli()
for _, blockArg := range blockArgs {
cBlockID := C.CString(blockArg.ID)
cBlockPath := C.CString(blockArg.Path)
defer C.free(unsafe.Pointer(cBlockID))
defer C.free(unsafe.Pointer(cBlockPath))

ret = C.krun_add_disk(C.uint(ctxID), cBlockID, cBlockPath, C.bool(false))
if ret < 0 {
return fmt.Errorf("krun_add_disk failed for %s with error code: %d", blockArg.ID, ret)
}
vmmLog.Debugf("Added block device: %s -> %s", blockArg.ID, blockArg.Path)
}

// Configure networking if tap device provided
if args.Net.TapDev != "" {
cTapDev := C.CString(args.Net.TapDev)
defer C.free(unsafe.Pointer(cTapDev))

// krun_add_net_tap takes (ctx_id, tap_name, mac, features, flags)
// Pass nil for mac to use default, 0 for features/flags
ret = C.krun_add_net_tap(C.uint(ctxID), cTapDev, nil, 0, 0)
if ret < 0 {
return fmt.Errorf("krun_add_net_tap failed with error code: %d", ret)
}
vmmLog.Debugf("Set network tap device: %s", args.Net.TapDev)

// Set MAC address if provided
if args.Net.MAC != "" {
// Parse MAC address to byte array
macStr := strings.ReplaceAll(args.Net.MAC, ":", "")
if len(macStr) == 12 {
var macBytes [6]C.uint8_t
for i := 0; i < 6; i++ {
var b byte
fmt.Sscanf(macStr[i*2:i*2+2], "%02x", &b)
macBytes[i] = C.uint8_t(b)
}
ret = C.krun_set_net_mac(C.uint(ctxID), &macBytes[0])
if ret < 0 {
return fmt.Errorf("krun_set_net_mac failed with error code: %d", ret)
}
vmmLog.Debugf("Set network MAC: %s", args.Net.MAC)
}
}
}

// Set environment variables
// krun_set_env expects a null-terminated array of C strings
if len(args.Environment) > 0 {
// Create null-terminated array of environment variables
cEnv := make([]*C.char, len(args.Environment)+1)
for i, env := range args.Environment {
cEnv[i] = C.CString(env)
defer C.free(unsafe.Pointer(cEnv[i]))
}
cEnv[len(args.Environment)] = nil // null-terminate

ret = C.krun_set_env(C.uint(ctxID), &cEnv[0])
if ret < 0 {
vmmLog.Warnf("krun_set_env failed with error code: %d", ret)
}
}

// Start the VM
vmmLog.Debug("Starting libkrun VM")
ret = C.krun_start_enter(C.uint(ctxID))
if ret < 0 {
return fmt.Errorf("krun_start_enter failed with error code: %d", ret)
}

// krun_start_enter blocks until VM exits
vmmLog.Debug("libkrun VM exited")
os.Exit(0)
return nil
}
4 changes: 4 additions & 0 deletions pkg/unikontainers/hypervisors/vmm.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ var vmmFactories = map[VmmType]VMMFactory{
binary: FirecrackerBinary,
createFunc: func(binary, binaryPath string) types.VMM { return &Firecracker{binary: binary, binaryPath: binaryPath} },
},
KrunVmm: {
binary: KrunBinary,
createFunc: func(binary, binaryPath string) types.VMM { return &Krun{binary: binary, binaryPath: binaryPath} },
},
}

func NewVMM(vmmType VmmType, monitors map[string]types.MonitorConfig) (vmm types.VMM, err error) {
Expand Down
1 change: 1 addition & 0 deletions pkg/unikontainers/urunc_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func defaultMonitorsConfig() map[string]types.MonitorConfig {
"hvt": {DefaultMemoryMB: 256, DefaultVCPUs: 1},
"spt": {DefaultMemoryMB: 256, DefaultVCPUs: 1},
"firecracker": {DefaultMemoryMB: 256, DefaultVCPUs: 1},
"libkrun": {DefaultMemoryMB: 256, DefaultVCPUs: 1},
}
}

Expand Down
18 changes: 18 additions & 0 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,21 @@ func TestDocker(t *testing.T) {
})
}
}

func TestKrun(t *testing.T) {
kvmGroup, err := getKVMGroupID()
if err != nil {
t.Errorf("Failed to get KVM group id")
}
tests := krunTestCases()

for i := range tests {
tests[i].Groups = []int64{kvmGroup}
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
nerdctlTool := newNerdctlTool(tc)
runTest(nerdctlTool, t)
})
}
}
54 changes: 54 additions & 0 deletions tests/e2e/test_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -1157,3 +1157,57 @@ func dockerTestCases(kvmGroup ...int64) []containerTestArgs {
},
}
}

func krunTestCases() []containerTestArgs {
return []containerTestArgs{
{
Image: "harbor.nbfc.io/nubificus/urunc/hello-krun-unikraft:latest",
Name: "Krun-unikraft-hello",
Devmapper: false,
Seccomp: true,
UID: 0,
GID: 0,
Groups: []int64{},
Memory: "256M",
Cli: "",
Volumes: []containerVolume{},
StaticNet: false,
SideContainers: []string{},
Skippable: true, // Skip if libkrun images not available
ExpectOut: "Hello world",
TestFunc: matchTest,
},
{
Image: "harbor.nbfc.io/nubificus/urunc/redis-krun-unikraft:latest",
Name: "Krun-unikraft-redis",
Devmapper: false,
Seccomp: true,
UID: 0,
GID: 0,
Groups: []int64{},
Memory: "512M",
Cli: "",
Volumes: []containerVolume{},
StaticNet: false,
SideContainers: []string{},
Skippable: true, // Skip if libkrun images not available
TestFunc: pingTest,
},
{
Image: "harbor.nbfc.io/nubificus/urunc/nginx-krun-unikraft:latest",
Name: "Krun-unikraft-nginx",
Devmapper: false,
Seccomp: true,
UID: 0,
GID: 0,
Groups: []int64{},
Memory: "512M",
Cli: "",
Volumes: []containerVolume{},
StaticNet: false,
SideContainers: []string{},
Skippable: true, // Skip if libkrun images not available
TestFunc: httpGetTest,
},
}
}