Remove dep on larger packages

This removes most of the deps on the larger packages for the shim and
reduces the binary size and memory footprint from a 7.1mb binary to a
2.6mb binary.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-04-04 11:23:40 -07:00
parent 96034177f9
commit 6f18acda73
5 changed files with 122 additions and 41 deletions

View File

@ -50,7 +50,7 @@ daemon-static:
cd containerd && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd cd containerd && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd
shim: bin shim: bin
cd containerd-shim && go build -tags "$(BUILDTAGS)" -o ../bin/containerd-shim cd containerd-shim && go build -tags "$(BUILDTAGS)" -ldflags "-w" -o ../bin/containerd-shim
shim-static: shim-static:
cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim

View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
"os"
"syscall"
"unsafe"
)
// NewConsole returns an initalized console that can be used within a container by copying bytes
// from the master side to the slave that is attached as the tty for the container's init process.
func newConsole(uid, gid int) (*os.File, string, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
console, err := ptsname(master)
if err != nil {
return nil, "", err
}
if err := unlockpt(master); err != nil {
return nil, "", err
}
if err := os.Chmod(console, 0600); err != nil {
return nil, "", err
}
if err := os.Chown(console, uid, gid); err != nil {
return nil, "", err
}
return master, console, nil
}
func ioctl(fd uintptr, flag, data uintptr) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var n int32
if err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}

View File

