containerd/cmd/containerd-shim/main.go

142 lines
4 KiB
Go
Raw Normal View History

package main
import (
"fmt"
"net"
"os"
"os/signal"
"strings"
"syscall"
"google.golang.org/grpc"
"github.com/Sirupsen/logrus"
runc "github.com/crosbymichael/go-runc"
"github.com/docker/containerd"
shimapi "github.com/docker/containerd/api/services/shim"
"github.com/docker/containerd/linux/shim"
"github.com/docker/containerd/reaper"
"github.com/docker/containerd/sys"
"github.com/urfave/cli"
)
const usage = `
__ _ __ __ _
_________ ____ / /_____ _(_)___ ___ _________/ / _____/ /_ (_)___ ___
/ ___/ __ \/ __ \/ __/ __ ` + "`" + `/ / __ \/ _ \/ ___/ __ /_____/ ___/ __ \/ / __ ` + "`" + `__ \
/ /__/ /_/ / / / / /_/ /_/ / / / / / __/ / / /_/ /_____(__ ) / / / / / / / / /
\___/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/ \__,_/ /____/_/ /_/_/_/ /_/ /_/
shim for container lifecycle and reconnection
`
func main() {
app := cli.NewApp()
app.Name = "containerd-shim"
app.Version = containerd.Version
app.Usage = usage
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "enable debug output in logs",
},
}
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
return nil
}
app.Action = func(context *cli.Context) error {
// start handling signals as soon as possible so that things are properly reaped
// or if runtime exits before we hit the handler
signals, err := setupSignals()
if err != nil {
return err
}
if err := setupRoot(); err != nil {
return err
}
var (
server = grpc.NewServer()
sv = shim.New()
)
logrus.Debug("registering grpc server")
shimapi.RegisterShimServer(server, sv)
if err := serve(server, "shim.sock"); err != nil {
return err
}
return handleSignals(signals, server, sv)
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "containerd-shim: %s\n", err)
os.Exit(1)
}
}
// setupSignals creates a new signal handler for all signals and sets the shim as a
// sub-reaper so that the container processes are reparented
func setupSignals() (chan os.Signal, error) {
signals := make(chan os.Signal, 2048)
signal.Notify(signals)
// make sure runc is setup to use the monitor
// for waiting on processes
runc.Monitor = reaper.Default
// set the shim as the subreaper for all orphaned processes created by the container
if err := sys.SetSubreaper(1); err != nil {
return nil, err
}
return signals, nil
}
// serve serves the grpc API over a unix socket at the provided path
// this function does not block
func serve(server *grpc.Server, path string) error {
l, err := net.FileListener(os.NewFile(3, "socket"))
if err != nil {
return err
}
logrus.WithField("socket", path).Debug("serving api on unix socket")
go func() {
defer l.Close()
if err := server.Serve(l); err != nil &&
!strings.Contains(err.Error(), "use of closed network connection") {
logrus.WithError(err).Fatal("containerd-shim: GRPC server failure")
}
}()
return nil
}
func handleSignals(signals chan os.Signal, server *grpc.Server, service *shim.Service) error {
for s := range signals {
logrus.WithField("signal", s).Debug("received signal")
switch s {
case syscall.SIGCHLD:
exits, err := reaper.Reap()
if err != nil {
logrus.WithError(err).Error("reap exit status")
}
for _, e := range exits {
logrus.WithFields(logrus.Fields{
"status": e.Status,
"pid": e.Pid,
}).Debug("process exited")
if err := service.ProcessExit(e); err != nil {
return err
}
}
case syscall.SIGTERM, syscall.SIGINT:
// TODO: should we forward signals to the processes if they are still running?
// i.e. machine reboot
server.Stop()
return nil
}
}
return nil
}
// setupRoot sets up the root as the shim is started in its own mount namespace
func setupRoot() error {
return syscall.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, "")
}