Update shim for exec

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-02-01 11:02:41 -08:00
parent 6808dbc02f
commit 835f3b6a97
37 changed files with 786 additions and 709 deletions

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

@ -1,43 +1,21 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strconv"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd/util"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)
const (
bufferSize = 2048
)
var fexec bool
type stdio struct {
stdin *os.File
stdout *os.File
stderr *os.File
console string
}
func (s *stdio) Close() error {
err := s.stdin.Close()
if oerr := s.stdout.Close(); err == nil {
err = oerr
}
if oerr := s.stderr.Close(); err == nil {
err = oerr
}
return err
func init() {
flag.BoolVar(&fexec, "exec", false, "exec a process instead of starting the init")
flag.Parse()
}
// containerd-shim is a small shim that sits in front of a runc implementation
@ -45,41 +23,27 @@ func (s *stdio) Close() error {
//
// the cwd of the shim should be the bundle for the container. Arg1 should be the path
// to the state directory where the shim can locate fifos and other information.
//
// └── shim
// ├── control
// ├── stderr
// ├── stdin
// ├── stdout
// ├── pid
// └── exit
func main() {
if len(os.Args) < 2 {
logrus.Fatal("shim: no arguments provided")
}
// start handling signals as soon as possible so that things are properly reaped
// or if runc exits before we hit the handler
signals := make(chan os.Signal, bufferSize)
signals := make(chan os.Signal, 2048)
signal.Notify(signals)
// set the shim as the subreaper for all orphaned processes created by the container
if err := util.SetSubreaper(1); err != nil {
logrus.WithField("error", err).Fatal("shim: set as subreaper")
}
// open the exit pipe
f, err := os.OpenFile(filepath.Join(os.Args[1], "exit"), syscall.O_WRONLY, 0)
f, err := os.OpenFile("exit", syscall.O_WRONLY, 0)
if err != nil {
logrus.WithField("error", err).Fatal("shim: open exit pipe")
}
defer f.Close()
// open the fifos for use with the command
std, err := openContainerSTDIO(os.Args[1])
p, err := newProcess(flag.Arg(0), flag.Arg(1), fexec)
if err != nil {
logrus.WithField("error", err).Fatal("shim: open container STDIO from fifo")
logrus.WithField("error", err).Fatal("shim: create new process")
}
// star the container process by invoking runc
runcPid, err := startRunc(std, os.Args[2])
if err != nil {
logrus.WithField("error", err).Fatal("shim: start runc")
if err := p.start(); err != nil {
logrus.WithField("error", err).Fatal("shim: start process")
}
var exitShim bool
for s := range signals {
@ -92,22 +56,27 @@ func main() {
}
for _, e := range exits {
// check to see if runc is one of the processes that has exited
if e.Pid == runcPid {
if e.Pid == p.pid() {
exitShim = true
logrus.WithFields(logrus.Fields{"pid": e.Pid, "status": e.Status}).Info("shim: runc exited")
if err := writeInt(filepath.Join(os.Args[1], "exitStatus"), e.Status); err != nil {
logrus.WithFields(logrus.Fields{"error": err, "status": e.Status}).Error("shim: write exit status")
logrus.WithFields(logrus.Fields{
"pid": e.Pid,
"status": e.Status,
}).Info("shim: runc exited")
if err := writeInt("exitStatus", e.Status); err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
"status": e.Status,
}).Error("shim: write exit status")
}
}
}
}
// runc has exited so the shim can also exit
if exitShim {
if err := std.Close(); err != nil {
if err := p.Close(); err != nil {
logrus.WithField("error", err).Error("shim: close stdio")
}
if err := deleteContainer(os.Args[2]); err != nil {
if err := p.delete(); err != nil {
logrus.WithField("error", err).Error("shim: delete runc state")
}
return
@ -115,90 +84,6 @@ func main() {
}
}
// startRunc starts runc detached and returns the container's pid
func startRunc(s *stdio, id string) (int, error) {
pidFile := filepath.Join(os.Args[1], "pid")
cmd := exec.Command("runc", "--id", id, "start", "-d", "--console", s.console, "--pid-file", pidFile)
cmd.Stdin = s.stdin
cmd.Stdout = s.stdout
cmd.Stderr = s.stderr
// set the parent death signal to SIGKILL so that if the shim dies the container
// process also dies
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
if err := cmd.Run(); err != nil {
return -1, err
}
data, err := ioutil.ReadFile(pidFile)
if err != nil {
return -1, err
}
return strconv.Atoi(string(data))
}
func deleteContainer(id string) error {
return exec.Command("runc", "--id", id, "delete").Run()
}
// openContainerSTDIO opens the pre-created fifo's for use with the container
// in RDWR so that they remain open if the other side stops listening
func openContainerSTDIO(dir string) (*stdio, error) {
s := &stdio{}
spec, err := getSpec()
if err != nil {
return nil, err
}
if spec.Process.Terminal {
console, err := libcontainer.NewConsole(int(spec.Process.User.UID), int(spec.Process.User.GID))
if err != nil {
return nil, err
}
s.console = console.Path()
stdin, err := os.OpenFile(filepath.Join(dir, "stdin"), syscall.O_RDWR, 0)
if err != nil {
return nil, err
}
go func() {
io.Copy(console, stdin)
}()
stdout, err := os.OpenFile(filepath.Join(dir, "stdout"), syscall.O_RDWR, 0)
if err != nil {
return nil, err
}
go func() {
io.Copy(stdout, console)
console.Close()
}()
return s, nil
}
for name, dest := range map[string]**os.File{
"stdin": &s.stdin,
"stdout": &s.stdout,
"stderr": &s.stderr,
} {
f, err := os.OpenFile(filepath.Join(dir, name), syscall.O_RDWR, 0)
if err != nil {
return nil, err
}
*dest = f
}
return s, nil
}
func getSpec() (*specs.Spec, error) {
var s specs.Spec
f, err := os.Open("config.json")
if err != nil {
return nil, err
}
defer f.Close()
if err := json.NewDecoder(f).Decode(&s); err != nil {
return nil, err
}
return &s, nil
}
func writeInt(path string, i int) error {
f, err := os.Create(path)
if err != nil {

165
containerd-shim/process.go Normal file
View file

@ -0,0 +1,165 @@
package main
import (
"encoding/json"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)
type process struct {
id string
bundle string
stdio *stdio
s specs.Process
exec bool
containerPid int
}
func newProcess(id, bundle string, exec bool) (*process, error) {
f, err := os.Open("process.json")
if err != nil {
return nil, err
}
defer f.Close()
p := &process{
id: id,
bundle: bundle,
exec: exec,
}
if err := json.NewDecoder(f).Decode(&p.s); err != nil {
return nil, err
}
if err := p.openIO(); err != nil {
return nil, err
}
return p, nil
}
func (p *process) start() error {
cwd, err := os.Getwd()
if err != nil {
return err
}
args := []string{
"--id", p.id,
}
if p.exec {
args = append(args, "exec",
"--process", filepath.Join(cwd, "process.json"))
} else {
args = append(args, "start",
"--bundle", p.bundle)
}
args = append(args,
"-d",
"--console", p.stdio.console,
"--pid-file", filepath.Join(cwd, "pid"),
)
cmd := exec.Command("runc", args...)
cmd.Stdin = p.stdio.stdin
cmd.Stdout = p.stdio.stdout
cmd.Stderr = p.stdio.stderr
// set the parent death signal to SIGKILL so that if the shim dies the container
// process also dies
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
if err := cmd.Run(); err != nil {
return err
}
data, err := ioutil.ReadFile("pid")
if err != nil {
return err
}
pid, err := strconv.Atoi(string(data))
if err != nil {
return err
}
p.containerPid = pid
return nil
}
func (p *process) pid() int {
return p.containerPid
}
func (p *process) delete() error {
if !p.exec {
return exec.Command("runc", "--id", p.id, "delete").Run()
}
return nil
}
// openIO opens the pre-created fifo's for use with the container
// in RDWR so that they remain open if the other side stops listening
func (p *process) openIO() error {
p.stdio = &stdio{}
if p.s.Terminal {
// FIXME: this is wrong for user namespaces and will need to be translated
console, err := libcontainer.NewConsole(int(p.s.User.UID), int(p.s.User.GID))
if err != nil {
return err
}
p.stdio.console = console.Path()
stdin, err := os.OpenFile("stdin", syscall.O_RDWR, 0)
if err != nil {
return err
}
go func() {
io.Copy(console, stdin)
}()
stdout, err := os.OpenFile("stdout", syscall.O_RDWR, 0)
if err != nil {
return err
}
go func() {
io.Copy(stdout, console)
console.Close()
}()
return nil
}
// non-tty
for name, dest := range map[string]**os.File{
"stdin": &p.stdio.stdin,
"stdout": &p.stdio.stdout,
"stderr": &p.stdio.stderr,
} {
f, err := os.OpenFile(name, syscall.O_RDWR, 0)
if err != nil {
return err
}
*dest = f
}
return nil
}
func (p *process) Close() error {
return p.stdio.Close()
}
type stdio struct {
stdin *os.File
stdout *os.File
stderr *os.File
console string
}
func (s *stdio) Close() error {
err := s.stdin.Close()
if oerr := s.stdout.Close(); err == nil {
err = oerr
}
if oerr := s.stderr.Close(); err == nil {
err = oerr
}
return err
}