Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Remove runtime files from containerd Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Update supervisor for orphaned containers Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Remove ctr/container.go back to rpc calls Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add attach to loaded container Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add monitor based on epoll for process exits Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Convert pids in containerd to string This is so that we no longer care about linux or system level pids and processes in containerd have user defined process id(pid) kinda like the exec process ids that docker has today. Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add reaper back to containerd Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement list containers with new process model Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement restore of processes Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add NONBLOCK to exit fifo open Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Implement tty reattach Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Fix race in exit pipe creation Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Add delete to shim Signed-off-by: Michael Crosby <crosbymichael@gmail.com> Update shim to use pid-file and not stdout Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
210 lines
5.3 KiB
Go
210 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"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
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// containerd-shim is a small shim that sits in front of a runc implementation
|
|
// that allows it to be repartented to init and handle reattach from the caller.
|
|
//
|
|
// 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)
|
|
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)
|
|
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])
|
|
if err != nil {
|
|
logrus.WithField("error", err).Fatal("shim: open container STDIO from fifo")
|
|
}
|
|
// 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")
|
|
}
|
|
var exitShim bool
|
|
for s := range signals {
|
|
logrus.WithField("signal", s).Debug("shim: received signal")
|
|
switch s {
|
|
case syscall.SIGCHLD:
|
|
exits, err := util.Reap()
|
|
if err != nil {
|
|
logrus.WithField("error", err).Error("shim: reaping child processes")
|
|
}
|
|
for _, e := range exits {
|
|
// check to see if runc is one of the processes that has exited
|
|
if e.Pid == runcPid {
|
|
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")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// runc has exited so the shim can also exit
|
|
if exitShim {
|
|
if err := std.Close(); err != nil {
|
|
logrus.WithField("error", err).Error("shim: close stdio")
|
|
}
|
|
if err := deleteContainer(os.Args[2]); err != nil {
|
|
logrus.WithField("error", err).Error("shim: delete runc state")
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
_, err = fmt.Fprintf(f, "%d", i)
|
|
return err
|
|
}
|