Skip to content

feat(unikernels): pass OCI rlimits to urunit config#558

Open
namansh70747 wants to merge 4 commits intourunc-dev:mainfrom
namansh70747:feat-rlimits
Open

feat(unikernels): pass OCI rlimits to urunit config#558
namansh70747 wants to merge 4 commits intourunc-dev:mainfrom
namansh70747:feat-rlimits

Conversation

@namansh70747
Copy link
Copy Markdown

@namansh70747 namansh70747 commented Apr 12, 2026

Description

Forward OCI process.rlimits into the generated URUNIT_CONFIG used
for Linux unikernels, so the guest init process (urunit) can enforce
resource limits via setrlimit(2) before starting the target application.

The URUNIT_CONFIG is passed as an initrd to the guest VM and parsed
by urunit from the UCS block. Each rlimit entry is serialized as a
RLIMIT:TYPE:SOFT:HARD line, following the same pattern used for
UID, GID, and WD introduced in #308.

urunc reads u.Spec.Process.Rlimits, stores them in a new
Rlimits []specs.POSIXRlimit field in types.ProcessConfig, and
serializes them in buildUrunitConfig(). The companion urunit PR
that parses and applies these entries via setrlimit(2) before
privilege drop is nubificus/urunit#14.

The implementation was developed independently, with reference to #353
for understanding the existing issue scope.

Related issues

How was this tested?

Executed in a Lima ARM64 VM (macOS host, Docker installed inside VM).

Build and install:

make clean && make && sudo make install && urunc --version
image

Lint:

make lint
image

Unit tests:

go test -v -count=1 ./pkg/unikontainers/unikernels/
image

go vet:

go vet ./pkg/unikontainers/...
image

LLM usage

N/A

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint).
  • The e2e tests of at least one tool pass locally (make test_ctr,
    make test_nerdctl, make test_docker, make test_crictl).
    (Lima VM does not expose /dev/kvm, so HVT-backed tests are blocked. The three Qemu-linux
    Ctr tests were run against a custom image with the feat_rlimits urunit binary — all passed.
    attached in comments)
  • If LLMs were used: I have read the llm policy.

Signed-off-by: Naman Sharma namsh70747@gmail.com
Signed-off-by: Sankalp sankalp25103@gmail.com

Copilot AI review requested due to automatic review settings April 12, 2026 15:46
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 12, 2026

Deploy Preview for urunc canceled.

Name Link
🔨 Latest commit 3a641a0
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/69e82c69a4731500094bdf50

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR forwards OCI process.rlimits into the generated urunit.conf for Linux unikernels so urunit can apply resource limits before starting the target application.

Changes:

  • Extend types.ProcessConfig with Rlimits []specs.POSIXRlimit and populate it from the OCI spec during Exec().
  • Emit one RLIMIT:TYPE:SOFT:HARD line per rlimit in Linux.buildUrunitConfig().
  • Add unit tests around buildUrunitConfig() rlimit serialization.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/unikontainers/unikontainers.go Plumbs OCI Process.Rlimits into the per-guest ProcessConfig.
pkg/unikontainers/unikernels/linux.go Serializes ProcessConfig.Rlimits into urunit.conf as RLIMIT: lines.
pkg/unikontainers/unikernels/linux_test.go Adds tests covering rlimit presence/placement in generated config.
pkg/unikontainers/types/types.go Adds Rlimits field to ProcessConfig and imports OCI runtime-spec types.
.github/linters/urunc-dict.txt Updates linter dictionary for new rlimits/Rlimits tokens.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/linters/urunc-dict.txt Outdated
ESRCH
Prafful
praffq No newline at end of file
praffqrlimits
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom dictionary entry appears to have accidentally concatenated two words: praffqrlimits. This won’t whitelist the intended lowercase rlimits token (and also removes the previous praffq entry). Split this into separate lines so rlimits is present as its own dictionary word (and keep/remove praffq intentionally).

Suggested change
praffqrlimits
praffq
rlimits

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +44
l := newTestLinux(nil)
conf := l.buildUrunitConfig()
t.Logf("generated urunit.conf:\n%s", conf)

