diff --git a/libcontainer/README.md b/libcontainer/README.md index 901351edb..2e4bd00e9 100644 --- a/libcontainer/README.md +++ b/libcontainer/README.md @@ -14,6 +14,8 @@ host system and which is (optionally) isolated from other containers in the syst ## Using libcontainer +For a brief overview of using libcontainer, see [example_test.go](example_test.go). + ### Container init 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". 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 -[init.go](https://github.com/opencontainers/runc/blob/master/init.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() -``` - +[../init.go](../init.go) and [init_linux.go](init_linux.go). ## Checkpoint & Restore diff --git a/libcontainer/example_test.go b/libcontainer/example_test.go new file mode 100644 index 000000000..023264b3b --- /dev/null +++ b/libcontainer/example_test.go @@ -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) +}