libcontainer: move example code out of README

Example code in README is outdated (especially since cgroups is moved to
a separate repository) and lacks proper import statements. And, since it
is not code, it is hard to keep it up to date.

Let's move it out to the example_test.go file and refer to it. Note we
still don't run it, but it will be compiled and linted in CI.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin
2026-02-25 16:07:35 -08:00
parent e89f01eda9
commit 6a374e6c1d
2 changed files with 245 additions and 215 deletions
+4 -215
View File
@@ -14,6 +14,8 @@ host system and which is (optionally) isolated from other containers in the syst
## Using libcontainer ## Using libcontainer
For a brief overview of using libcontainer, see [example_test.go](example_test.go).
### Container init ### Container init
Because containers are spawned in a two step process you will need a binary that Because containers are spawned in a two step process you will need a binary that
@@ -23,223 +25,10 @@ arg "init", we call the first step process "bootstrap", so you always need a "in
function as the entry of "bootstrap". function as the entry of "bootstrap".
In addition to the go init function the early stage bootstrap is handled by importing In addition to the go init function the early stage bootstrap is handled by importing
[nsenter](https://github.com/opencontainers/runc/blob/master/libcontainer/nsenter/README.md). [nsenter](../nsenter/README.md).
For details on how runc implements such "init", see For details on how runc implements such "init", see
[init.go](https://github.com/opencontainers/runc/blob/master/init.go) [../init.go](../init.go) and [init_linux.go](init_linux.go).
and [libcontainer/init_linux.go](https://github.com/opencontainers/runc/blob/master/libcontainer/init_linux.go).
### Device management
If you want containers that have access to some devices, you need to import
this package into your code:
```go
import (
_ "github.com/opencontainers/cgroups/devices"
)
```
Without doing this, libcontainer cgroup manager won't be able to set up device
access rules, and will fail if devices are specified in the container
configuration.
### Container creation
To create a container you first have to create a configuration
struct describing how the container is to be created. A sample would look similar to this:
```go
defaultMountFlags := unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
var devices []*devices.Rule
for _, device := range specconv.AllowedDevices {
devices = append(devices, &device.Rule)
}
config := &configs.Config{
Rootfs: "/your/path/to/rootfs",
Capabilities: &configs.Capabilities{
Bounding: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Effective: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Permitted: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
},
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: configs.NEWNS},
{Type: configs.NEWUTS},
{Type: configs.NEWIPC},
{Type: configs.NEWPID},
{Type: configs.NEWUSER},
{Type: configs.NEWNET},
{Type: configs.NEWCGROUP},
}),
Cgroups: &configs.Cgroup{
Name: "test-container",
Parent: "system",
Resources: &configs.Resources{
MemorySwappiness: nil,
Devices: devices,
},
},
MaskPaths: []string{
"/proc/kcore",
"/sys/firmware",
},
ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
},
Devices: specconv.AllowedDevices,
Hostname: "testing",
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: unix.MS_NOSUID | unix.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: unix.MS_NOSUID | unix.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Device: "tmpfs",
Source: "shm",
Destination: "/dev/shm",
Data: "mode=1777,size=65536k",
Flags: defaultMountFlags,
},
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | unix.MS_RDONLY,
},
},
UIDMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
GIDMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
Networks: []*configs.Network{
{
Type: "loopback",
Address: "127.0.0.1/0",
Gateway: "localhost",
},
},
Rlimits: []configs.Rlimit{
{
Type: unix.RLIMIT_NOFILE,
Hard: uint64(1025),
Soft: uint64(1025),
},
},
}
```
Once you have the configuration populated you can create a container
with a specified ID under a specified state directory:
```go
container, err := libcontainer.Create("/run/containers", "container-id", config)
if err != nil {
logrus.Fatal(err)
return
}
```
To spawn bash as the initial process inside the container and have the
processes pid returned in order to wait, signal, or kill the process:
```go
process := &libcontainer.Process{
Args: []string{"/bin/bash"},
Env: []string{"PATH=/bin"},
User: "daemon",
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Init: true,
}
err := container.Run(process)
if err != nil {
container.Destroy()
logrus.Fatal(err)
return
}
// wait for the process to finish.
_, err := process.Wait()
if err != nil {
logrus.Fatal(err)
}
// destroy the container.
container.Destroy()
```
Additional ways to interact with a running container are:
```go
// return all the pids for all processes running inside the container.
processes, err := container.Processes()
// get detailed cpu, memory, io, and network statistics for the container and
// it's processes.
stats, err := container.Stats()
// pause all processes inside the container.
container.Pause()
// resume all paused processes.
container.Resume()
// send signal to container's init process.
container.Signal(signal)
// update container resource constraints.
container.Set(config)
// get current status of the container.
status, err := container.Status()
// get current container's state information.
state, err := container.State()
```
## Checkpoint & Restore ## Checkpoint & Restore
+241
View File
@@ -0,0 +1,241 @@
package libcontainer_test
import (
"log"
"os"
"golang.org/x/sys/unix"
"github.com/opencontainers/cgroups"
"github.com/opencontainers/cgroups/devices/config"
// To enable device management code, import cgroups/devices package.
// Without it, cgroup manager won't be able to set up device access rules,
// and will fail if devices are specified in the container configuration.
_ "github.com/opencontainers/cgroups/devices"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
// Required for container enter functionality.
_ "github.com/opencontainers/runc/libcontainer/nsenter"
)
func Example_container() {
const defaultMountFlags = unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV
// Default set of allowed devices.
var devices []*config.Rule
for _, device := range specconv.AllowedDevices {
devices = append(devices, &device.Rule)
}
// To create a container you first have to create a configuration
// struct describing how the container is to be created.
config := &configs.Config{
Rootfs: "/your/path/to/rootfs",
Capabilities: &configs.Capabilities{
Bounding: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Effective: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Permitted: []string{
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
},
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: configs.NEWNS},
{Type: configs.NEWUTS},
{Type: configs.NEWIPC},
{Type: configs.NEWPID},
{Type: configs.NEWUSER},
{Type: configs.NEWNET},
{Type: configs.NEWCGROUP},
}),
Cgroups: &cgroups.Cgroup{
Name: "test-container",
Parent: "system",
Resources: &cgroups.Resources{
MemorySwappiness: nil,
Devices: devices,
},
},
MaskPaths: []string{
"/proc/kcore",
"/sys/firmware",
},
ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
},
Devices: specconv.AllowedDevices,
Hostname: "testing",
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: unix.MS_NOSUID | unix.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: unix.MS_NOSUID | unix.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Device: "tmpfs",
Source: "shm",
Destination: "/dev/shm",
Data: "mode=1777,size=65536k",
Flags: defaultMountFlags,
},
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | unix.MS_RDONLY,
},
},
UIDMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
GIDMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
Networks: []*configs.Network{
{
Type: "loopback",
Address: "127.0.0.1/0",
Gateway: "localhost",
},
},
Rlimits: []configs.Rlimit{
{
Type: unix.RLIMIT_NOFILE,
Hard: uint64(1025),
Soft: uint64(1025),
},
},
}
// Once you have the configuration populated you can create a container
// with a specified ID under a specified state directory:
container, err := libcontainer.Create("/run/containers", "container-id", config)
if err != nil {
log.Fatal(err)
return
}
// To spawn bash as the initial process inside the container and have the
// processes pid returned in order to wait, signal, or kill the process:
process := &libcontainer.Process{
Args: []string{"/bin/bash"},
Env: []string{"PATH=/bin"},
UID: 0,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Init: true,
}
err = container.Run(process)
if err != nil {
_ = container.Destroy()
log.Fatal(err)
return
}
// Wait for the process to finish.
_, err = process.Wait()
if err != nil {
log.Fatal(err)
}
// Destroy the container.
err = container.Destroy()
if err != nil {
log.Fatal(err)
}
// Additional ways to interact with a running container are:
// Return all the pids for all processes running inside the container.
processes, err := container.Processes()
if err != nil {
log.Fatal(err)
}
log.Print(processes)
// Get detailed cpu, memory, io, and network statistics for the container and
// it's processes.
stats, err := container.Stats()
if err != nil {
log.Fatal(err)
}
log.Print(stats)
// Pause all processes inside the container.
err = container.Pause()
if err != nil {
log.Fatal(err)
}
// Resume all paused processes.
err = container.Resume()
if err != nil {
log.Fatal(err)
}
// Send signal to container's init process.
err = container.Signal(unix.SIGHUP)
if err != nil {
log.Fatal(err)
}
// Update container resource constraints.
err = container.Set(*config)
if err != nil {
log.Fatal(err)
}
// Get current status of the container.
status, err := container.Status()
if err != nil {
log.Fatal(err)
}
log.Print(status)
// Get current container's state information.
state, err := container.State()
if err != nil {
log.Fatal(err)
}
log.Print(state)
}