@ -8,11 +8,14 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd/osutils" "github.com/docker/containerd/osutils"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
) )
func writeMessage(f *os.File, level string, err error) {
fmt.Fprintf(f, `{"level": "%s","msg": "%s"}`, level, err)
}
// containerd-shim is a small shim that sits in front of a runtime implementation // containerd-shim is a small shim that sits in front of a runtime implementation
// that allows it to be repartented to init and handle reattach from the caller. // that allows it to be repartented to init and handle reattach from the caller.
// //
@ -28,9 +31,7 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
logrus.SetOutput(f) if err := start(f); err != nil {
logrus.SetFormatter(&logrus.JSONFormatter{})
if err := start(); err != nil {
// this means that the runtime failed starting the container and will have the // this means that the runtime failed starting the container and will have the
// proper error messages in the runtime log so we should to treat this as a // proper error messages in the runtime log so we should to treat this as a
// shim failure because the sim executed properly // shim failure because the sim executed properly
@ -41,13 +42,13 @@ func main() {
// log the error instead of writing to stderr because the shim will have // log the error instead of writing to stderr because the shim will have
// /dev/null as it's stdio because it is supposed to be reparented to system // /dev/null as it's stdio because it is supposed to be reparented to system
// init and will not have anyone to read from it // init and will not have anyone to read from it
logrus.Error(err) writeMessage(f, "error", err)
f.Close() f.Close()
os.Exit(1) os.Exit(1)
} }
} }
func start() error { func start(log *os.File) error {
// start handling signals as soon as possible so that things are properly reaped // start handling signals as soon as possible so that things are properly reaped
// or if runtime exits before we hit the handler // or if runtime exits before we hit the handler
signals := make(chan os.Signal, 2048) signals := make(chan os.Signal, 2048)
@ -73,7 +74,7 @@ func start() error {
} }
defer func() { defer func() {
if err := p.Close(); err != nil { if err := p.Close(); err != nil {
logrus.Warn(err) writeMessage(log, "warn", err)
} }
}() }()
if err := p.start(); err != nil { if err := p.start(); err != nil {
@ -84,7 +85,7 @@ func start() error {
for { for {
var msg, w, h int var msg, w, h int
if _, err := fmt.Fscanf(control, "%d %d %d\n", &msg, &w, &h); err != nil { if _, err := fmt.Fscanf(control, "%d %d %d\n", &msg, &w, &h); err != nil {
logrus.Warn(err) continue
} }
switch msg { switch msg {
case 0: case 0:
@ -108,21 +109,12 @@ func start() error {
for s := range signals { for s := range signals {
switch s { switch s {
case syscall.SIGCHLD: case syscall.SIGCHLD:
exits, err := osutils.Reap() exits, _ := osutils.Reap()
if err != nil {
logrus.Warn(err)
}
for _, e := range exits { for _, e := range exits {
// check to see if runtime is one of the processes that has exited // check to see if runtime is one of the processes that has exited
if e.Pid == p.pid() { if e.Pid == p.pid() {
exitShim = true exitShim = true
logrus.WithFields(logrus.Fields{ writeInt("exitStatus", e.Status)
"pid": e.Pid,
"status": e.Status,
}).Info("shim: runtime exited")
if err := writeInt("exitStatus", e.Status); err != nil {
logrus.WithFields(logrus.Fields{"status": e.Status}).Warn(err)
}
} }
} }
} }

View File

@ -12,13 +12,41 @@ import (
"strconv" "strconv"
"sync" "sync"
"syscall" "syscall"
"time"
"github.com/docker/containerd/runtime" "github.com/docker/containerd/specs"
"github.com/opencontainers/runc/libcontainer"
) )
var errRuntime = errors.New("shim: runtime execution error") var errRuntime = errors.New("shim: runtime execution error")
type checkpoint struct {
// Timestamp is the time that checkpoint happened
Created time.Time `json:"created"`
// Name is the name of the checkpoint
Name string `json:"name"`
// Tcp checkpoints open tcp connections
Tcp bool `json:"tcp"`
// UnixSockets persists unix sockets in the checkpoint
UnixSockets bool `json:"unixSockets"`
// Shell persists tty sessions in the checkpoint
Shell bool `json:"shell"`
// Exit exits the container after the checkpoint is finished
Exit bool `json:"exit"`
}
type processState struct {
specs.ProcessSpec
Exec bool `json:"exec"`
Stdin string `json:"containerdStdin"`
Stdout string `json:"containerdStdout"`
Stderr string `json:"containerdStderr"`
RuntimeArgs []string `json:"runtimeArgs"`
NoPivotRoot bool `json:"noPivotRoot"`
Checkpoint string `json:"checkpoint"`
RootUID int `json:"rootUID"`
RootGID int `json:"rootGID"`
}
type process struct { type process struct {
sync.WaitGroup sync.WaitGroup
id string id string
@ -26,12 +54,12 @@ type process struct {
stdio *stdio stdio *stdio
exec bool exec bool
containerPid int containerPid int
checkpoint *runtime.Checkpoint checkpoint *checkpoint
shimIO *IO shimIO *IO
stdinCloser io.Closer stdinCloser io.Closer
console libcontainer.Console console *os.File
consolePath string consolePath string
state *runtime.ProcessState state *processState
runtime string runtime string
} }
@ -59,26 +87,26 @@ func newProcess(id, bundle, runtimeName string) (*process, error) {
return p, nil return p, nil
} }
func loadProcess() (*runtime.ProcessState, error) { func loadProcess() (*processState, error) {
f, err := os.Open("process.json") f, err := os.Open("process.json")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
var s runtime.ProcessState var s processState
if err := json.NewDecoder(f).Decode(&s); err != nil { if err := json.NewDecoder(f).Decode(&s); err != nil {
return nil, err return nil, err
} }
return &s, nil return &s, nil
} }
func loadCheckpoint(bundle, name string) (*runtime.Checkpoint, error) { func loadCheckpoint(bundle, name string) (*checkpoint, error) {
f, err := os.Open(filepath.Join(bundle, "checkpoints", name, "config.json")) f, err := os.Open(filepath.Join(bundle, "checkpoints", name, "config.json"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
var cpt runtime.Checkpoint var cpt checkpoint
if err := json.NewDecoder(f).Decode(&cpt); err != nil { if err := json.NewDecoder(f).Decode(&cpt); err != nil {
return nil, err return nil, err
} }
@ -200,25 +228,25 @@ func (p *process) openIO() error {
}() }()
if p.state.Terminal { if p.state.Terminal {
console, err := libcontainer.NewConsole(uid, gid) master, console, err := newConsole(uid, gid)
if err != nil { if err != nil {
return err return err
} }
p.console = console p.console = master
p.consolePath = console.Path() p.consolePath = console
stdin, err := os.OpenFile(p.state.Stdin, syscall.O_RDONLY, 0) stdin, err := os.OpenFile(p.state.Stdin, syscall.O_RDONLY, 0)
if err != nil { if err != nil {
return err return err
} }
go io.Copy(console, stdin) go io.Copy(master, stdin)
stdout, err := os.OpenFile(p.state.Stdout, syscall.O_RDWR, 0) stdout, err := os.OpenFile(p.state.Stdout, syscall.O_RDWR, 0)
if err != nil { if err != nil {
return err return err
} }
p.Add(1) p.Add(1)
go func() { go func() {
io.Copy(stdout, console) io.Copy(stdout, master)
console.Close() master.Close()
p.Done() p.Done()
}() }()
return nil return nil

View File

@ -2,11 +2,7 @@
package osutils package osutils
import ( import "syscall"
"syscall"
"github.com/opencontainers/runc/libcontainer/utils"
)
// Exit is the wait4 information from an exited process // Exit is the wait4 information from an exited process
type Exit struct { type Exit struct {
@ -34,7 +30,18 @@ func Reap() (exits []Exit, err error) {
} }
exits = append(exits, Exit{ exits = append(exits, Exit{
Pid: pid, Pid: pid,
Status: utils.ExitStatus(ws), Status: exitStatus(ws),
}) })
} }
} }
const exitSignalOffset = 128
// exitStatus returns the correct exit status for a process based on if it
// was signaled or exited cleanly
func exitStatus(status syscall.WaitStatus) int {
if status.Signaled() {
return exitSignalOffset + int(status.Signal())
}
return status.ExitStatus()
}