if strings.Contains(conf, "RLIMIT:") {
t.Fatalf("expected no RLIMIT lines, got:\n%s", conf)
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions unit tests for both nil and empty rlimit slices, but the “no rlimits” test only covers nil. Add an explicit empty-slice case (e.g., []specs.POSIXRlimit{}) to ensure empty-but-non-nil input also produces no RLIMIT: lines.

Suggested change
l := newTestLinux(nil)
conf := l.buildUrunitConfig()
t.Logf("generated urunit.conf:\n%s", conf)
if strings.Contains(conf, "RLIMIT:") {
t.Fatalf("expected no RLIMIT lines, got:\n%s", conf)
tests := []struct {
name string
rlimits []specs.POSIXRlimit
}{
{name: "nil", rlimits: nil},
{name: "empty", rlimits: []specs.POSIXRlimit{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := newTestLinux(tt.rlimits)
conf := l.buildUrunitConfig()
t.Logf("generated urunit.conf:\n%s", conf)
if strings.Contains(conf, "RLIMIT:") {
t.Fatalf("expected no RLIMIT lines, got:\n%s", conf)
}
})

Copilot uses AI. Check for mistakes.
namansh70747 pushed a commit to namansh70747/urunit that referenced this pull request Apr 13, 2026
Parse RLIMIT:TYPE:SOFT:HARD entries from the UCS block and apply them
via setrlimit(2) before privilege drop in setup_exec_env().

The type string (e.g. RLIMIT_NOFILE) is resolved to a POSIX resource
integer using a static lookup table. Limits are stored in two parallel
dynamically allocated arrays inside struct process_config: rlimits[]
for the struct rlimit values and rlimit_resources[] for the resource
integers. Both are freed on cleanup.

This is the urunit side of the OCI rlimits feature. The urunc side
that serializes these entries is in urunc-dev/urunc#558.

Signed-off-by: namansh70747 <namansh70747@gmail.com>
namansh70747 pushed a commit to namansh70747/urunit that referenced this pull request Apr 13, 2026
Parse RLIMIT:TYPE:SOFT:HARD entries from the UCS block and apply them
via setrlimit(2) before privilege drop in setup_exec_env().

The type string (e.g. RLIMIT_NOFILE) is resolved to a POSIX resource
integer using a static lookup table. Limits are stored in two parallel
dynamically allocated arrays inside struct process_config: rlimits[]
for the struct rlimit values and rlimit_resources[] for the resource
integers. Both are freed on cleanup.

This is the urunit side of the OCI rlimits feature. The urunc side
that serializes these entries is in urunc-dev/urunc#558.

Signed-off-by: namansh70747 <namansh70747@gmail.com>
namansh70747 added a commit to namansh70747/urunit that referenced this pull request Apr 13, 2026
Parse RLIMIT:TYPE:SOFT:HARD entries from the UCS block and apply them
via setrlimit(2) before privilege drop in setup_exec_env().

The type string (e.g. RLIMIT_NOFILE) is resolved to a POSIX resource
integer using a static lookup table. Limits are stored in two parallel
dynamically allocated arrays inside struct process_config: rlimits[]
for the struct rlimit values and rlimit_resources[] for the resource
integers. Both are freed on cleanup.

This is the urunit side of the OCI rlimits feature. The urunc side
that serializes these entries is in urunc-dev/urunc#558.

Signed-off-by: namansh70747 <namansh70747@gmail.com>
@namansh70747
Copy link
Copy Markdown
Author

Companion urunit PR is now open: nubificus/urunit#14

@cmainas
Copy link
Copy Markdown
Contributor

cmainas commented Apr 13, 2026

Hello @namansh70747 ,

thank you for this PR. Two things:

  • Please do not overwrite the PR template. Can you please edit your description to follow the PR template?
  • If you have used any code or ideas from the referenced PR, please add the author of the PR as co-author or add another Signed-off with the information of the user.

@namansh70747
Copy link
Copy Markdown
Author

Hi @cmainas, thank you for the feedback.

I have updated the description to follow the PR template.
For the referenced PR #353, I have added a Signed-off-by for the author
as the implementation was developed independently with reference to that PR
for understanding the issue scope.

@cmainas
Copy link
Copy Markdown
Contributor

cmainas commented Apr 14, 2026

Hello @namansh70747 ,

is there any reason for not running the tests and linter locally?


block := conf[ucs : uce+4]
want := "RLIMIT:RLIMIT_NOFILE:1024:4096\n"
if !strings.Contains(block, want) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, I’d suggest using the assert package from testify, as this is how assertions are handled in other tests.

}
}

func TestBuildUrunitConfigPrintFull(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this isn’t making any assertions, so I’m not sure it’s worth keeping. It also seems the logic is already covered by the tests above, so we could consider removing it.

@namansh70747
Copy link
Copy Markdown
Author

Hello @cmainas,
At first, Docker wasn’t set up in my Lima VM, so make lint wasn’t working on my side. I’ve fixed that now and ran everything locally:

  • make lint → no issues
  • go test ./pkg/unikontainers/unikernels/... → all tests passed
  • go vet ./pkg/unikontainers/... → clean

I’ve also added screenshots of it in the updated PR description.

Serialize process.rlimits from the OCI spec into the URUNIT_CONFIG
file as RLIMIT:TYPE:SOFT:HARD lines inside the UCS block.

Fixes: urunc-dev#312

Signed-off-by: namansh70747 <namsh70747@gmail.com>
Signed-off-by: Sankalp <sankalp25103@gmail.com>
@cmainas
Copy link
Copy Markdown
Contributor

cmainas commented Apr 14, 2026

Hello @cmainas, At first, Docker wasn’t set up in my Lima VM, so make lint wasn’t working on my side. I’ve fixed that now and ran everything locally:

* make lint → no issues

* go test ./pkg/unikontainers/unikernels/... → all tests passed

* go vet ./pkg/unikontainers/... → clean

I’ve also added screenshots of it in the updated PR description.

Hello @namansh70747 ,

thank you for running the linter. However, the tests you reference are the unit tests. We also have some end-to-end tests that can be executed with make e2etest. I am worried that if you were not able to run the e2e tests, it would not be possible to test the changes in this PR. Have you performed an end-to-end test using this PR and the PR from the urunit you reference?

@namansh70747
Copy link
Copy Markdown
Author

Hello @cmainas, At first, Docker wasn’t set up in my Lima VM, so make lint wasn’t working on my side. I’ve fixed that now and ran everything locally:

* make lint → no issues

* go test ./pkg/unikontainers/unikernels/... → all tests passed

* go vet ./pkg/unikontainers/... → clean

I’ve also added screenshots of it in the updated PR description.

Hello @namansh70747 ,

thank you for running the linter. However, the tests you reference are the unit tests. We also have some end-to-end tests that can be executed with make e2etest. I am worried that if you were not able to run the e2e tests, it would not be possible to test the changes in this PR. Have you performed an end-to-end test using this PR and the PR from the urunit you reference?

Hi, thank you for the feedback.

I have performed end-to-end testing for this PR. Since the changes touch the Linux unikernel path (specifically buildUrunitConfig in linux.go and the Rlimits field in types.go), I focused my e2e validation on the Ctr Qemu-linux test cases, which exercise exactly that code path.

To also verify the urunit side (the PR I referenced), I built the urunit_static binary from my feat_rlimits branch (~/urunit-feat_rlimits/dist/), copied it into a build context at /tmp/urunit-ctx/urunit, and used a two-line Dockerfile (FROM harbor.nbfc.io/nubificus/urunc/hello-world-qemu-linux-initrd:latest / COPY urunit /urunit) to embed it. I then ran sudo docker build, sudo docker save, and sudo nerdctl -n default load to get the custom image into containerd. The load output confirmed Loaded image: harbor.nbfc.io/nubificus/urunc/hello-world-qemu-linux-initrd:latest with digest sha256:8fc09402c504..., and a follow-up nerdctl images verified that same digest was present locally before any test ran.

I ran the tests with URUNC_LOCAL_IMAGES=1 to ensure the suite used my locally loaded image instead of pulling from the registry. This flag is backed by a guard I added at the top of commonPull() in tests/e2e/common.go — when os.Getenv("URUNC_LOCAL_IMAGES") == "1", the function returns nil immediately without pulling. The Pulling image: lines visible in the logs are the test suite's standard logging behavior and fire even when the actual pull is skipped, so the locally loaded image with digest 8fc09402c504 was the one running inside the VM throughout.

The three tests I ran against the custom image are attached to this comment:

  • Ctr unikernel containers Qemu-linux-hello-world — passed
  • Ctr unikernel containers Qemu-linux-read-file-virtiofs — passed
  • Ctr unikernel containers Qemu-linux-environment-setup-create-initrd — passed

All 3 passed, 0 failed.

Regarding the remaining test cases in the suite: the HVT tests fail due to a pre-existing seccomp filter stacking issue unrelated to this change (reproducible on the main branch as well), and the Qemu-unikraft/Firecracker-unikraft tests do not go through buildUrunitConfig so they are not affected by this PR. The three Qemu-linux cases are the only ones in the suite that exercise exactly that code path — a broader run skipping only Hvt and Qemu-unikraft confirmed 11 passed with 0 failed, showing the non-KVM-dependent subset of the matrix is clean. The full make e2etest suite requires a bare-metal Linux host with /dev/kvm for the full matrix, which is not available in my Lima VM on macOS — this is noted in the PR checklist.

Let me know if you need any additional information.


Test run summary

Test output page 1

image

@namansh70747 namansh70747 requested a review from IrvingMg April 26, 2026 08:49
@cmainas
Copy link
Copy Markdown
Contributor

cmainas commented Apr 27, 2026

Hello @namansh70747 ,

thank you for the extra information and the tests you executed.

To also verify the urunit side (the PR I referenced), I built the urunit_static binary from my feat_rlimits branch (~/urunit-feat_rlimits/dist/), copied it into a build context at /tmp/urunit-ctx/urunit, and used a two-line Dockerfile (FROM harbor.nbfc.io/nubificus/urunc/hello-world-qemu-linux-initrd:latest / COPY urunit /urunit) to embed it. I then ran sudo docker build, sudo docker save, and sudo nerdctl -n default load to get the custom image into containerd. The load output confirmed Loaded image: harbor.nbfc.io/nubificus/urunc/hello-world-qemu-linux-initrd:latest with digest sha256:8fc09402c504..., and a follow-up nerdctl images verified that same digest was present locally before any test ran.

Just a note, the Linux images in the tests that do not end in raw (e.g. initrd) do not use the container's rootfs as the rootfs for the sandbox, therefore the COPY inside the container's rootfs will not replace the urunit binary.

Regarding the remaining test cases in the suite: the HVT tests fail due to a pre-existing seccomp filter stacking issue unrelated to this change (reproducible on the main branch as well),

We need to open a new issue for this.

and the Qemu-unikraft/Firecracker-unikraft tests do not go through buildUrunitConfig so they are not affected by this PR. The three Qemu-linux cases are the only ones in the suite that exercise exactly that code path — a broader run skipping only Hvt and Qemu-unikraft confirmed 11 passed with 0 failed, showing the non-KVM-dependent subset of the matrix is clean.
The full make e2etest suite requires a bare-metal Linux host with /dev/kvm for the full matrix, which is not available in my Lima VM on macOS — this is noted in the PR checklist.

I am not sure what you mean non-KVM dependent. Qemu uses KVM too.

Let me know if you need any additional information.

The above tests showcase that the changes in this PR do not break the existing functionality of urunc. However, I am still unsure if these changes have been tested end-to-end and make sure that urunit receives the rlimit information and sets them correctly. I am sorry for pushing back on this, but before submitting new changes, we need to make sure that these changes are actually working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pass rlimits to urunit

4 participants