config.go 18 KB

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