diff --git a/.github/contributors.yaml b/.github/contributors.yaml index 8263669..0f5189b 100644 --- a/.github/contributors.yaml +++ b/.github/contributors.yaml @@ -80,3 +80,9 @@ users: praffq-dev: name: Prafful email: praffq.dev@gmail.com + jim-junior: + name: Beingana Jim Junior + email: jimjunior854@gmail.com + charma7: + name: Another test user + email: test@mail.com diff --git a/.github/workflows/add-git-trailers.yml b/.github/workflows/add-git-trailers.yml index 73d5f5f..b56fb83 100644 --- a/.github/workflows/add-git-trailers.yml +++ b/.github/workflows/add-git-trailers.yml @@ -1,12 +1,12 @@ name: Add Git Trailers to PR commits on: - workflow_call: - secrets: - GIT_CLONE_PAT: - required: false - URUNC_BOT_PRIVATE_KEY: - required: true + pull_request_review: + types: [submitted] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true permissions: contents: read @@ -14,13 +14,10 @@ permissions: jobs: git-trailers: name: Add Git Trailers - runs-on: ${{ matrix.runner }} - strategy: - matrix: - include: - - arch: amd64 - runner: ubuntu-22.04 - continue-on-error: true + if: >- + github.event.pull_request.base.ref == 'main' && + github.event.review.state == 'approved' + runs-on: ubuntu-22.04 permissions: contents: write pull-requests: write @@ -40,11 +37,6 @@ jobs: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: Append git trailers - uses: nubificus/git-trailers@8e08c91bb4c1fd9cb1ccbd9cc8029c31acf8da66 # feat_use_rebase - with: - user_info: .github/contributors.yaml - - name: Generate urunc-bot token id: generate-token uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 @@ -52,20 +44,16 @@ jobs: app-id: ${{ vars.URUNC_BOT_APP_ID }} private-key: ${{ secrets.URUNC_BOT_PRIVATE_KEY }} - - name: Set up Git - run: | - git config --global user.name "urunc-bot[bot]" - git config --global user.email "urunc-bot[bot]@users.noreply.github.com" - - name: Append git trailers - uses: nubificus/git-trailers@18fd322f3fbfd505b4de728974a4ac1f32f758a7 # feat_auto_merge + uses: nubificus/git-trailers@a7153bbbe525fca4cdb8e0eacaf771921b8f069b with: - user_info: .github/contributors.yaml + token: ${{ steps.generate-token.outputs.token }} + user-info: .github/contributors.yaml - name: Merge PR env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_URL: ${{ github.event.pull_request.html_url }} run: | - PR_URL=${{ github.event.pull_request.html_url }} - + sleep 5 # Wait for github to get updated with the push. Otherwise merge will fail gh pr merge "$PR_URL" --rebase --admin diff --git a/.github/workflows/pr-merge.yml b/.github/workflows/pr-merge.yml index 1b57dfd..c5109f1 100644 --- a/.github/workflows/pr-merge.yml +++ b/.github/workflows/pr-merge.yml @@ -4,6 +4,8 @@ on: pull_request_target: types: - closed + branches: + - 'main-pr*' permissions: contents: read @@ -11,8 +13,7 @@ permissions: jobs: add-trailers-and-merge: if: | - github.event.pull_request.merged == true && - startsWith(github.event.pull_request.base.ref, 'main-pr') + github.event.pull_request.merged == true runs-on: ubuntu-latest permissions: contents: write @@ -23,10 +24,9 @@ jobs: with: egress-policy: audit - - name: Set up Git - run: | - git config --global user.name "urunc-bot[bot]" - git config --global user.email "urunc-bot[bot]@users.noreply.github.com" + - name: Exit if PR is not rebaseable + if: ${{ github.event.pull_request.rebaseable != null && github.event.pull_request.rebaseable == false }} + run: exit 1 - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -42,16 +42,18 @@ jobs: private-key: ${{ secrets.URUNC_BOT_PRIVATE_KEY }} - name: Append git trailers - uses: nubificus/git-trailers@18fd322f3fbfd505b4de728974a4ac1f32f758a7 # feat_auto_merge + uses: nubificus/git-trailers@a7153bbbe525fca4cdb8e0eacaf771921b8f069b with: - user_info: .github/contributors.yaml + token: ${{ steps.generate-token.outputs.token }} + user-info: .github/contributors.yaml - name: Create a Pull Request from PR_BRANCH to main and merge it env: GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_BRANCH: ${{ github.event.pull_request.base.ref }} run: | PR_BRANCH=${{ github.event.pull_request.base.ref }} - + # Create the pull request PR_URL=$(gh pr create \ --head "$PR_BRANCH" \ diff --git a/.github/workflows/pr-trailers.yml b/.github/workflows/pr-trailers.yml deleted file mode 100644 index 82fbcde..0000000 --- a/.github/workflows/pr-trailers.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Add Git Trailers to PR commits - -on: - pull_request_review: - types: [submitted] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - git-trailers: - name: Add Git Trailers to PR commits - if: ${{ github.event.pull_request.base.ref == 'main' && github.event.review.state == 'approved' }} - uses: ./.github/workflows/add-git-trailers.yml - secrets: inherit diff --git a/README.md b/README.md index 3715bb8..78668fc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # urunc +This is only for testing. + [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10840/badge)](https://www.bestpractices.dev/projects/10840) Welcome to `urunc`, the "runc for unikernels". @@ -139,13 +141,14 @@ At the moment, `urunc` is available on GNU/Linux for x86\_64 and arm64 architect In addition, the following table provides an overview of the currently supported VM/Sandbox monitors and unikernels: -| Unikernel | VM/Sandbox Monitor | Arch | Storage | -|----------- |--------------------------- |------------- |------------------------------ | -| 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 | -| Mewz | QEMU | x86 | In-memory | -| Linux | QEMU, Firecracker | x86 | Initrd, Block/Devmapper, 9pfs, Virtiofs | +| Unikernel | VM/Sandbox Monitor | Arch | Storage | +| --------- | -------------------------- | ----------- | --------------------------------------- | +| 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 | +| Mewz | QEMU | x86 | In-memory | +| Linux | QEMU, Firecracker, clh | x86 | Initrd, Block/Devmapper, 9pfs, Virtiofs | +| Hermit | QEMU | x86 | Initrd | We plan to add support for more unikernel frameworks and other platforms too. Feel free to [contact](#Contact) us for a specific unikernel framework or similar diff --git a/docs/Sample-images.md b/docs/Sample-images.md index c8ed5f0..0dea15e 100644 --- a/docs/Sample-images.md +++ b/docs/Sample-images.md @@ -50,3 +50,4 @@ We plan to create and maintain multi-platform images soon, as well as enrich thi - harbor.nbfc.io/nubificus/urunc/whoami-firecracker-linux-initrd:latest - harbor.nbfc.io/nubificus/urunc/busybox-qemu-linux-raw:latest - harbor.nbfc.io/nubificus/urunc/busybox-firecracker-linux-raw:latest +- harbor.nbfc.io/nubificus/urunc/hello-world-qemu-hermit-initrd:latest diff --git a/docs/hypervisor-support.md b/docs/hypervisor-support.md index df349d7..3d3e02b 100644 --- a/docs/hypervisor-support.md +++ b/docs/hypervisor-support.md @@ -65,6 +65,7 @@ Supported unikernel frameworks with `urunc`: - [MirageOS](../unikernel-support#mirage) - [Mewz](../unikernel-support#mewz) - [Linux](../unikernel-support#linux) +- [Hermit](../unikernel-support#hermit) An example unikernel: diff --git a/docs/index.md b/docs/index.md index c9fc2e0..7dd2ec6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -60,6 +60,7 @@ Sandbox monitors, along with the unikernels that can run on top of them. | [MirageOS](./unikernel-support#mirage)| [Qemu](./hypervisor-support#qemu), [Solo5-hvt](./hypervisor-support#solo5-hvt), [Solo5-spt](./hypervisor-support#solo5-spt) | x86, aarch64 | Block/Devmapper | | [Mewz](./unikernel-support#mewz)| [Qemu](./hypervisor-support#qemu) | x86 | In-memory | | [Linux](./unikernel-support#linux)| [Qemu](./hypervisor-support#qemu), [Firecracker](./hypervisor-support#aws-firecracker) | x86, aarch64 | Initrd, Block/Devmapper, 9pfs, Virtiofs | +| [Hermit](./unikernel-support#hermit)| [Qemu](./hypervisor-support#qemu) | x86 | Initrd | diff --git a/docs/unikernel-support.md b/docs/unikernel-support.md index f0a8bd8..ef2c7b7 100644 --- a/docs/unikernel-support.md +++ b/docs/unikernel-support.md @@ -342,6 +342,45 @@ An example of a Redis alpine image transformed to a block file on top of sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/redis-firecracker-linux-block:latest ``` +## Hermit + +[Hermit](https://hermit-os.org/) is a unikernel designed for +high-performance and cloud/HPC workloads. +Its a Rust re-write of the Hermit-Core unikernel in order to leverage the +memory/thread-safety guarantees provided by the Rust ownership model. + +Hermit provides support for multiple compiled languages like Rust, C, C++, Go and Fotran. Hermit applications are compiled +into a unikernel image that includes both the application and the operating system. +When the unikernel boots, the application starts execution immediately. + +Hermit provides support for multithreading, networking, and basic system +interfaces. It is particularly well-suited for cloud and microservice +environments where lightweight isolation is required. + +### VMMs and other sandbox monitors + +Hermit runs on top of the [QEMU](https://www.qemu.org/) virtual machine monitor. +It supports standard virtualization interfaces such as virtio devices for networking. + +When executed with [QEMU](https://www.qemu.org/), Hermit can access the network through +a `virtio-net` device. Hermit supports both `virtio-fs` and `initrd` as storage options with the respective configuration at build time. + +### Hermit and `urunc` + +In the case of Hermit, `urunc` provides support for running unikernels on +top of QEMU. If the container is configured with network access, `urunc` will +attach a `virtio-net` device to enable networking for the unikernel. Hermit in `urunc` only supports `initrd` for storage. + +For more information on packaging +[Hermit](https://hermit-os.org/) unikernels for `urunc` take +a look at our [packaging](../package/) page. + +An example of running a Hermit unikernel with `urunc`: + +```bash +sudo nerdctl run --rm -ti --runtime io.containerd.urunc.v2 harbor.nbfc.io/nubificus/urunc/hello-world-qemu-hermit-initrd:latest +``` + ## Future unikernels and frameworks: In the near future, we plan to add support for the following frameworks: diff --git a/go.mod b/go.mod index fc5f362..2da9e6a 100644 --- a/go.mod +++ b/go.mod @@ -19,14 +19,14 @@ require ( github.com/opencontainers/runc v1.3.4 github.com/opencontainers/runtime-spec v1.2.1 github.com/prometheus-community/pro-bing v0.8.0 - github.com/rs/zerolog v1.35.0 + github.com/rs/zerolog v1.35.1 github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v3 v3.8.0 github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netns v0.0.5 golang.org/x/sys v0.43.0 - k8s.io/cri-api v0.35.3 + k8s.io/cri-api v0.35.4 ) require ( diff --git a/go.sum b/go.sum index 88f5192..08ef77e 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= -github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -230,8 +230,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= @@ -341,5 +341,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/cri-api v0.35.3 h1:gONTLBvK1eBPyveXEQ39mtTqi2oANeHj1mCo1YhQosI= -k8s.io/cri-api v0.35.3/go.mod h1:Cnt29u/tYl1Se1cBRL30uSZ/oJ5TaIp4sZm1xDLvcMc= +k8s.io/cri-api v0.35.4 h1:9/3Wj18YldMLADyiPoZGrdFHZ7miA8LVO2cjjWENPlk= +k8s.io/cri-api v0.35.4/go.mod h1:V7aEqk4QGvezHJYFCLGfTA+XqSkD6WoWTQdPirLLbFM= diff --git a/pkg/unikontainers/unikernels/hermit_rs.go b/pkg/unikontainers/unikernels/hermit_rs.go new file mode 100644 index 0000000..4047e5b --- /dev/null +++ b/pkg/unikontainers/unikernels/hermit_rs.go @@ -0,0 +1,125 @@ +// Copyright (c) 2023-2026, 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" + "runtime" + "strings" + + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +const HermitUnikernel string = "hermit" + +type Hermit struct { + Command string + Monitor string + Net HermitNet +} + +type HermitNet struct { + Address string + Mask int + Gateway string +} + +func (h *Hermit) CommandString() (string, error) { + var args []string + + if h.Net.Address != "" { + args = append(args, fmt.Sprintf("ip=%s/%d", h.Net.Address, h.Net.Mask)) + } + if h.Net.Gateway != "" { + args = append(args, fmt.Sprintf("gateway=%s", h.Net.Gateway)) + } + + // Add separator ONLY if we have net args AND a command + appArgs := strings.TrimSpace(h.Command) + if len(args) > 0 && appArgs != "" { + args = append(args, "--") + } + + if appArgs != "" { + args = append(args, appArgs) + } + + return strings.Join(args, " "), nil +} + +func (h *Hermit) SupportsBlock() bool { + return false +} + +func (h *Hermit) SupportsFS(fsType string) bool { + return fsType == "initrd" +} + +func (h *Hermit) MonitorNetCli(ifName string, mac string) string { + switch h.Monitor { + case "qemu": + netdev := fmt.Sprintf(" -netdev tap,id=net0,ifname=%s,script=no,downscript=no", ifName) + + var deviceArgs string + + // QEMU on x86_64 typically uses virtio-net-pci. + // On arm64 virtio-net-device is the safer default. + if runtime.GOARCH == "arm64" { + deviceArgs = " -device " + "virtio-net-device" + ",netdev=net0" + } else { + deviceArgs = " -device " + "virtio-net-pci" + ",netdev=net0,disable-legacy=on" + } + + if mac != "" { + deviceArgs += ",mac=" + mac + } + + return netdev + deviceArgs + default: + return "" + } +} + +func (h *Hermit) MonitorBlockCli() []types.MonitorBlockArgs { + return nil +} + +func (h *Hermit) MonitorCli() types.MonitorCliArgs { + return types.MonitorCliArgs{ + OtherArgs: " -no-reboot", + } +} + +func (h *Hermit) Init(data types.UnikernelParams) error { + + if data.Net.Mask != "" { + mask, err := subnetMaskToCIDR(data.Net.Mask) + if err != nil { + return err + } + h.Net.Address = data.Net.IP + h.Net.Gateway = data.Net.Gateway + h.Net.Mask = mask + } + + h.Command = strings.Join(data.CmdLine, " ") + h.Monitor = data.Monitor + + return nil +} + +func newHermit() *Hermit { + return new(Hermit) +} diff --git a/pkg/unikontainers/unikernels/unikernel.go b/pkg/unikontainers/unikernels/unikernel.go index 3832687..103c208 100644 --- a/pkg/unikontainers/unikernels/unikernel.go +++ b/pkg/unikontainers/unikernels/unikernel.go @@ -39,6 +39,9 @@ func New(unikernelType string) (types.Unikernel, error) { case LinuxUnikernel: unikernel := newLinux() return unikernel, nil + case HermitUnikernel: + unikernel := newHermit() + return unikernel, nil default: return nil, ErrNotSupportedUnikernel } diff --git a/tests/e2e/test_cases.go b/tests/e2e/test_cases.go index a910d52..31bec3d 100644 --- a/tests/e2e/test_cases.go +++ b/tests/e2e/test_cases.go @@ -733,6 +733,23 @@ func ctrTestCases() []containerTestArgs { ExpectOut: "UID: 0 GID: 42 WD: /test_dir URUNC: urunc", TestFunc: matchTest, }, + { + Image: "harbor.nbfc.io/nubificus/urunc/hello-world-qemu-hermit-initrd:latest", + Name: "Qemu-hermit-hello-world", + Devmapper: false, + Seccomp: true, + UID: 0, + GID: 0, + Groups: []int64{}, + Memory: "", + Cli: "", + Volumes: []containerVolume{}, + StaticNet: false, + SideContainers: []string{}, + Skippable: false, + ExpectOut: "Hello, world!", + TestFunc: matchTest, + }, } }