Add rough container type and runc runtime

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-09-28 15:09:22 -07:00
parent c2da97c4d1
commit b3ab74fee3
5 changed files with 545 additions and 0 deletions

147
container.go Normal file
View File

@ -0,0 +1,147 @@
package containerkit
import (
"encoding/json"
"io"
"os"
"path/filepath"
"sync"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
func NewContainer(root, id string, m Mount, s *specs.Spec, driver ExecutionDriver) (*Container, error) {
path := filepath.Join(root, id)
if err := os.MkdirAll(filepath.Join(path, s.Root.Path), 0711); err != nil {
return nil, err
}
// FIXME: find a better UI for this
s.Mounts = append([]specs.Mount{
{
Type: m.Type,
Source: m.Source,
Destination: "/",
Options: m.Options,
},
}, s.Mounts...)
f, err := os.Create(filepath.Join(path, "config.json"))
if err != nil {
return nil, err
}
// write the spec file to the container's directory
err = json.NewEncoder(f).Encode(s)
f.Close()
if err != nil {
return nil, err
}
return &Container{
id: id,
path: path,
s: s,
driver: driver,
}, nil
}
type Container struct {
mu sync.Mutex
id string
path string
s *specs.Spec
driver ExecutionDriver
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// init is the container's init processes
init *Process
// processes is a list of additional processes executed inside the container
// via the NewProcess method on the container
processes []*Process
}
// ID returns the id of the container
func (c *Container) ID() string {
return c.id
}
// Path returns the fully qualified path to the container on disk
func (c *Container) Path() string {
return c.path
}
// Create will create the container on the system by running the runtime's
// initial setup and process waiting for the user process to be started
func (c *Container) Create() error {
c.mu.Lock()
defer c.mu.Unlock()
d, err := c.driver.Create(c)
if err != nil {
return err
}
c.init = &Process{
d: d,
driver: c.driver,
}
return nil
}
// Start will start the container's user specified process
func (c *Container) Start() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.driver.Start(c)
}
// NewProcess will create a new process that will be executed inside the
// container and tied to the init processes lifecycle
func (c *Container) NewProcess(spec *specs.Process) (*Process, error) {
c.mu.Lock()
defer c.mu.Unlock()
process := &Process{
s: spec,
c: c,
exec: true,
}
c.processes = append(c.processes, process)
return process, nil
}
// Pid returns the pid of the init or main process hosted inside the container
func (c *Container) Pid() int {
c.mu.Lock()
if c.init == nil {
c.mu.Unlock()
return -1
}
pid := c.init.Pid()
c.mu.Unlock()
return pid
}
// Wait will perform a blocking wait on the init process of the container
func (c *Container) Wait() (uint32, error) {
c.mu.Lock()
defer c.mu.Unlock()
return c.init.Wait()
}
// Signal will send the provided signal to the init process of the container
func (c *Container) Signal(s os.Signal) error {
c.mu.Lock()
defer c.mu.Unlock()
return c.init.Signal(s)
}
// Delete will delete the container if it no long has any processes running
// inside the container and removes all state on disk for the container
func (c *Container) Delete() error {
c.mu.Lock()
defer c.mu.Unlock()
err := c.driver.Delete(c)
if rerr := os.RemoveAll(c.path); err == nil {
err = rerr
}
return err
}

207
example/main.go Normal file
View File

@ -0,0 +1,207 @@
package main
import (
"os"
"path/filepath"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/containerkit"
"github.com/docker/containerkit/osutils"
"github.com/docker/containerkit/runc"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
var RWM = "rwm"
// "Hooks do optional work. Drivers do mandatory work"
func main() {
if err := osutils.SetSubreaper(1); err != nil {
logrus.Fatal(err)
}
if err := runTest(); err != nil {
logrus.Fatal(err)
}
}
func runTest() error {
// create a new runc runtime that implements the ExecutionDriver interface
driver, err := runc.New("/run/runc", "/tmp/runc")
if err != nil {
return err
}
// create a new container
container, err := containerkit.NewContainer(
"/var/lib/containerkit", /* container root */
"test", /* container id */
containerkit.Mount{
Type: "bind",
Source: "/containers/redis/rootfs",
Options: []string{
"rbind",
"rw",
},
}, /* mount from the graph subsystem for the container */
spec("test"), /* the spec for the container */
driver, /* the exec driver to use for the container */
)
if err != nil {
return err
}
// setup some stdio for our container
container.Stdin = os.Stdin
container.Stdout = os.Stdout
container.Stderr = os.Stderr
// go ahead and set the container in the create state and have it ready to start
logrus.Info("create container")
if err := container.Create(); err != nil {
return err
}
// start the user defined process in the container
logrus.Info("start container")
if err := container.Start(); err != nil {
return err
}
// wait for it to exit and get the exit status
logrus.Info("wait container")
status, err := container.Wait()
if err != nil {
return err
}
// delete the container after it is done
logrus.Info("delete container")
if container.Delete(); err != nil {
return err
}
logrus.Infof("exit status %d", status)
return nil
}
// bla bla bla spec stuff
func spec(id string) *specs.Spec {
cgpath := filepath.Join("/containerkit", id)
return &specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
},
Root: specs.Root{
Path: "rootfs",
Readonly: false,
},
Process: specs.Process{
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
Args: []string{"sleep", "10"},
Terminal: false,
Cwd: "/",
NoNewPrivileges: true,
Capabilities: []string{
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_FOWNER",
"CAP_CHOWN",
"CAP_MKNOD",
"CAP_FSETID",
"CAP_DAC_OVERRIDE",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_SETGID",
"CAP_SETUID",
"CAP_NET_BIND_SERVICE",
},
},
Hostname: "containerkit",
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/run",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/etc/resolv.conf",
Type: "bind",
Source: "/etc/resolv.conf",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/hosts",
Type: "bind",
Source: "/etc/hosts",
Options: []string{"rbind", "ro"},
},
{
Destination: "/etc/localtime",
Type: "bind",
Source: "/etc/localtime",
Options: []string{"rbind", "ro"},
},
},
Linux: &specs.Linux{
CgroupsPath: &cgpath,
Resources: &specs.Resources{
Devices: []specs.DeviceCgroup{
{
Allow: false,
Access: &RWM,
},
},
},
Namespaces: []specs.Namespace{
{
Type: "pid",
},
{
Type: "ipc",
},
{
Type: "uts",
},
{
Type: "mount",
},
},
},
}
}

