github.com/sameo/containerd@v0.2.8/containerd/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net"
     7  	"os"
     8  	"os/signal"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"google.golang.org/grpc"
    16  	"google.golang.org/grpc/health"
    17  	"google.golang.org/grpc/health/grpc_health_v1"
    18  
    19  	"github.com/Sirupsen/logrus"
    20  	"github.com/codegangsta/cli"
    21  	"github.com/cyberdelia/go-metrics-graphite"
    22  	"github.com/docker/containerd"
    23  	grpcserver "github.com/docker/containerd/api/grpc/server"
    24  	"github.com/docker/containerd/api/grpc/types"
    25  	"github.com/docker/containerd/api/http/pprof"
    26  	"github.com/docker/containerd/supervisor"
    27  	"github.com/docker/docker/pkg/listeners"
    28  	"github.com/rcrowley/go-metrics"
    29  )
    30  
    31  const (
    32  	usage               = `High performance container daemon`
    33  	minRlimit           = 1024
    34  	defaultStateDir     = "/run/containerd"
    35  	defaultGRPCEndpoint = "unix:///run/containerd/containerd.sock"
    36  )
    37  
    38  var daemonFlags = []cli.Flag{
    39  	cli.BoolFlag{
    40  		Name:  "debug",
    41  		Usage: "enable debug output in the logs",
    42  	},
    43  	cli.StringFlag{
    44  		Name:  "log-level",
    45  		Usage: "Set the logging level [debug, info, warn, error, fatal, panic]",
    46  	},
    47  	cli.StringFlag{
    48  		Name:  "state-dir",
    49  		Value: defaultStateDir,
    50  		Usage: "runtime state directory",
    51  	},
    52  	cli.DurationFlag{
    53  		Name:  "metrics-interval",
    54  		Value: 5 * time.Minute,
    55  		Usage: "interval for flushing metrics to the store",
    56  	},
    57  	cli.StringFlag{
    58  		Name:  "listen,l",
    59  		Value: defaultGRPCEndpoint,
    60  		Usage: "proto://address on which the GRPC API will listen",
    61  	},
    62  	cli.StringFlag{
    63  		Name:  "runtime,r",
    64  		Value: "runc",
    65  		Usage: "name or path of the OCI compliant runtime to use when executing containers",
    66  	},
    67  	cli.StringSliceFlag{
    68  		Name:  "runtime-args",
    69  		Value: &cli.StringSlice{},
    70  		Usage: "specify additional runtime args",
    71  	},
    72  	cli.StringFlag{
    73  		Name:  "shim",
    74  		Value: "containerd-shim",
    75  		Usage: "Name or path of shim",
    76  	},
    77  	cli.StringFlag{
    78  		Name:  "pprof-address",
    79  		Usage: "http address to listen for pprof events",
    80  	},
    81  	cli.DurationFlag{
    82  		Name:  "start-timeout",
    83  		Value: 30 * time.Second,
    84  		Usage: "timeout duration for waiting on a container to start before it is killed",
    85  	},
    86  	cli.IntFlag{
    87  		Name:  "retain-count",
    88  		Value: 500,
    89  		Usage: "number of past events to keep in the event log",
    90  	},
    91  	cli.StringFlag{
    92  		Name:  "graphite-address",
    93  		Usage: "Address of graphite server",
    94  	},
    95  }
    96  
    97  // DumpStacks dumps the runtime stack.
    98  func dumpStacks() {
    99  	var (
   100  		buf       []byte
   101  		stackSize int
   102  	)
   103  	bufferLen := 16384
   104  	for stackSize == len(buf) {
   105  		buf = make([]byte, bufferLen)
   106  		stackSize = runtime.Stack(buf, true)
   107  		bufferLen *= 2
   108  	}
   109  	buf = buf[:stackSize]
   110  	logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
   111  }
   112  
   113  func setupDumpStacksTrap() {
   114  	c := make(chan os.Signal, 1)
   115  	signal.Notify(c, syscall.SIGUSR1)
   116  	go func() {
   117  		for range c {
   118  			dumpStacks()
   119  		}
   120  	}()
   121  }
   122  
   123  func main() {
   124  	logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: time.RFC3339Nano})
   125  	app := cli.NewApp()
   126  	app.Name = "containerd"
   127  	if containerd.GitCommit != "" {
   128  		app.Version = fmt.Sprintf("%s commit: %s", containerd.Version, containerd.GitCommit)
   129  	} else {
   130  		app.Version = containerd.Version
   131  	}
   132  	app.Usage = usage
   133  	app.Flags = daemonFlags
   134  	app.Before = func(context *cli.Context) error {
   135  		setupDumpStacksTrap()
   136  		if context.GlobalBool("debug") {
   137  			logrus.SetLevel(logrus.DebugLevel)
   138  			if context.GlobalDuration("metrics-interval") > 0 {
   139  				if err := debugMetrics(context.GlobalDuration("metrics-interval"), context.GlobalString("graphite-address")); err != nil {
   140  					return err
   141  				}
   142  			}
   143  		}
   144  		if logLevel := context.GlobalString("log-level"); logLevel != "" {
   145  			lvl, err := logrus.ParseLevel(logLevel)
   146  			if err != nil {
   147  				lvl = logrus.InfoLevel
   148  				fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n, and being defaulted to info", logLevel)
   149  			}
   150  			logrus.SetLevel(lvl)
   151  		}
   152  		if p := context.GlobalString("pprof-address"); len(p) > 0 {
   153  			pprof.Enable(p)
   154  		}
   155  		if err := checkLimits(); err != nil {
   156  			return err
   157  		}
   158  		return nil
   159  	}
   160  
   161  	app.Action = func(context *cli.Context) {
   162  		if err := daemon(context); err != nil {
   163  			logrus.Fatal(err)
   164  		}
   165  	}
   166  	if err := app.Run(os.Args); err != nil {
   167  		logrus.Fatal(err)
   168  	}
   169  }
   170  
   171  func daemon(context *cli.Context) error {
   172  	stateDir := context.String("state-dir")
   173  	if err := os.MkdirAll(stateDir, 0755); err != nil {
   174  		return err
   175  	}
   176  	s := make(chan os.Signal, 2048)
   177  	signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
   178  	// Split the listen string of the form proto://addr
   179  	listenSpec := context.String("listen")
   180  	listenParts := strings.SplitN(listenSpec, "://", 2)
   181  	if len(listenParts) != 2 {
   182  		return fmt.Errorf("bad listen address format %s, expected proto://address", listenSpec)
   183  	}
   184  	// Register server early to allow healthcheck to be done
   185  	server, err := startServer(listenParts[0], listenParts[1])
   186  	if err != nil {
   187  		return err
   188  	}
   189  	sv, err := supervisor.New(
   190  		stateDir,
   191  		context.String("runtime"),
   192  		context.String("shim"),
   193  		context.StringSlice("runtime-args"),
   194  		context.Duration("start-timeout"),
   195  		context.Int("retain-count"))
   196  	if err != nil {
   197  		return err
   198  	}
   199  	types.RegisterAPIServer(server, grpcserver.NewServer(sv))
   200  	wg := &sync.WaitGroup{}
   201  	for i := 0; i < 10; i++ {
   202  		wg.Add(1)
   203  		w := supervisor.NewWorker(sv, wg)
   204  		go w.Start()
   205  	}
   206  	if err := sv.Start(); err != nil {
   207  		return err
   208  	}
   209  	for ss := range s {
   210  		switch ss {
   211  		default:
   212  			logrus.Infof("stopping containerd after receiving %s", ss)
   213  			server.Stop()
   214  			os.Exit(0)
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  func startServer(protocol, address string) (*grpc.Server, error) {
   221  	// TODO: We should use TLS.
   222  	// TODO: Add an option for the SocketGroup.
   223  	sockets, err := listeners.Init(protocol, address, "", nil)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	if len(sockets) != 1 {
   228  		return nil, fmt.Errorf("incorrect number of listeners")
   229  	}
   230  	l := sockets[0]
   231  	s := grpc.NewServer()
   232  	healthServer := health.NewServer()
   233  	grpc_health_v1.RegisterHealthServer(s, healthServer)
   234  
   235  	go func() {
   236  		logrus.Debugf("containerd: grpc api on %s", address)
   237  		if err := s.Serve(l); err != nil {
   238  			logrus.WithField("error", err).Fatal("containerd: serve grpc")
   239  		}
   240  	}()
   241  	return s, nil
   242  }
   243  
   244  func checkLimits() error {
   245  	var l syscall.Rlimit
   246  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &l); err != nil {
   247  		return err
   248  	}
   249  	if l.Cur <= minRlimit {
   250  		logrus.WithFields(logrus.Fields{
   251  			"current": l.Cur,
   252  			"max":     l.Max,
   253  		}).Warn("containerd: low RLIMIT_NOFILE changing to max")
   254  		l.Cur = l.Max
   255  		return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &l)
   256  	}
   257  	return nil
   258  }
   259  
   260  func debugMetrics(interval time.Duration, graphiteAddr string) error {
   261  	for name, m := range supervisor.Metrics() {
   262  		if err := metrics.DefaultRegistry.Register(name, m); err != nil {
   263  			return err
   264  		}
   265  	}
   266  	processMetrics()
   267  	if graphiteAddr != "" {
   268  		addr, err := net.ResolveTCPAddr("tcp", graphiteAddr)
   269  		if err != nil {
   270  			return err
   271  		}
   272  		go graphite.Graphite(metrics.DefaultRegistry, 10e9, "metrics", addr)
   273  	} else {
   274  		l := log.New(os.Stdout, "[containerd] ", log.LstdFlags)
   275  		go metrics.Log(metrics.DefaultRegistry, interval, l)
   276  	}
   277  	return nil
   278  }