Skip to content
Draft
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
11 changes: 11 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,21 @@ Each monitor subsection supports the following options:
| `default_vcpus` | integer | `1` | Default number of virtual CPUs |
| `path` | string | (empty) | Optional custom path to the monitor binary. If not specified, urunc will search for the binary in PATH |
| `data_path` | string | (empty) | Optional custom path for the monitor's data file directory |
| `socket_path` | string | (empty) | Optional path for the monitor's control socket. If not specified, urunc uses a per-container default (`/tmp/<container-id>.sock`) |
| `graceful_shutdown` | boolean | `false` | Optional. When `true`, on SIGTERM urunc asks the monitor to shut the guest down gracefully over its control socket (QEMU and Cloud Hypervisor on all architectures, Firecracker on x86 only) instead of killing it; the container manager still escalates to SIGKILL after its grace period. Defaults to `false` |

Since Qemu is the only currently supported monitor which requires extra data to
boot a VM, `urunc` will first check `/usr/local/share` and then `/usr/share` for
Qemu's data files.

The `socket_path` option applies to the monitors that expose a control socket:
Firecracker (its API socket), Qemu (a QMP socket) and Cloud Hypervisor (its REST
API socket). It has no effect on the other monitors. The monitor creates the
socket inside its own (pivoted) rootfs; `urunc` creates the directory of a
custom `socket_path` there for you, so the path can be anywhere. It only fails
if the location is invalid, for example when a file already exists at one of the
directories in the path.

**Example:**

```toml
Expand All @@ -130,6 +140,7 @@ data_path = "/usr/local/share/"
default_memory_mb = 512
default_vcpus = 2
path = "/opt/firecracker/firecracker"
socket_path = "/run/urunc/fc.sock"
```

### Extra binaries Configuration
Expand Down
18 changes: 18 additions & 0 deletions pkg/unikontainers/hypervisors/cloud_hypervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package hypervisors

import (
"fmt"
"net/http"
"strings"

"github.com/urunc-dev/urunc/pkg/unikontainers/types"
Expand Down Expand Up @@ -44,6 +45,19 @@ func (ch *CloudHypervisor) Ok() error {
return nil
}

// SupportsGuestShutdown reports that Cloud Hypervisor can shut the guest down
// gracefully via its REST API power-button endpoint.
func (ch *CloudHypervisor) SupportsGuestShutdown() bool {
return true
}

// RequestGuestShutdown asks Cloud Hypervisor to press the guest's power button
// over its REST API control socket. socketPath is the already-resolved,
// host-reachable path; it is dialed directly. A non-2xx response is an error.
func (ch *CloudHypervisor) RequestGuestShutdown(socketPath string) error {
return unixSocketRequest(socketPath, http.MethodPut, "/api/v1/vm.power-button", nil)
}

// UsesKVM returns true as Cloud Hypervisor is a KVM-based VMM
func (ch *CloudHypervisor) UsesKVM() bool {
return true
Expand All @@ -70,6 +84,10 @@ func (ch *CloudHypervisor) BuildExecCmd(args types.ExecArgs, ukernel types.Unike
// Start building the command
exArgs := []string{ch.binaryPath}

// Expose the REST API over a control socket so the runtime can talk to
// Cloud Hypervisor after boot (e.g. for graceful shutdown).
exArgs = append(exArgs, "--api-socket", "path="+ResolveSocketPath(args))

// Memory configuration
if args.Sharedfs.Type == "virtiofs" {
exArgs = append(exArgs, "--memory", fmt.Sprintf("size=%sM,shared=on", chMem))
Expand Down
25 changes: 23 additions & 2 deletions pkg/unikontainers/hypervisors/firecracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ package hypervisors
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/urunc-dev/urunc/pkg/unikontainers/types"
Expand Down Expand Up @@ -88,6 +90,21 @@ func (fc *Firecracker) Ok() error {
return nil
}

// SupportsGuestShutdown reports whether Firecracker can shut the guest down
// gracefully. Firecracker's SendCtrlAltDel action only exists on x86; on
// aarch64 the API rejects it, so guest shutdown is supported on amd64 only.
func (fc *Firecracker) SupportsGuestShutdown() bool {
return runtime.GOARCH == "amd64"
}

// RequestGuestShutdown asks Firecracker to inject a Ctrl+Alt+Del into the
// guest over its REST API control socket. socketPath is the already-resolved,
// host-reachable path; it is dialed directly. A non-2xx response is an error.
func (fc *Firecracker) RequestGuestShutdown(socketPath string) error {
body := []byte(`{"action_type":"SendCtrlAltDel"}`)
return unixSocketRequest(socketPath, http.MethodPut, "/actions", body)
}

func (fc *Firecracker) UsesKVM() bool {
return true
}
Expand All @@ -108,9 +125,13 @@ func (fc *Firecracker) BuildExecCmd(args types.ExecArgs, ukernel types.Unikernel
// options in FC, since the string return value of the Monitor related
// functions in the unikernel interface do not integrate well with FC's
// json configuration.
cmdString := fc.Path() + " --no-api --config-file "
// Launch Firecracker with its API socket enabled (drop --no-api) while
// still booting the guest from the config file. This preserves today's
// boot behavior and additionally leaves the control socket open for use
// after the guest has started.
apiSockPath := ResolveSocketPath(args)
JSONConfigFile := filepath.Join("/tmp/", FCJsonFilename)
cmdString += JSONConfigFile
cmdString := fc.Path() + " --api-sock " + apiSockPath + " --config-file " + JSONConfigFile
if !args.Seccomp {
cmdString += " --no-seccomp"
}
Expand Down
Loading