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
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:
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"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd/osutils"
"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
// that allows it to be repartented to init and handle reattach from the caller.
//
@ -28,9 +31,7 @@ func main() {
if err != nil {
panic(err)
}
logrus.SetOutput(f)
logrus.SetFormatter(&logrus.JSONFormatter{})
if err := start(); err != nil {
if err := start(f); err != nil {
// 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
// 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
// /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
logrus.Error(err)
writeMessage(f, "error", err)
f.Close()
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
// or if runtime exits before we hit the handler
signals := make(chan os.Signal, 2048)
@ -73,7 +74,7 @@ func start() error {
}
defer func() {
if err := p.Close(); err != nil {
logrus.Warn(err)
writeMessage(log, "warn", err)
}
}()
if err := p.start(); err != nil {
@ -84,7 +85,7 @@ func start() error {
for {
var msg, w, h int
if _, err := fmt.Fscanf(control, "%d %d %d\n", &msg, &w, &h); err != nil {
logrus.Warn(err)
continue
}
switch msg {
case 0:
@ -108,21 +109,12 @@ func start() error {
for s := range signals {
switch s {
case syscall.SIGCHLD:
exits, err := osutils.Reap()
if err != nil {
logrus.Warn(err)
}
exits, _ := osutils.Reap()
for _, e := range exits {
// check to see if runtime is one of the processes that has exited
if e.Pid == p.pid() {
exitShim = true
logrus.WithFields(logrus.Fields{
"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)
}
writeInt("exitStatus", e.Status)
}
}
}

View File

@ -12,13 +12,41 @@ import (
"strconv"
"sync"
"syscall"
"time"
"github.com/docker/containerd/runtime"
"github.com/opencontainers/runc/libcontainer"
"github.com/docker/containerd/specs"
)
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 {
sync.WaitGroup
id string
@ -26,12 +54,12 @@ type process struct {
stdio *stdio
exec bool
containerPid int
checkpoint *runtime.Checkpoint
checkpoint *checkpoint
shimIO *IO
stdinCloser io.Closer
console libcontainer.Console
console *os.File
consolePath string
state *runtime.ProcessState
state *processState
runtime string
}
@ -59,26 +87,26 @@ func newProcess(id, bundle, runtimeName string) (*process, error) {
return p, nil
}
func loadProcess() (*runtime.ProcessState, error) {
func loadProcess() (*processState, error) {
f, err := os.Open("process.json")
if err != nil {
return nil, err
}
defer f.Close()
var s runtime.ProcessState
var s processState
if err := json.NewDecoder(f).Decode(&s); err != nil {
return nil, err
}
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"))
if err != nil {
return nil, err
}
defer f.Close()
var cpt runtime.Checkpoint
var cpt checkpoint
if err := json.NewDecoder(f).Decode(&cpt); err != nil {
return nil, err
}
@ -200,25 +228,25 @@ func (p *process) openIO() error {
}()
if p.state.Terminal {
console, err := libcontainer.NewConsole(uid, gid)
master, console, err := newConsole(uid, gid)
if err != nil {
return err
}
p.console = console
p.consolePath = console.Path()
p.console = master
p.consolePath = console
stdin, err := os.OpenFile(p.state.Stdin, syscall.O_RDONLY, 0)
if err != nil {
return err
}
go io.Copy(console, stdin)
go io.Copy(master, stdin)
stdout, err := os.OpenFile(p.state.Stdout, syscall.O_RDWR, 0)
if err != nil {
return err
}
p.Add(1)
go func() {
io.Copy(stdout, console)
console.Close()
io.Copy(stdout, master)
master.Close()
p.Done()
}()
return nil

View File

@ -2,11 +2,7 @@
package osutils
import (
"syscall"
"github.com/opencontainers/runc/libcontainer/utils"
)
import "syscall"
// Exit is the wait4 information from an exited process
type Exit struct {
@ -34,7 +30,18 @@ func Reap() (exits []Exit, err error) {
}
exits = append(exits, Exit{
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()
}