14
execution.go Normal file
View File

@ -0,0 +1,14 @@
package containerkit
import "errors"
var (
ErrProcessSet = errors.New("containerkit: container process is already set")
)
type ExecutionDriver interface {
Create(*Container) (ProcessDelegate, error)
Start(*Container) error
Delete(*Container) error
Exec(*Container, *Process) (ProcessDelegate, error)
}

60
process.go Normal file
View File

@ -0,0 +1,60 @@
package containerkit
import (
"errors"
"io"
"os"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
var (
ErrNotExecProcess = errors.New("containerkit: process not an exec process")
)
type ProcessDelegate interface {
Pid() int
Wait() (uint32, error)
Signal(os.Signal) error
}
type Process struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
exec bool
s *specs.Process
driver ExecutionDriver
c *Container
d ProcessDelegate
}
func (p *Process) Spec() *specs.Process {
return p.s
}
func (p *Process) Start() error {
if !p.exec {
return ErrNotExecProcess
}
d, err := p.driver.Exec(p.c, p)
if err != nil {
return err
}
p.d = d
return nil
}
func (p *Process) Pid() int {
return p.d.Pid()
}
func (p *Process) Wait() (uint32, error) {
return p.d.Wait()
}
func (p *Process) Signal(s os.Signal) error {
return p.d.Signal(s)
}

117
runc/runc.go Normal file
View File

@ -0,0 +1,117 @@
package runc
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"github.com/docker/containerkit"
)
func New(root, log string) (*Runc, error) {
if err := os.MkdirAll(root, 0711); err != nil {
return nil, err
}
return &Runc{
root: root,
log: log,
}, nil
}
type Runc struct {
root string
log string
}
func (r *Runc) Create(c *containerkit.Container) (containerkit.ProcessDelegate, error) {
pidFile := fmt.Sprintf("%s/%s.pid", filepath.Join(r.root, c.ID()), "init")
cmd := r.command("create", "--pid-file", pidFile, "--bundle", c.Path(), c.ID())
cmd.Stdin, cmd.Stdout, cmd.Stderr = c.Stdin, c.Stdout, c.Stderr
if err := cmd.Run(); err != nil {
return nil, err
}
data, err := ioutil.ReadFile(pidFile)
if err != nil {
return nil, err
}
i, err := strconv.Atoi(string(data))
if err != nil {
return nil, err
}
return newProcess(i)
}
func (r *Runc) Start(c *containerkit.Container) error {
return r.command("start", c.ID()).Run()
}
func (r *Runc) Delete(c *containerkit.Container) error {
return r.command("delete", c.ID()).Run()
}
func (r *Runc) Exec(c *containerkit.Container, p *containerkit.Process) (containerkit.ProcessDelegate, error) {
f, err := ioutil.TempFile(filepath.Join(r.root, c.ID()), "process")
if err != nil {
return nil, err
}
path := f.Name()
pidFile := fmt.Sprintf("%s/%s.pid", filepath.Join(r.root, c.ID()), filepath.Base(path))
err = json.NewEncoder(f).Encode(p.Spec())
f.Close()
if err != nil {
return nil, err
}
cmd := r.command("exec", "--process", path, "--pid-file", pidFile, c.ID())
cmd.Stdin, cmd.Stdout, cmd.Stderr = p.Stdin, p.Stdout, p.Stderr
data, err := ioutil.ReadFile(pidFile)
if err != nil {
return nil, err
}
i, err := strconv.Atoi(string(data))
if err != nil {
return nil, err
}
return newProcess(i)
}
func (r *Runc) command(args ...string) *exec.Cmd {
return exec.Command("runc", append([]string{
"--root", r.root,
"--log", r.log,
}, args...)...)
}
func newProcess(pid int) (*process, error) {
proc, err := os.FindProcess(pid)
if err != nil {
return nil, err
}
return &process{
proc: proc,
}, nil
}
type process struct {
proc *os.Process
}
func (p *process) Pid() int {
return p.proc.Pid
}
func (p *process) Wait() (uint32, error) {
state, err := p.proc.Wait()
if err != nil {
return 0, nil
}
return uint32(state.Sys().(syscall.WaitStatus).ExitStatus()), nil
}
func (p *process) Signal(s os.Signal) error {
return p.proc.Signal(s)
}