| package daemon // import "github.com/docker/docker/daemon" |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "strconv" |
| |
| "github.com/containerd/log" |
| "github.com/docker/docker/api/types/events" |
| "github.com/hashicorp/go-multierror" |
| "github.com/mitchellh/copystructure" |
| |
| "github.com/docker/docker/daemon/config" |
| ) |
| |
| // reloadTxn is used to defer side effects of a config reload. |
| type reloadTxn struct { |
| onCommit, onRollback []func() error |
| } |
| |
| // OnCommit defers a function to be called when a config reload is being finalized. |
| // The error returned from cb is purely informational. |
| func (tx *reloadTxn) OnCommit(cb func() error) { |
| tx.onCommit = append(tx.onCommit, cb) |
| } |
| |
| // OnRollback defers a function to be called when a config reload is aborted. |
| // The error returned from cb is purely informational. |
| func (tx *reloadTxn) OnRollback(cb func() error) { |
| tx.onCommit = append(tx.onRollback, cb) |
| } |
| |
| func (tx *reloadTxn) run(cbs []func() error) error { |
| tx.onCommit = nil |
| tx.onRollback = nil |
| |
| var res *multierror.Error |
| for _, cb := range cbs { |
| res = multierror.Append(res, cb()) |
| } |
| return res.ErrorOrNil() |
| } |
| |
| // Commit calls all functions registered with OnCommit. |
| // Any errors returned by the functions are collated into a |
| // *github.com/hashicorp/go-multierror.Error value. |
| func (tx *reloadTxn) Commit() error { |
| return tx.run(tx.onCommit) |
| } |
| |
| // Rollback calls all functions registered with OnRollback. |
| // Any errors returned by the functions are collated into a |
| // *github.com/hashicorp/go-multierror.Error value. |
| func (tx *reloadTxn) Rollback() error { |
| return tx.run(tx.onRollback) |
| } |
| |
| // Reload modifies the live daemon configuration from conf. |
| // conf is assumed to be a validated configuration. |
| // |
| // These are the settings that Reload changes: |
| // - Platform runtime |
| // - Daemon debug log level |
| // - Daemon max concurrent downloads |
| // - Daemon max concurrent uploads |
| // - Daemon max download attempts |
| // - Daemon shutdown timeout (in seconds) |
| // - Cluster discovery (reconfigure and restart) |
| // - Daemon labels |
| // - Insecure registries |
| // - Registry mirrors |
| // - Daemon live restore |
| func (daemon *Daemon) Reload(conf *config.Config) error { |
| daemon.configReload.Lock() |
| defer daemon.configReload.Unlock() |
| copied, err := copystructure.Copy(daemon.config().Config) |
| if err != nil { |
| return err |
| } |
| newCfg := &configStore{ |
| Config: copied.(config.Config), |
| } |
| |
| attributes := map[string]string{} |
| |
| // Ideally reloading should be transactional: the reload either completes |
| // successfully, or the daemon config and state are left untouched. We use a |
| // two-phase commit protocol to achieve this. Any fallible reload operation is |
| // split into two phases. The first phase performs all the fallible operations |
| // and mutates the newCfg copy. The second phase atomically swaps newCfg into |
| // the live daemon configuration and executes any commit functions the first |
| // phase registered to apply the side effects. If any first-phase returns an |
| // error, the reload transaction is rolled back by discarding newCfg and |
| // executing any registered rollback functions. |
| |
| var txn reloadTxn |
| for _, reload := range []func(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error{ |
| daemon.reloadPlatform, |
| daemon.reloadDebug, |
| daemon.reloadMaxConcurrentDownloadsAndUploads, |
| daemon.reloadMaxDownloadAttempts, |
| daemon.reloadShutdownTimeout, |
| daemon.reloadFeatures, |
| daemon.reloadLabels, |
| daemon.reloadRegistryConfig, |
| daemon.reloadLiveRestore, |
| daemon.reloadNetworkDiagnosticPort, |
| } { |
| if err := reload(&txn, newCfg, conf, attributes); err != nil { |
| if rollbackErr := txn.Rollback(); rollbackErr != nil { |
| return multierror.Append(nil, err, rollbackErr) |
| } |
| return err |
| } |
| } |
| |
| jsonString, _ := json.Marshal(&struct { |
| *config.Config |
| config.Proxies `json:"proxies"` |
| }{ |
| Config: &newCfg.Config, |
| Proxies: config.Proxies{ |
| HTTPProxy: config.MaskCredentials(newCfg.HTTPProxy), |
| HTTPSProxy: config.MaskCredentials(newCfg.HTTPSProxy), |
| NoProxy: config.MaskCredentials(newCfg.NoProxy), |
| }, |
| }) |
| log.G(context.TODO()).Infof("Reloaded configuration: %s", jsonString) |
| daemon.configStore.Store(newCfg) |
| daemon.LogDaemonEventWithAttributes(events.ActionReload, attributes) |
| return txn.Commit() |
| } |
| |
| func marshalAttributeSlice(v []string) string { |
| if v == nil { |
| return "[]" |
| } |
| b, err := json.Marshal(v) |
| if err != nil { |
| panic(err) // Should never happen as the input type is fixed. |
| } |
| return string(b) |
| } |
| |
| // reloadDebug updates configuration with Debug option |
| // and updates the passed attributes |
| func (daemon *Daemon) reloadDebug(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // update corresponding configuration |
| if conf.IsValueSet("debug") { |
| newCfg.Debug = conf.Debug |
| } |
| // prepare reload event attributes with updatable configurations |
| attributes["debug"] = strconv.FormatBool(newCfg.Debug) |
| return nil |
| } |
| |
| // reloadMaxConcurrentDownloadsAndUploads updates configuration with max concurrent |
| // download and upload options and updates the passed attributes |
| func (daemon *Daemon) reloadMaxConcurrentDownloadsAndUploads(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // We always "reset" as the cost is lightweight and easy to maintain. |
| newCfg.MaxConcurrentDownloads = config.DefaultMaxConcurrentDownloads |
| newCfg.MaxConcurrentUploads = config.DefaultMaxConcurrentUploads |
| |
| if conf.IsValueSet("max-concurrent-downloads") && conf.MaxConcurrentDownloads != 0 { |
| newCfg.MaxConcurrentDownloads = conf.MaxConcurrentDownloads |
| } |
| if conf.IsValueSet("max-concurrent-uploads") && conf.MaxConcurrentUploads != 0 { |
| newCfg.MaxConcurrentUploads = conf.MaxConcurrentUploads |
| } |
| txn.OnCommit(func() error { |
| if daemon.imageService != nil { |
| daemon.imageService.UpdateConfig( |
| newCfg.MaxConcurrentDownloads, |
| newCfg.MaxConcurrentUploads, |
| ) |
| } |
| return nil |
| }) |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["max-concurrent-downloads"] = strconv.Itoa(newCfg.MaxConcurrentDownloads) |
| attributes["max-concurrent-uploads"] = strconv.Itoa(newCfg.MaxConcurrentUploads) |
| log.G(context.TODO()).Debug("Reset Max Concurrent Downloads: ", attributes["max-concurrent-downloads"]) |
| log.G(context.TODO()).Debug("Reset Max Concurrent Uploads: ", attributes["max-concurrent-uploads"]) |
| return nil |
| } |
| |
| // reloadMaxDownloadAttempts updates configuration with max concurrent |
| // download attempts when a connection is lost and updates the passed attributes |
| func (daemon *Daemon) reloadMaxDownloadAttempts(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // We always "reset" as the cost is lightweight and easy to maintain. |
| newCfg.MaxDownloadAttempts = config.DefaultDownloadAttempts |
| if conf.IsValueSet("max-download-attempts") && conf.MaxDownloadAttempts != 0 { |
| newCfg.MaxDownloadAttempts = conf.MaxDownloadAttempts |
| } |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["max-download-attempts"] = strconv.Itoa(newCfg.MaxDownloadAttempts) |
| log.G(context.TODO()).Debug("Reset Max Download Attempts: ", attributes["max-download-attempts"]) |
| return nil |
| } |
| |
| // reloadShutdownTimeout updates configuration with daemon shutdown timeout option |
| // and updates the passed attributes |
| func (daemon *Daemon) reloadShutdownTimeout(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // update corresponding configuration |
| if conf.IsValueSet("shutdown-timeout") { |
| newCfg.ShutdownTimeout = conf.ShutdownTimeout |
| log.G(context.TODO()).Debugf("Reset Shutdown Timeout: %d", newCfg.ShutdownTimeout) |
| } |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["shutdown-timeout"] = strconv.Itoa(newCfg.ShutdownTimeout) |
| return nil |
| } |
| |
| // reloadLabels updates configuration with engine labels |
| // and updates the passed attributes |
| func (daemon *Daemon) reloadLabels(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // update corresponding configuration |
| if conf.IsValueSet("labels") { |
| newCfg.Labels = conf.Labels |
| } |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["labels"] = marshalAttributeSlice(newCfg.Labels) |
| return nil |
| } |
| |
| // reloadRegistryConfig updates the configuration with registry options |
| // and updates the passed attributes. |
| func (daemon *Daemon) reloadRegistryConfig(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // Update corresponding configuration. |
| if conf.IsValueSet("allow-nondistributable-artifacts") { |
| newCfg.ServiceOptions.AllowNondistributableArtifacts = conf.AllowNondistributableArtifacts |
| } |
| if conf.IsValueSet("insecure-registries") { |
| newCfg.ServiceOptions.InsecureRegistries = conf.InsecureRegistries |
| } |
| if conf.IsValueSet("registry-mirrors") { |
| newCfg.ServiceOptions.Mirrors = conf.Mirrors |
| } |
| |
| commit, err := daemon.registryService.ReplaceConfig(newCfg.ServiceOptions) |
| if err != nil { |
| return err |
| } |
| txn.OnCommit(func() error { commit(); return nil }) |
| |
| attributes["allow-nondistributable-artifacts"] = marshalAttributeSlice(newCfg.ServiceOptions.AllowNondistributableArtifacts) |
| attributes["insecure-registries"] = marshalAttributeSlice(newCfg.ServiceOptions.InsecureRegistries) |
| attributes["registry-mirrors"] = marshalAttributeSlice(newCfg.ServiceOptions.Mirrors) |
| |
| return nil |
| } |
| |
| // reloadLiveRestore updates configuration with live restore option |
| // and updates the passed attributes |
| func (daemon *Daemon) reloadLiveRestore(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // update corresponding configuration |
| if conf.IsValueSet("live-restore") { |
| newCfg.LiveRestoreEnabled = conf.LiveRestoreEnabled |
| } |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["live-restore"] = strconv.FormatBool(newCfg.LiveRestoreEnabled) |
| return nil |
| } |
| |
| // reloadNetworkDiagnosticPort updates the network controller starting the diagnostic if the config is valid |
| func (daemon *Daemon) reloadNetworkDiagnosticPort(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| txn.OnCommit(func() error { |
| if conf == nil || daemon.netController == nil || !conf.IsValueSet("network-diagnostic-port") || |
| conf.NetworkDiagnosticPort < 1 || conf.NetworkDiagnosticPort > 65535 { |
| // If there is no config make sure that the diagnostic is off |
| if daemon.netController != nil { |
| daemon.netController.StopDiagnostic() |
| } |
| return nil |
| } |
| // Enable the network diagnostic if the flag is set with a valid port within the range |
| log.G(context.TODO()).WithFields(log.Fields{"port": conf.NetworkDiagnosticPort, "ip": "127.0.0.1"}).Warn("Starting network diagnostic server") |
| daemon.netController.StartDiagnostic(conf.NetworkDiagnosticPort) |
| return nil |
| }) |
| return nil |
| } |
| |
| // reloadFeatures updates configuration with enabled/disabled features |
| func (daemon *Daemon) reloadFeatures(txn *reloadTxn, newCfg *configStore, conf *config.Config, attributes map[string]string) error { |
| // update corresponding configuration |
| // note that we allow features option to be entirely unset |
| newCfg.Features = conf.Features |
| |
| // prepare reload event attributes with updatable configurations |
| attributes["features"] = fmt.Sprintf("%v", newCfg.Features) |
| return nil |
| } |