containerd/cmd/containerd/main.go

232 lines
5.9 KiB
Go

package main
import (
_ "expvar"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
gocontext "golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/Sirupsen/logrus"
"github.com/docker/containerd"
api "github.com/docker/containerd/api/services/execution"
_ "github.com/docker/containerd/linux"
"github.com/docker/containerd/log"
"github.com/docker/containerd/services/execution"
"github.com/docker/containerd/utils"
metrics "github.com/docker/go-metrics"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
const usage = `
__ _ __
_________ ____ / /_____ _(_)___ ___ _________/ /
/ ___/ __ \/ __ \/ __/ __ ` + "`" + `/ / __ \/ _ \/ ___/ __ /
/ /__/ /_/ / / / / /_/ /_/ / / / / / __/ / / /_/ /
\___/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/ \__,_/
high performance container runtime
`
var global = log.WithModule(gocontext.Background(), "containerd")
func main() {
app := cli.NewApp()
app.Name = "containerd"
app.Version = containerd.Version
app.Usage = usage
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "enable debug output in logs",
},
cli.StringFlag{
Name: "log-level",
Usage: "Set the logging level [debug, info, warn, error, fatal, panic]",
Value: "info",
},
cli.StringFlag{
Name: "root",
Usage: "containerd state directory",
Value: "/run/containerd",
},
cli.StringFlag{
Name: "socket, s",
Usage: "socket path for containerd's GRPC server",
Value: "/run/containerd/containerd.sock",
},
cli.StringFlag{
Name: "debug-socket, d",
Usage: "socket path for containerd's debug server",
Value: "/run/containerd/containerd-debug.sock",
},
cli.StringFlag{
Name: "metrics-address, m",
Usage: "tcp address to serve metrics on",
Value: "127.0.0.1:7897",
},
}
app.Before = before
app.Action = func(context *cli.Context) error {
start := time.Now()
// start the signal handler as soon as we can to make sure that
// we don't miss any signals during boot
signals := make(chan os.Signal, 2048)
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGUSR1)
log.G(global).Info("starting containerd boot...")
runtimes, err := loadRuntimes(context)
if err != nil {
return err
}
supervisor, err := containerd.NewSupervisor(log.WithModule(global, "execution"), runtimes)
if err != nil {
return err
}
// start debug and metrics APIs
if err := serveDebugAPI(context); err != nil {
return err
}
serveMetricsAPI(context)
// start the GRPC api with the execution service registered
server := newGRPCServer(execution.New(supervisor))
if err := serveGRPC(context, server); err != nil {
return err
}
log.G(global).Infof("containerd successfully booted in %fs", time.Now().Sub(start).Seconds())
return handleSignals(signals, server)
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "containerd: %s\n", err)
os.Exit(1)
}
}
func before(context *cli.Context) error {
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
if l := context.GlobalString("log-level"); l != "" {
lvl, err := logrus.ParseLevel(l)
if err != nil {
lvl = logrus.InfoLevel
fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n, and being defaulted to info", l)
}
logrus.SetLevel(lvl)
}
return nil
}
func serveMetricsAPI(context *cli.Context) {
if addr := context.GlobalString("metrics-address"); addr != "" {
log.G(global).WithField("metrics", addr).Info("starting metrics API...")
h := newMetricsHandler()
go func() {
if err := http.ListenAndServe(addr, h); err != nil {
log.G(global).WithError(err).Fatal("serve metrics API")
}
}()
}
}
func newMetricsHandler() http.Handler {
m := http.NewServeMux()
m.Handle("/metrics", metrics.Handler())
return m
}
func serveDebugAPI(context *cli.Context) error {
path := context.GlobalString("debug-socket")
if path == "" {
return errors.New("--debug-socket path cannot be empty")
}
l, err := utils.CreateUnixSocket(path)
if err != nil {
return err
}
log.G(global).WithField("debug", path).Info("starting debug API...")
go func() {
defer l.Close()
// pprof and expvars are imported and automatically register their endpoints
// under /debug
if err := http.Serve(l, nil); err != nil {
log.G(global).WithError(err).Fatal("serve debug API")
}
}()
return nil
}
func loadRuntimes(context *cli.Context) (map[string]containerd.Runtime, error) {
var (
root = context.GlobalString("root")
o = make(map[string]containerd.Runtime)
)
for _, name := range containerd.Runtimes() {
r, err := containerd.NewRuntime(name, root)
if err != nil {
return nil, err
}
o[name] = r
log.G(global).WithField("runtime", name).Info("load runtime")
}
return o, nil
}
func newGRPCServer(service api.ContainerServiceServer) *grpc.Server {
s := grpc.NewServer(grpc.UnaryInterceptor(interceptor))
api.RegisterContainerServiceServer(s, service)
return s
}
func serveGRPC(context *cli.Context, server *grpc.Server) error {
path := context.GlobalString("socket")
if path == "" {
return errors.New("--socket path cannot be empty")
}
l, err := utils.CreateUnixSocket(path)
if err != nil {
return err
}
go func() {
defer l.Close()
if err := server.Serve(l); err != nil {
log.G(global).WithError(err).Fatal("serve GRPC")
}
}()
return nil
}
func interceptor(ctx gocontext.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
ctx = log.WithModule(ctx, "containerd")
switch info.Server.(type) {
case api.ContainerServiceServer:
ctx = log.WithModule(global, "execution")
default:
fmt.Printf("unknown GRPC server type: %#v\n", info.Server)
}
return handler(global, req)
}
func handleSignals(signals chan os.Signal, server *grpc.Server) error {
for s := range signals {
log.G(global).WithField("signal", s).Debug("received signal")
switch s {
default:
server.Stop()
return nil
}
}
return nil
}