github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/cmd/dockerd/daemon.go (about) 1 package main 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 "time" 12 13 "github.com/docker/distribution/uuid" 14 "github.com/docker/docker/api" 15 apiserver "github.com/docker/docker/api/server" 16 buildbackend "github.com/docker/docker/api/server/backend/build" 17 "github.com/docker/docker/api/server/middleware" 18 "github.com/docker/docker/api/server/router" 19 "github.com/docker/docker/api/server/router/build" 20 checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" 21 "github.com/docker/docker/api/server/router/container" 22 distributionrouter "github.com/docker/docker/api/server/router/distribution" 23 "github.com/docker/docker/api/server/router/image" 24 "github.com/docker/docker/api/server/router/network" 25 pluginrouter "github.com/docker/docker/api/server/router/plugin" 26 sessionrouter "github.com/docker/docker/api/server/router/session" 27 swarmrouter "github.com/docker/docker/api/server/router/swarm" 28 systemrouter "github.com/docker/docker/api/server/router/system" 29 "github.com/docker/docker/api/server/router/volume" 30 "github.com/docker/docker/builder/dockerfile" 31 "github.com/docker/docker/builder/fscache" 32 "github.com/docker/docker/cli/debug" 33 "github.com/docker/docker/daemon" 34 "github.com/docker/docker/daemon/cluster" 35 "github.com/docker/docker/daemon/config" 36 "github.com/docker/docker/daemon/listeners" 37 "github.com/docker/docker/daemon/logger" 38 "github.com/docker/docker/dockerversion" 39 "github.com/docker/docker/libcontainerd" 40 dopts "github.com/docker/docker/opts" 41 "github.com/docker/docker/pkg/authorization" 42 "github.com/docker/docker/pkg/jsonmessage" 43 "github.com/docker/docker/pkg/pidfile" 44 "github.com/docker/docker/pkg/plugingetter" 45 "github.com/docker/docker/pkg/signal" 46 "github.com/docker/docker/pkg/system" 47 "github.com/docker/docker/plugin" 48 "github.com/docker/docker/registry" 49 "github.com/docker/docker/runconfig" 50 "github.com/docker/go-connections/tlsconfig" 51 swarmapi "github.com/docker/swarmkit/api" 52 "github.com/moby/buildkit/session" 53 "github.com/pkg/errors" 54 "github.com/sirupsen/logrus" 55 "github.com/spf13/pflag" 56 ) 57 58 // DaemonCli represents the daemon CLI. 59 type DaemonCli struct { 60 *config.Config 61 configFile *string 62 flags *pflag.FlagSet 63 64 api *apiserver.Server 65 d *daemon.Daemon 66 authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins 67 } 68 69 // NewDaemonCli returns a daemon CLI 70 func NewDaemonCli() *DaemonCli { 71 return &DaemonCli{} 72 } 73 74 func (cli *DaemonCli) start(opts *daemonOptions) (err error) { 75 stopc := make(chan bool) 76 defer close(stopc) 77 78 // warn from uuid package when running the daemon 79 uuid.Loggerf = logrus.Warnf 80 81 opts.SetDefaultOptions(opts.flags) 82 83 if cli.Config, err = loadDaemonCliConfig(opts); err != nil { 84 return err 85 } 86 cli.configFile = &opts.configFile 87 cli.flags = opts.flags 88 89 if cli.Config.Debug { 90 debug.Enable() 91 } 92 93 if cli.Config.Experimental { 94 logrus.Warn("Running experimental build") 95 } 96 97 logrus.SetFormatter(&logrus.TextFormatter{ 98 TimestampFormat: jsonmessage.RFC3339NanoFixed, 99 DisableColors: cli.Config.RawLogs, 100 FullTimestamp: true, 101 }) 102 103 system.InitLCOW(cli.Config.Experimental) 104 105 if err := setDefaultUmask(); err != nil { 106 return fmt.Errorf("Failed to set umask: %v", err) 107 } 108 109 if len(cli.LogConfig.Config) > 0 { 110 if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil { 111 return fmt.Errorf("Failed to set log opts: %v", err) 112 } 113 } 114 115 // Create the daemon root before we create ANY other files (PID, or migrate keys) 116 // to ensure the appropriate ACL is set (particularly relevant on Windows) 117 if err := daemon.CreateDaemonRoot(cli.Config); err != nil { 118 return err 119 } 120 121 if cli.Pidfile != "" { 122 pf, err := pidfile.New(cli.Pidfile) 123 if err != nil { 124 return fmt.Errorf("Error starting daemon: %v", err) 125 } 126 defer func() { 127 if err := pf.Remove(); err != nil { 128 logrus.Error(err) 129 } 130 }() 131 } 132 133 // TODO: extract to newApiServerConfig() 134 serverConfig := &apiserver.Config{ 135 Logging: true, 136 SocketGroup: cli.Config.SocketGroup, 137 Version: dockerversion.Version, 138 CorsHeaders: cli.Config.CorsHeaders, 139 } 140 141 if cli.Config.TLS { 142 tlsOptions := tlsconfig.Options{ 143 CAFile: cli.Config.CommonTLSOptions.CAFile, 144 CertFile: cli.Config.CommonTLSOptions.CertFile, 145 KeyFile: cli.Config.CommonTLSOptions.KeyFile, 146 ExclusiveRootPools: true, 147 } 148 149 if cli.Config.TLSVerify { 150 // server requires and verifies client's certificate 151 tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert 152 } 153 tlsConfig, err := tlsconfig.Server(tlsOptions) 154 if err != nil { 155 return err 156 } 157 serverConfig.TLSConfig = tlsConfig 158 } 159 160 if len(cli.Config.Hosts) == 0 { 161 cli.Config.Hosts = make([]string, 1) 162 } 163 164 cli.api = apiserver.New(serverConfig) 165 166 var hosts []string 167 168 for i := 0; i < len(cli.Config.Hosts); i++ { 169 var err error 170 if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { 171 return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) 172 } 173 174 protoAddr := cli.Config.Hosts[i] 175 protoAddrParts := strings.SplitN(protoAddr, "://", 2) 176 if len(protoAddrParts) != 2 { 177 return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) 178 } 179 180 proto := protoAddrParts[0] 181 addr := protoAddrParts[1] 182 183 // It's a bad idea to bind to TCP without tlsverify. 184 if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) { 185 logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]") 186 } 187 ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) 188 if err != nil { 189 return err 190 } 191 ls = wrapListeners(proto, ls) 192 // If we're binding to a TCP port, make sure that a container doesn't try to use it. 193 if proto == "tcp" { 194 if err := allocateDaemonPort(addr); err != nil { 195 return err 196 } 197 } 198 logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr) 199 hosts = append(hosts, protoAddrParts[1]) 200 cli.api.Accept(addr, ls...) 201 } 202 203 registryService, err := registry.NewService(cli.Config.ServiceOptions) 204 if err != nil { 205 return err 206 } 207 208 rOpts, err := cli.getRemoteOptions() 209 if err != nil { 210 return fmt.Errorf("Failed to generate containerd options: %s", err) 211 } 212 containerdRemote, err := libcontainerd.New(filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), rOpts...) 213 if err != nil { 214 return err 215 } 216 signal.Trap(func() { 217 cli.stop() 218 <-stopc // wait for daemonCli.start() to return 219 }, logrus.StandardLogger()) 220 221 // Notify that the API is active, but before daemon is set up. 222 preNotifySystem() 223 224 pluginStore := plugin.NewStore() 225 226 if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil { 227 logrus.Fatalf("Error creating middlewares: %v", err) 228 } 229 230 d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore) 231 if err != nil { 232 return fmt.Errorf("Error starting daemon: %v", err) 233 } 234 235 d.StoreHosts(hosts) 236 237 // validate after NewDaemon has restored enabled plugins. Dont change order. 238 if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil { 239 return fmt.Errorf("Error validating authorization plugin: %v", err) 240 } 241 242 // TODO: move into startMetricsServer() 243 if cli.Config.MetricsAddress != "" { 244 if !d.HasExperimental() { 245 return fmt.Errorf("metrics-addr is only supported when experimental is enabled") 246 } 247 if err := startMetricsServer(cli.Config.MetricsAddress); err != nil { 248 return err 249 } 250 } 251 252 // TODO: createAndStartCluster() 253 name, _ := os.Hostname() 254 255 // Use a buffered channel to pass changes from store watch API to daemon 256 // A buffer allows store watch API and daemon processing to not wait for each other 257 watchStream := make(chan *swarmapi.WatchMessage, 32) 258 259 c, err := cluster.New(cluster.Config{ 260 Root: cli.Config.Root, 261 Name: name, 262 Backend: d, 263 PluginBackend: d.PluginManager(), 264 NetworkSubnetsProvider: d, 265 DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr, 266 RuntimeRoot: cli.getSwarmRunRoot(), 267 WatchStream: watchStream, 268 }) 269 if err != nil { 270 logrus.Fatalf("Error creating cluster component: %v", err) 271 } 272 d.SetCluster(c) 273 err = c.Start() 274 if err != nil { 275 logrus.Fatalf("Error starting cluster component: %v", err) 276 } 277 278 // Restart all autostart containers which has a swarm endpoint 279 // and is not yet running now that we have successfully 280 // initialized the cluster. 281 d.RestartSwarmContainers() 282 283 logrus.Info("Daemon has completed initialization") 284 285 cli.d = d 286 287 routerOptions, err := newRouterOptions(cli.Config, d) 288 if err != nil { 289 return err 290 } 291 routerOptions.api = cli.api 292 routerOptions.cluster = c 293 294 initRouter(routerOptions) 295 296 // process cluster change notifications 297 watchCtx, cancel := context.WithCancel(context.Background()) 298 defer cancel() 299 go d.ProcessClusterNotifications(watchCtx, watchStream) 300 301 cli.setupConfigReloadTrap() 302 303 // The serve API routine never exits unless an error occurs 304 // We need to start it as a goroutine and wait on it so 305 // daemon doesn't exit 306 serveAPIWait := make(chan error) 307 go cli.api.Wait(serveAPIWait) 308 309 // after the daemon is done setting up we can notify systemd api 310 notifySystem() 311 312 // Daemon is fully initialized and handling API traffic 313 // Wait for serve API to complete 314 errAPI := <-serveAPIWait 315 c.Cleanup() 316 shutdownDaemon(d) 317 containerdRemote.Cleanup() 318 if errAPI != nil { 319 return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI) 320 } 321 322 return nil 323 } 324 325 type routerOptions struct { 326 sessionManager *session.Manager 327 buildBackend *buildbackend.Backend 328 buildCache *fscache.FSCache 329 daemon *daemon.Daemon 330 api *apiserver.Server 331 cluster *cluster.Cluster 332 } 333 334 func newRouterOptions(config *config.Config, daemon *daemon.Daemon) (routerOptions, error) { 335 opts := routerOptions{} 336 sm, err := session.NewManager() 337 if err != nil { 338 return opts, errors.Wrap(err, "failed to create sessionmanager") 339 } 340 341 builderStateDir := filepath.Join(config.Root, "builder") 342 343 buildCache, err := fscache.NewFSCache(fscache.Opt{ 344 Backend: fscache.NewNaiveCacheBackend(builderStateDir), 345 Root: builderStateDir, 346 GCPolicy: fscache.GCPolicy{ // TODO: expose this in config 347 MaxSize: 1024 * 1024 * 512, // 512MB 348 MaxKeepDuration: 7 * 24 * time.Hour, // 1 week 349 }, 350 }) 351 if err != nil { 352 return opts, errors.Wrap(err, "failed to create fscache") 353 } 354 355 manager, err := dockerfile.NewBuildManager(daemon, sm, buildCache, daemon.IDMappings()) 356 if err != nil { 357 return opts, err 358 } 359 360 bb, err := buildbackend.NewBackend(daemon, manager, buildCache) 361 if err != nil { 362 return opts, errors.Wrap(err, "failed to create buildmanager") 363 } 364 365 return routerOptions{ 366 sessionManager: sm, 367 buildBackend: bb, 368 buildCache: buildCache, 369 daemon: daemon, 370 }, nil 371 } 372 373 func (cli *DaemonCli) reloadConfig() { 374 reload := func(config *config.Config) { 375 376 // Revalidate and reload the authorization plugins 377 if err := validateAuthzPlugins(config.AuthorizationPlugins, cli.d.PluginStore); err != nil { 378 logrus.Fatalf("Error validating authorization plugin: %v", err) 379 return 380 } 381 cli.authzMiddleware.SetPlugins(config.AuthorizationPlugins) 382 383 if err := cli.d.Reload(config); err != nil { 384 logrus.Errorf("Error reconfiguring the daemon: %v", err) 385 return 386 } 387 388 if config.IsValueSet("debug") { 389 debugEnabled := debug.IsEnabled() 390 switch { 391 case debugEnabled && !config.Debug: // disable debug 392 debug.Disable() 393 case config.Debug && !debugEnabled: // enable debug 394 debug.Enable() 395 } 396 397 } 398 } 399 400 if err := config.Reload(*cli.configFile, cli.flags, reload); err != nil { 401 logrus.Error(err) 402 } 403 } 404 405 func (cli *DaemonCli) stop() { 406 cli.api.Close() 407 } 408 409 // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case 410 // d.Shutdown() is waiting too long to kill container or worst it's 411 // blocked there 412 func shutdownDaemon(d *daemon.Daemon) { 413 shutdownTimeout := d.ShutdownTimeout() 414 ch := make(chan struct{}) 415 go func() { 416 d.Shutdown() 417 close(ch) 418 }() 419 if shutdownTimeout < 0 { 420 <-ch 421 logrus.Debug("Clean shutdown succeeded") 422 return 423 } 424 select { 425 case <-ch: 426 logrus.Debug("Clean shutdown succeeded") 427 case <-time.After(time.Duration(shutdownTimeout) * time.Second): 428 logrus.Error("Force shutdown daemon") 429 } 430 } 431 432 func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) { 433 conf := opts.daemonConfig 434 flags := opts.flags 435 conf.Debug = opts.Debug 436 conf.Hosts = opts.Hosts 437 conf.LogLevel = opts.LogLevel 438 conf.TLS = opts.TLS 439 conf.TLSVerify = opts.TLSVerify 440 conf.CommonTLSOptions = config.CommonTLSOptions{} 441 442 if opts.TLSOptions != nil { 443 conf.CommonTLSOptions.CAFile = opts.TLSOptions.CAFile 444 conf.CommonTLSOptions.CertFile = opts.TLSOptions.CertFile 445 conf.CommonTLSOptions.KeyFile = opts.TLSOptions.KeyFile 446 } 447 448 if conf.TrustKeyPath == "" { 449 conf.TrustKeyPath = filepath.Join( 450 getDaemonConfDir(conf.Root), 451 defaultTrustKeyFile) 452 } 453 454 if flags.Changed("graph") && flags.Changed("data-root") { 455 return nil, fmt.Errorf(`cannot specify both "--graph" and "--data-root" option`) 456 } 457 458 if opts.configFile != "" { 459 c, err := config.MergeDaemonConfigurations(conf, flags, opts.configFile) 460 if err != nil { 461 if flags.Changed("config-file") || !os.IsNotExist(err) { 462 return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v", opts.configFile, err) 463 } 464 } 465 // the merged configuration can be nil if the config file didn't exist. 466 // leave the current configuration as it is if when that happens. 467 if c != nil { 468 conf = c 469 } 470 } 471 472 if err := config.Validate(conf); err != nil { 473 return nil, err 474 } 475 476 if runtime.GOOS != "windows" { 477 if flags.Changed("disable-legacy-registry") { 478 // TODO: Remove this error after 3 release cycles (18.03) 479 return nil, errors.New("ERROR: The '--disable-legacy-registry' flag has been removed. Interacting with legacy (v1) registries is no longer supported") 480 } 481 if !conf.V2Only { 482 // TODO: Remove this error after 3 release cycles (18.03) 483 return nil, errors.New("ERROR: The 'disable-legacy-registry' configuration option has been removed. Interacting with legacy (v1) registries is no longer supported") 484 } 485 } 486 487 if flags.Changed("graph") { 488 logrus.Warnf(`The "-g / --graph" flag is deprecated. Please use "--data-root" instead`) 489 } 490 491 // Check if duplicate label-keys with different values are found 492 newLabels, err := config.GetConflictFreeLabels(conf.Labels) 493 if err != nil { 494 return nil, err 495 } 496 conf.Labels = newLabels 497 498 // Regardless of whether the user sets it to true or false, if they 499 // specify TLSVerify at all then we need to turn on TLS 500 if conf.IsValueSet(FlagTLSVerify) { 501 conf.TLS = true 502 } 503 504 // ensure that the log level is the one set after merging configurations 505 setLogLevel(conf.LogLevel) 506 507 return conf, nil 508 } 509 510 func initRouter(opts routerOptions) { 511 decoder := runconfig.ContainerDecoder{} 512 513 routers := []router.Router{ 514 // we need to add the checkpoint router before the container router or the DELETE gets masked 515 checkpointrouter.NewRouter(opts.daemon, decoder), 516 container.NewRouter(opts.daemon, decoder), 517 image.NewRouter(opts.daemon, decoder), 518 systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache), 519 volume.NewRouter(opts.daemon), 520 build.NewRouter(opts.buildBackend, opts.daemon), 521 sessionrouter.NewRouter(opts.sessionManager), 522 swarmrouter.NewRouter(opts.cluster), 523 pluginrouter.NewRouter(opts.daemon.PluginManager()), 524 distributionrouter.NewRouter(opts.daemon), 525 } 526 527 if opts.daemon.NetworkControllerEnabled() { 528 routers = append(routers, network.NewRouter(opts.daemon, opts.cluster)) 529 } 530 531 if opts.daemon.HasExperimental() { 532 for _, r := range routers { 533 for _, route := range r.Routes() { 534 if experimental, ok := route.(router.ExperimentalRoute); ok { 535 experimental.Enable() 536 } 537 } 538 } 539 } 540 541 opts.api.InitRouter(routers...) 542 } 543 544 // TODO: remove this from cli and return the authzMiddleware 545 func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config, pluginStore plugingetter.PluginGetter) error { 546 v := cfg.Version 547 548 exp := middleware.NewExperimentalMiddleware(cli.Config.Experimental) 549 s.UseMiddleware(exp) 550 551 vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion) 552 s.UseMiddleware(vm) 553 554 if cfg.CorsHeaders != "" { 555 c := middleware.NewCORSMiddleware(cfg.CorsHeaders) 556 s.UseMiddleware(c) 557 } 558 559 cli.authzMiddleware = authorization.NewMiddleware(cli.Config.AuthorizationPlugins, pluginStore) 560 cli.Config.AuthzMiddleware = cli.authzMiddleware 561 s.UseMiddleware(cli.authzMiddleware) 562 return nil 563 } 564 565 func (cli *DaemonCli) getRemoteOptions() ([]libcontainerd.RemoteOption, error) { 566 opts := []libcontainerd.RemoteOption{} 567 568 pOpts, err := cli.getPlatformRemoteOptions() 569 if err != nil { 570 return nil, err 571 } 572 opts = append(opts, pOpts...) 573 return opts, nil 574 } 575 576 // validates that the plugins requested with the --authorization-plugin flag are valid AuthzDriver 577 // plugins present on the host and available to the daemon 578 func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGetter) error { 579 for _, reqPlugin := range requestedPlugins { 580 if _, err := pg.Get(reqPlugin, authorization.AuthZApiImplements, plugingetter.Lookup); err != nil { 581 return err 582 } 583 } 584 return nil 585 }