Skip to content

exec: Run commands inside a running instance - #18

Merged
congwang-mk merged 10 commits into
mainfrom
kerf-exec
Sep 27, 2026
Merged

congwang-mk merged 10 commits into
mainfrom
kerf-exec

Conversation

@congwang-mk

@congwang-mk congwang-mk commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

kerf exec now runs a command inside a running instance, the way docker exec runs one inside a container. The old boot verb becomes kerf start, matching docker start.

kerf exec web-server ls /
kerf exec -it web-server sh
kerf exec -u nobody web-server id

Design

  • No new agent. kerf-init, already PID 1 in every --image/--rootfs-dir spawn, listens on AF_VSOCK port 1023 over the multikernel transport. It forks one session process per connection, so the relay never runs inside PID 1.
  • Trust: only a peer at CID 0 on a reserved port (<= 1023) is accepted, which means root on the host.
  • Protocol: one stream per exec with length-prefixed frames: OPEN, stdio, RESIZE, SIGNAL, STARTED/ERROR/EXIT. Stdin is flow-controlled with STDIN_ACK credits (64 KiB window), so signals and disconnects are never stuck behind stdin the command is not reading.
  • Users follow kata: -u names are resolved on the host against the instance rootfs, and only numeric uid/gid/groups go to the spawn. Without -u no credential syscalls are made, so CONFIG_MULTIUSER=n spawn kernels work.
  • Differences from docker: without -t, signals are forwarded to the command, and a host disconnect sends SIGHUP to the command's process group.

Testing

  • The C session is also built into a session-test harness (not shipped), so CI drives it over a socketpair. This covers pipes, tty and resize, signals, disconnect, concurrency, flow control, background writers and malformed requests.
  • python3 -m pytest -q: 403 passed, 1 skipped (the -u switch test needs root). pylint is clean.

kerf exec is about to run a command inside a running instance, the way
docker exec does. Booting an instance becomes kerf start, matching
docker start.

Signed-off-by: Cong Wang <cwang@multikernel.io>
One vsock stream per exec carries length-prefixed frames: OPEN with
argv, env, cwd, tty and credentials, then stdio, resize and signal
frames, and STARTED, ERROR or EXIT back.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Like containerd for kata, names are looked up in the rootfs the host has
mounted and only numbers go to the spawn, so kerf-init never parses
passwd or group files.

Signed-off-by: Cong Wang <cwang@multikernel.io>
A session reads OPEN, forks the command with a pty or pipes, relays
stdio as frames with backpressure, forwards resize and signals, and
reports EXIT or ERROR. It is built into a session-test harness as well,
so the tests drive it over a socketpair without vsock.

Signed-off-by: Cong Wang <cwang@multikernel.io>
kerf-init listens on vsock port 1023 next to the entrypoint and forks
one session process per connection, so the relay never runs inside PID
1. Only host root can connect: the peer must be CID 0 on a reserved
port. Signals move to a signalfd so one poll loop handles reaping,
forwarding and accepting.

Signed-off-by: Cong Wang <cwang@multikernel.io>
A single writer thread owns the socket so stdin, resize and signal
frames never interleave, and the main thread only reads, so output keeps
draining while stdin waits on backpressure.

Signed-off-by: Cong Wang <cwang@multikernel.io>
kerf exec connects to kerf-init in the instance over the multikernel
vsock transport from a reserved port and runs the command docker exec
style: -i, -t, -e, -w and -u, exit status passed through.

Signed-off-by: Cong Wang <cwang@multikernel.io>
The session stopped reading the socket while stdin it had buffered was
unwritten, so a command that does not read its stdin held up SIGNAL,
RESIZE and the host's disconnect behind it. The host now keeps at most
64 KiB of stdin unacknowledged and the session acknowledges what it
writes or drops with a new STDIN_ACK frame, so the session can always
read the socket.

Also bound the output drain after the command exits to 100 ms in total,
since a background process that keeps writing kept EXIT from ever being
sent, and drop buffered stdin when a pty master reports hangup instead
of spinning on it.

Signed-off-by: Cong Wang <cwang@multikernel.io>
With -it the terminal is raw, and SIGTERM or SIGHUP killed kerf exec
without restoring it; both now unwind through the restore path and exit
128 + signal. A closed stdout, as in kerf exec web cat log | head, exits
141 like a command killed by SIGPIPE instead of printing a traceback.

Signed-off-by: Cong Wang <cwang@multikernel.io>
A pending connection can go away between poll and accept, which would
leave PID 1 blocked in accept and stop reaping and signal forwarding.
The listener is non-blocking now.

Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk
congwang-mk merged commit 31020af into main Sep 27, 2026
4 checks passed
@congwang-mk
congwang-mk deleted the kerf-exec branch September 27, 2026 00:11
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.

1 participant