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 }