config.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. package daemon
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "runtime"
  10. "strings"
  11. "sync"
  12. "github.com/Sirupsen/logrus"
  13. "github.com/docker/docker/opts"
  14. "github.com/docker/docker/pkg/discovery"
  15. "github.com/docker/docker/registry"
  16. "github.com/imdario/mergo"
  17. "github.com/spf13/pflag"
  18. )
  19. const (
  20. // defaultMaxConcurrentDownloads is the default value for
  21. // maximum number of downloads that
  22. // may take place at a time for each pull.
  23. defaultMaxConcurrentDownloads = 3
  24. // defaultMaxConcurrentUploads is the default value for
  25. // maximum number of uploads that
  26. // may take place at a time for each push.
  27. defaultMaxConcurrentUploads = 5
  28. // stockRuntimeName is the reserved name/alias used to represent the
  29. // OCI runtime being shipped with the docker daemon package.
  30. stockRuntimeName = "runc"
  31. )
  32. const (
  33. defaultNetworkMtu = 1500
  34. disableNetworkBridge = "none"
  35. )
  36. const (
  37. defaultShutdownTimeout = 15
  38. )
  39. // flatOptions contains configuration keys
  40. // that MUST NOT be parsed as deep structures.
  41. // Use this to differentiate these options
  42. // with others like the ones in CommonTLSOptions.
  43. var flatOptions = map[string]bool{
  44. "cluster-store-opts": true,
  45. "log-opts": true,
  46. "runtimes": true,
  47. "default-ulimits": true,
  48. }
  49. // LogConfig represents the default log configuration.
  50. // It includes json tags to deserialize configuration from a file
  51. // using the same names that the flags in the command line use.
  52. type LogConfig struct {
  53. Type string `json:"log-driver,omitempty"`
  54. Config map[string]string `json:"log-opts,omitempty"`
  55. }
  56. // commonBridgeConfig stores all the platform-common bridge driver specific
  57. // configuration.
  58. type commonBridgeConfig struct {
  59. Iface string `json:"bridge,omitempty"`
  60. FixedCIDR string `json:"fixed-cidr,omitempty"`
  61. }
  62. // CommonTLSOptions defines TLS configuration for the daemon server.
  63. // It includes json tags to deserialize configuration from a file
  64. // using the same names that the flags in the command line use.
  65. type CommonTLSOptions struct {
  66. CAFile string `json:"tlscacert,omitempty"`
  67. CertFile string `json:"tlscert,omitempty"`
  68. KeyFile string `json:"tlskey,omitempty"`
  69. }
  70. // CommonConfig defines the configuration of a docker daemon which is
  71. // common across platforms.
  72. // It includes json tags to deserialize configuration from a file
  73. // using the same names that the flags in the command line use.
  74. type CommonConfig struct {
  75. AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
  76. AutoRestart bool `json:"-"`
  77. Context map[string][]string `json:"-"`
  78. DisableBridge bool `json:"-"`
  79. DNS []string `json:"dns,omitempty"`
  80. DNSOptions []string `json:"dns-opts,omitempty"`
  81. DNSSearch []string `json:"dns-search,omitempty"`
  82. ExecOptions []string `json:"exec-opts,omitempty"`
  83. GraphDriver string `json:"storage-driver,omitempty"`
  84. GraphOptions []string `json:"storage-opts,omitempty"`
  85. Labels []string `json:"labels,omitempty"`
  86. Mtu int `json:"mtu,omitempty"`
  87. Pidfile string `json:"pidfile,omitempty"`
  88. RawLogs bool `json:"raw-logs,omitempty"`
  89. Root string `json:"graph,omitempty"`
  90. SocketGroup string `json:"group,omitempty"`
  91. TrustKeyPath string `json:"-"`
  92. CorsHeaders string `json:"api-cors-header,omitempty"`
  93. EnableCors bool `json:"api-enable-cors,omitempty"`
  94. // LiveRestoreEnabled determines whether we should keep containers
  95. // alive upon daemon shutdown/start
  96. LiveRestoreEnabled bool `json:"live-restore,omitempty"`
  97. // ClusterStore is the storage backend used for the cluster information. It is used by both
  98. // multihost networking (to store networks and endpoints information) and by the node discovery
  99. // mechanism.
  100. ClusterStore string `json:"cluster-store,omitempty"`
  101. // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
  102. // as TLS configuration settings.
  103. ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
  104. // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
  105. // discovery. This should be a 'host:port' combination on which that daemon instance is
  106. // reachable by other hosts.
  107. ClusterAdvertise string `json:"cluster-advertise,omitempty"`
  108. // MaxConcurrentDownloads is the maximum number of downloads that
  109. // may take place at a time for each pull.
  110. MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"`
  111. // MaxConcurrentUploads is the maximum number of uploads that
  112. // may take place at a time for each push.
  113. MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"`
  114. // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container
  115. // to stop when daemon is being shutdown
  116. ShutdownTimeout int `json:"shutdown-timeout,omitempty"`
  117. Debug bool `json:"debug,omitempty"`
  118. Hosts []string `json:"hosts,omitempty"`
  119. LogLevel string `json:"log-level,omitempty"`
  120. TLS bool `json:"tls,omitempty"`
  121. TLSVerify bool `json:"tlsverify,omitempty"`
  122. // Embedded structs that allow config
  123. // deserialization without the full struct.
  124. CommonTLSOptions
  125. // SwarmDefaultAdvertiseAddr is the default host/IP or network interface
  126. // to use if a wildcard address is specified in the ListenAddr value
  127. // given to the /swarm/init endpoint and no advertise address is
  128. // specified.
  129. SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"`
  130. MetricsAddress string `json:"metrics-addr"`
  131. LogConfig
  132. bridgeConfig // bridgeConfig holds bridge network specific configuration.
  133. registry.ServiceOptions
  134. reloadLock sync.Mutex
  135. valuesSet map[string]interface{}
  136. Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not
  137. }
  138. // InstallCommonFlags adds flags to the pflag.FlagSet to configure the daemon
  139. func (config *Config) InstallCommonFlags(flags *pflag.FlagSet) {
  140. var maxConcurrentDownloads, maxConcurrentUploads int
  141. config.ServiceOptions.InstallCliFlags(flags)
  142. flags.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), "storage-opt", "Storage driver options")
  143. flags.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), "authorization-plugin", "Authorization plugins to load")
  144. flags.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), "exec-opt", "Runtime execution options")
  145. flags.StringVarP(&config.Pidfile, "pidfile", "p", defaultPidFile, "Path to use for daemon PID file")
  146. flags.StringVarP(&config.Root, "graph", "g", defaultGraph, "Root of the Docker runtime")
  147. flags.BoolVarP(&config.AutoRestart, "restart", "r", true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
  148. flags.MarkDeprecated("restart", "Please use a restart policy on docker run")
  149. flags.StringVarP(&config.GraphDriver, "storage-driver", "s", "", "Storage driver to use")
  150. flags.IntVar(&config.Mtu, "mtu", 0, "Set the containers network MTU")
  151. flags.BoolVar(&config.RawLogs, "raw-logs", false, "Full timestamps without ANSI coloring")
  152. // FIXME: why the inconsistency between "hosts" and "sockets"?
  153. flags.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), "dns", "DNS server to use")
  154. flags.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), "dns-opt", "DNS options to use")
  155. flags.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), "dns-search", "DNS search domains to use")
  156. flags.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), "label", "Set key=value labels to the daemon")
  157. flags.StringVar(&config.LogConfig.Type, "log-driver", "json-file", "Default driver for container logs")
  158. flags.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), "log-opt", "Default log driver options for containers")
  159. flags.StringVar(&config.ClusterAdvertise, "cluster-advertise", "", "Address or interface name to advertise")
  160. flags.StringVar(&config.ClusterStore, "cluster-store", "", "URL of the distributed storage backend")
  161. flags.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), "cluster-store-opt", "Set cluster store options")
  162. flags.StringVar(&config.CorsHeaders, "api-cors-header", "", "Set CORS headers in the Engine API")
  163. flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", defaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull")
  164. flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", defaultMaxConcurrentUploads, "Set the max concurrent uploads for each push")
  165. flags.IntVar(&config.ShutdownTimeout, "shutdown-timeout", defaultShutdownTimeout, "Set the default shutdown timeout")
  166. flags.StringVar(&config.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address")
  167. flags.BoolVar(&config.Experimental, "experimental", false, "Enable experimental features")
  168. flags.StringVar(&config.MetricsAddress, "metrics-addr", "", "Set default address and port to serve the metrics api on")
  169. config.MaxConcurrentDownloads = &maxConcurrentDownloads
  170. config.MaxConcurrentUploads = &maxConcurrentUploads
  171. }
  172. // IsValueSet returns true if a configuration value
  173. // was explicitly set in the configuration file.
  174. func (config *Config) IsValueSet(name string) bool {
  175. if config.valuesSet == nil {
  176. return false
  177. }
  178. _, ok := config.valuesSet[name]
  179. return ok
  180. }
  181. // NewConfig returns a new fully initialized Config struct
  182. func NewConfig() *Config {
  183. config := Config{}
  184. config.LogConfig.Config = make(map[string]string)
  185. config.ClusterOpts = make(map[string]string)
  186. if runtime.GOOS != "linux" {
  187. config.V2Only = true
  188. }
  189. return &config
  190. }
  191. func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
  192. if runtime.GOOS == "solaris" && (clusterAdvertise != "" || clusterStore != "") {
  193. return "", errors.New("Cluster Advertise Settings not supported on Solaris")
  194. }
  195. if clusterAdvertise == "" {
  196. return "", errDiscoveryDisabled
  197. }
  198. if clusterStore == "" {
  199. return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
  200. }
  201. advertise, err := discovery.ParseAdvertise(clusterAdvertise)
  202. if err != nil {
  203. return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
  204. }
  205. return advertise, nil
  206. }
  207. // GetConflictFreeLabels validate Labels for conflict
  208. // In swarm the duplicates for labels are removed
  209. // so we only take same values here, no conflict values
  210. // If the key-value is the same we will only take the last label
  211. func GetConflictFreeLabels(labels []string) ([]string, error) {
  212. labelMap := map[string]string{}
  213. for _, label := range labels {
  214. stringSlice := strings.SplitN(label, "=", 2)
  215. if len(stringSlice) > 1 {
  216. // If there is a conflict we will return an error
  217. if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] {
  218. return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v)
  219. }
  220. labelMap[stringSlice[0]] = stringSlice[1]
  221. }
  222. }
  223. newLabels := []string{}
  224. for k, v := range labelMap {
  225. newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v))
  226. }
  227. return newLabels, nil
  228. }
  229. // ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
  230. func ReloadConfiguration(configFile string, flags *pflag.FlagSet, reload func(*Config)) error {
  231. logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
  232. newConfig, err := getConflictFreeConfiguration(configFile, flags)
  233. if err != nil {
  234. return err
  235. }
  236. if err := ValidateConfiguration(newConfig); err != nil {
  237. return fmt.Errorf("file configuration validation failed (%v)", err)
  238. }
  239. // Labels of the docker engine used to allow multiple values associated with the same key.
  240. // This is deprecated in 1.13, and, be removed after 3 release cycles.
  241. // The following will check the conflict of labels, and report a warning for deprecation.
  242. //
  243. // TODO: After 3 release cycles (1.16) an error will be returned, and labels will be
  244. // sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
  245. //
  246. // newLabels, err := GetConflictFreeLabels(newConfig.Labels)
  247. // if err != nil {
  248. // return err
  249. // }
  250. // newConfig.Labels = newLabels
  251. //
  252. if _, err := GetConflictFreeLabels(newConfig.Labels); err != nil {
  253. logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
  254. }
  255. reload(newConfig)
  256. return nil
  257. }
  258. // boolValue is an interface that boolean value flags implement
  259. // to tell the command line how to make -name equivalent to -name=true.
  260. type boolValue interface {
  261. IsBoolFlag() bool
  262. }
  263. // MergeDaemonConfigurations reads a configuration file,
  264. // loads the file configuration in an isolated structure,
  265. // and merges the configuration provided from flags on top
  266. // if there are no conflicts.
  267. func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) {
  268. fileConfig, err := getConflictFreeConfiguration(configFile, flags)
  269. if err != nil {
  270. return nil, err
  271. }
  272. if err := ValidateConfiguration(fileConfig); err != nil {
  273. return nil, fmt.Errorf("file configuration validation failed (%v)", err)
  274. }
  275. // merge flags configuration on top of the file configuration
  276. if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
  277. return nil, err
  278. }
  279. // We need to validate again once both fileConfig and flagsConfig
  280. // have been merged
  281. if err := ValidateConfiguration(fileConfig); err != nil {
  282. return nil, fmt.Errorf("file configuration validation failed (%v)", err)
  283. }
  284. return fileConfig, nil
  285. }
  286. // getConflictFreeConfiguration loads the configuration from a JSON file.
  287. // It compares that configuration with the one provided by the flags,
  288. // and returns an error if there are conflicts.
  289. func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) {
  290. b, err := ioutil.ReadFile(configFile)
  291. if err != nil {
  292. return nil, err
  293. }
  294. var config Config
  295. var reader io.Reader
  296. if flags != nil {
  297. var jsonConfig map[string]interface{}
  298. reader = bytes.NewReader(b)
  299. if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
  300. return nil, err
  301. }
  302. configSet := configValuesSet(jsonConfig)
  303. if err := findConfigurationConflicts(configSet, flags); err != nil {
  304. return nil, err
  305. }
  306. // Override flag values to make sure the values set in the config file with nullable values, like `false`,
  307. // are not overridden by default truthy values from the flags that were not explicitly set.
  308. // See https://github.com/docker/docker/issues/20289 for an example.
  309. //
  310. // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
  311. namedOptions := make(map[string]interface{})
  312. for key, value := range configSet {
  313. f := flags.Lookup(key)
  314. if f == nil { // ignore named flags that don't match
  315. namedOptions[key] = value
  316. continue
  317. }
  318. if _, ok := f.Value.(boolValue); ok {
  319. f.Value.Set(fmt.Sprintf("%v", value))
  320. }
  321. }
  322. if len(namedOptions) > 0 {
  323. // set also default for mergeVal flags that are boolValue at the same time.
  324. flags.VisitAll(func(f *pflag.Flag) {
  325. if opt, named := f.Value.(opts.NamedOption); named {
  326. v, set := namedOptions[opt.Name()]
  327. _, boolean := f.Value.(boolValue)
  328. if set && boolean {
  329. f.Value.Set(fmt.Sprintf("%v", v))
  330. }
  331. }
  332. })
  333. }
  334. config.valuesSet = configSet
  335. }
  336. reader = bytes.NewReader(b)
  337. err = json.NewDecoder(reader).Decode(&config)
  338. return &config, err
  339. }
  340. // configValuesSet returns the configuration values explicitly set in the file.
  341. func configValuesSet(config map[string]interface{}) map[string]interface{} {
  342. flatten := make(map[string]interface{})
  343. for k, v := range config {
  344. if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
  345. for km, vm := range m {
  346. flatten[km] = vm
  347. }
  348. continue
  349. }
  350. flatten[k] = v
  351. }
  352. return flatten
  353. }
  354. // findConfigurationConflicts iterates over the provided flags searching for
  355. // duplicated configurations and unknown keys. It returns an error with all the conflicts if
  356. // it finds any.
  357. func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error {
  358. // 1. Search keys from the file that we don't recognize as flags.
  359. unknownKeys := make(map[string]interface{})
  360. for key, value := range config {
  361. if flag := flags.Lookup(key); flag == nil {
  362. unknownKeys[key] = value
  363. }
  364. }
  365. // 2. Discard values that implement NamedOption.
  366. // Their configuration name differs from their flag name, like `labels` and `label`.
  367. if len(unknownKeys) > 0 {
  368. unknownNamedConflicts := func(f *pflag.Flag) {
  369. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  370. if _, valid := unknownKeys[namedOption.Name()]; valid {
  371. delete(unknownKeys, namedOption.Name())
  372. }
  373. }
  374. }
  375. flags.VisitAll(unknownNamedConflicts)
  376. }
  377. if len(unknownKeys) > 0 {
  378. var unknown []string
  379. for key := range unknownKeys {
  380. unknown = append(unknown, key)
  381. }
  382. return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
  383. }
  384. var conflicts []string
  385. printConflict := func(name string, flagValue, fileValue interface{}) string {
  386. return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
  387. }
  388. // 3. Search keys that are present as a flag and as a file option.
  389. duplicatedConflicts := func(f *pflag.Flag) {
  390. // search option name in the json configuration payload if the value is a named option
  391. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  392. if optsValue, ok := config[namedOption.Name()]; ok {
  393. conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
  394. }
  395. } else {
  396. // search flag name in the json configuration payload
  397. for _, name := range []string{f.Name, f.Shorthand} {
  398. if value, ok := config[name]; ok {
  399. conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
  400. break
  401. }
  402. }
  403. }
  404. }
  405. flags.Visit(duplicatedConflicts)
  406. if len(conflicts) > 0 {
  407. return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
  408. }
  409. return nil
  410. }
  411. // ValidateConfiguration validates some specific configs.
  412. // such as config.DNS, config.Labels, config.DNSSearch,
  413. // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads.
  414. func ValidateConfiguration(config *Config) error {
  415. // validate DNS
  416. for _, dns := range config.DNS {
  417. if _, err := opts.ValidateIPAddress(dns); err != nil {
  418. return err
  419. }
  420. }
  421. // validate DNSSearch
  422. for _, dnsSearch := range config.DNSSearch {
  423. if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
  424. return err
  425. }
  426. }
  427. // validate Labels
  428. for _, label := range config.Labels {
  429. if _, err := opts.ValidateLabel(label); err != nil {
  430. return err
  431. }
  432. }
  433. // validate MaxConcurrentDownloads
  434. if config.IsValueSet("max-concurrent-downloads") && config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 {
  435. return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads)
  436. }
  437. // validate MaxConcurrentUploads
  438. if config.IsValueSet("max-concurrent-uploads") && config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 {
  439. return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads)
  440. }
  441. // validate that "default" runtime is not reset
  442. if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
  443. if _, ok := runtimes[stockRuntimeName]; ok {
  444. return fmt.Errorf("runtime name '%s' is reserved", stockRuntimeName)
  445. }
  446. }
  447. if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != stockRuntimeName {
  448. runtimes := config.GetAllRuntimes()
  449. if _, ok := runtimes[defaultRuntime]; !ok {
  450. return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
  451. }
  452. }
  453. return nil
  454. }