1package config // import "github.com/docker/docker/daemon/config" 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net" 10 "os" 11 "reflect" 12 "strings" 13 "sync" 14 15 daemondiscovery "github.com/docker/docker/daemon/discovery" 16 "github.com/docker/docker/opts" 17 "github.com/docker/docker/pkg/authorization" 18 "github.com/docker/docker/pkg/discovery" 19 "github.com/docker/docker/registry" 20 "github.com/imdario/mergo" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 "github.com/spf13/pflag" 24) 25 26const ( 27 // DefaultMaxConcurrentDownloads is the default value for 28 // maximum number of downloads that 29 // may take place at a time for each pull. 30 DefaultMaxConcurrentDownloads = 3 31 // DefaultMaxConcurrentUploads is the default value for 32 // maximum number of uploads that 33 // may take place at a time for each push. 34 DefaultMaxConcurrentUploads = 5 35 // DefaultDownloadAttempts is the default value for 36 // maximum number of attempts that 37 // may take place at a time for each pull when the connection is lost. 38 DefaultDownloadAttempts = 5 39 // DefaultShmSize is the default value for container's shm size 40 DefaultShmSize = int64(67108864) 41 // DefaultNetworkMtu is the default value for network MTU 42 DefaultNetworkMtu = 1500 43 // DisableNetworkBridge is the default value of the option to disable network bridge 44 DisableNetworkBridge = "none" 45 // DefaultInitBinary is the name of the default init binary 46 DefaultInitBinary = "docker-init" 47 48 // StockRuntimeName is the reserved name/alias used to represent the 49 // OCI runtime being shipped with the docker daemon package. 50 StockRuntimeName = "runc" 51 // LinuxV1RuntimeName is the runtime used to specify the containerd v1 shim with the runc binary 52 // Note this is different than io.containerd.runc.v1 which would be the v1 shim using the v2 shim API. 53 // This is specifically for the v1 shim using the v1 shim API. 54 LinuxV1RuntimeName = "io.containerd.runtime.v1.linux" 55 // LinuxV2RuntimeName is the runtime used to specify the containerd v2 runc shim 56 LinuxV2RuntimeName = "io.containerd.runc.v2" 57) 58 59var builtinRuntimes = map[string]bool{ 60 StockRuntimeName: true, 61 LinuxV1RuntimeName: true, 62 LinuxV2RuntimeName: true, 63} 64 65// flatOptions contains configuration keys 66// that MUST NOT be parsed as deep structures. 67// Use this to differentiate these options 68// with others like the ones in CommonTLSOptions. 69var flatOptions = map[string]bool{ 70 "cluster-store-opts": true, 71 "log-opts": true, 72 "runtimes": true, 73 "default-ulimits": true, 74 "features": true, 75 "builder": true, 76} 77 78// skipValidateOptions contains configuration keys 79// that will be skipped from findConfigurationConflicts 80// for unknown flag validation. 81var skipValidateOptions = map[string]bool{ 82 "features": true, 83 "builder": true, 84 // Corresponding flag has been removed because it was already unusable 85 "deprecated-key-path": true, 86} 87 88// skipDuplicates contains configuration keys that 89// will be skipped when checking duplicated 90// configuration field defined in both daemon 91// config file and from dockerd cli flags. 92// This allows some configurations to be merged 93// during the parsing. 94var skipDuplicates = map[string]bool{ 95 "runtimes": true, 96} 97 98// LogConfig represents the default log configuration. 99// It includes json tags to deserialize configuration from a file 100// using the same names that the flags in the command line use. 101type LogConfig struct { 102 Type string `json:"log-driver,omitempty"` 103 Config map[string]string `json:"log-opts,omitempty"` 104} 105 106// commonBridgeConfig stores all the platform-common bridge driver specific 107// configuration. 108type commonBridgeConfig struct { 109 Iface string `json:"bridge,omitempty"` 110 FixedCIDR string `json:"fixed-cidr,omitempty"` 111} 112 113// NetworkConfig stores the daemon-wide networking configurations 114type NetworkConfig struct { 115 // Default address pools for docker networks 116 DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` 117 // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components 118 NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` 119} 120 121// CommonTLSOptions defines TLS configuration for the daemon server. 122// It includes json tags to deserialize configuration from a file 123// using the same names that the flags in the command line use. 124type CommonTLSOptions struct { 125 CAFile string `json:"tlscacert,omitempty"` 126 CertFile string `json:"tlscert,omitempty"` 127 KeyFile string `json:"tlskey,omitempty"` 128} 129 130// DNSConfig defines the DNS configurations. 131type DNSConfig struct { 132 DNS []string `json:"dns,omitempty"` 133 DNSOptions []string `json:"dns-opts,omitempty"` 134 DNSSearch []string `json:"dns-search,omitempty"` 135 HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"` 136} 137 138// CommonConfig defines the configuration of a docker daemon which is 139// common across platforms. 140// It includes json tags to deserialize configuration from a file 141// using the same names that the flags in the command line use. 142type CommonConfig struct { 143 AuthzMiddleware *authorization.Middleware `json:"-"` 144 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins 145 AutoRestart bool `json:"-"` 146 Context map[string][]string `json:"-"` 147 DisableBridge bool `json:"-"` 148 ExecOptions []string `json:"exec-opts,omitempty"` 149 GraphDriver string `json:"storage-driver,omitempty"` 150 GraphOptions []string `json:"storage-opts,omitempty"` 151 Labels []string `json:"labels,omitempty"` 152 Mtu int `json:"mtu,omitempty"` 153 NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` 154 Pidfile string `json:"pidfile,omitempty"` 155 RawLogs bool `json:"raw-logs,omitempty"` 156 RootDeprecated string `json:"graph,omitempty"` 157 Root string `json:"data-root,omitempty"` 158 ExecRoot string `json:"exec-root,omitempty"` 159 SocketGroup string `json:"group,omitempty"` 160 CorsHeaders string `json:"api-cors-header,omitempty"` 161 162 // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests 163 // when pushing to a registry which does not support schema 2. This field is marked as 164 // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the 165 // daemon ID will use a dedicated identifier not shared with exported signatures. 166 TrustKeyPath string `json:"deprecated-key-path,omitempty"` 167 168 // LiveRestoreEnabled determines whether we should keep containers 169 // alive upon daemon shutdown/start 170 LiveRestoreEnabled bool `json:"live-restore,omitempty"` 171 172 // ClusterStore is the storage backend used for the cluster information. It is used by both 173 // multihost networking (to store networks and endpoints information) and by the node discovery 174 // mechanism. 175 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 176 ClusterStore string `json:"cluster-store,omitempty"` 177 178 // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such 179 // as TLS configuration settings. 180 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 181 ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"` 182 183 // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node 184 // discovery. This should be a 'host:port' combination on which that daemon instance is 185 // reachable by other hosts. 186 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 187 ClusterAdvertise string `json:"cluster-advertise,omitempty"` 188 189 // MaxConcurrentDownloads is the maximum number of downloads that 190 // may take place at a time for each pull. 191 MaxConcurrentDownloads *int `json:"max-concurrent-downloads,omitempty"` 192 193 // MaxConcurrentUploads is the maximum number of uploads that 194 // may take place at a time for each push. 195 MaxConcurrentUploads *int `json:"max-concurrent-uploads,omitempty"` 196 197 // MaxDownloadAttempts is the maximum number of attempts that 198 // may take place at a time for each push. 199 MaxDownloadAttempts *int `json:"max-download-attempts,omitempty"` 200 201 // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container 202 // to stop when daemon is being shutdown 203 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 204 205 Debug bool `json:"debug,omitempty"` 206 Hosts []string `json:"hosts,omitempty"` 207 LogLevel string `json:"log-level,omitempty"` 208 TLS *bool `json:"tls,omitempty"` 209 TLSVerify *bool `json:"tlsverify,omitempty"` 210 211 // Embedded structs that allow config 212 // deserialization without the full struct. 213 CommonTLSOptions 214 215 // SwarmDefaultAdvertiseAddr is the default host/IP or network interface 216 // to use if a wildcard address is specified in the ListenAddr value 217 // given to the /swarm/init endpoint and no advertise address is 218 // specified. 219 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"` 220 221 // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat 222 // Typical value is 1 223 SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"` 224 225 // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose 226 // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick. 227 // Higher values can make the quorum less sensitive to transient faults in the environment, but this also 228 // means it takes longer for the managers to detect a down leader. 229 SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"` 230 231 MetricsAddress string `json:"metrics-addr"` 232 233 DNSConfig 234 LogConfig 235 BridgeConfig // bridgeConfig holds bridge network specific configuration. 236 NetworkConfig 237 registry.ServiceOptions 238 239 sync.Mutex 240 // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags 241 // It should probably be handled outside this package. 242 ValuesSet map[string]interface{} `json:"-"` 243 244 Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not 245 246 // Exposed node Generic Resources 247 // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"] 248 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 249 250 // ContainerAddr is the address used to connect to containerd if we're 251 // not starting it ourselves 252 ContainerdAddr string `json:"containerd,omitempty"` 253 254 // CriContainerd determines whether a supervised containerd instance 255 // should be configured with the CRI plugin enabled. This allows using 256 // Docker's containerd instance directly with a Kubernetes kubelet. 257 CriContainerd bool `json:"cri-containerd,omitempty"` 258 259 // Features contains a list of feature key value pairs indicating what features are enabled or disabled. 260 // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false). 261 Features map[string]bool `json:"features,omitempty"` 262 263 Builder BuilderConfig `json:"builder,omitempty"` 264 265 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 266 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 267} 268 269// IsValueSet returns true if a configuration value 270// was explicitly set in the configuration file. 271func (conf *Config) IsValueSet(name string) bool { 272 if conf.ValuesSet == nil { 273 return false 274 } 275 _, ok := conf.ValuesSet[name] 276 return ok 277} 278 279// New returns a new fully initialized Config struct 280func New() *Config { 281 config := Config{} 282 config.LogConfig.Config = make(map[string]string) 283 config.ClusterOpts = make(map[string]string) 284 return &config 285} 286 287// ParseClusterAdvertiseSettings parses the specified advertise settings 288func ParseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) { 289 if clusterAdvertise == "" { 290 return "", daemondiscovery.ErrDiscoveryDisabled 291 } 292 if clusterStore == "" { 293 return "", errors.New("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration") 294 } 295 296 advertise, err := discovery.ParseAdvertise(clusterAdvertise) 297 if err != nil { 298 return "", errors.Wrap(err, "discovery advertise parsing failed") 299 } 300 return advertise, nil 301} 302 303// GetConflictFreeLabels validates Labels for conflict 304// In swarm the duplicates for labels are removed 305// so we only take same values here, no conflict values 306// If the key-value is the same we will only take the last label 307func GetConflictFreeLabels(labels []string) ([]string, error) { 308 labelMap := map[string]string{} 309 for _, label := range labels { 310 stringSlice := strings.SplitN(label, "=", 2) 311 if len(stringSlice) > 1 { 312 // If there is a conflict we will return an error 313 if v, ok := labelMap[stringSlice[0]]; ok && v != stringSlice[1] { 314 return nil, fmt.Errorf("conflict labels for %s=%s and %s=%s", stringSlice[0], stringSlice[1], stringSlice[0], v) 315 } 316 labelMap[stringSlice[0]] = stringSlice[1] 317 } 318 } 319 320 newLabels := []string{} 321 for k, v := range labelMap { 322 newLabels = append(newLabels, fmt.Sprintf("%s=%s", k, v)) 323 } 324 return newLabels, nil 325} 326 327// Reload reads the configuration in the host and reloads the daemon and server. 328func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { 329 logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) 330 newConfig, err := getConflictFreeConfiguration(configFile, flags) 331 if err != nil { 332 if flags.Changed("config-file") || !os.IsNotExist(err) { 333 return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile) 334 } 335 newConfig = New() 336 } 337 338 if err := Validate(newConfig); err != nil { 339 return errors.Wrap(err, "file configuration validation failed") 340 } 341 342 // Check if duplicate label-keys with different values are found 343 newLabels, err := GetConflictFreeLabels(newConfig.Labels) 344 if err != nil { 345 return err 346 } 347 newConfig.Labels = newLabels 348 349 reload(newConfig) 350 return nil 351} 352 353// boolValue is an interface that boolean value flags implement 354// to tell the command line how to make -name equivalent to -name=true. 355type boolValue interface { 356 IsBoolFlag() bool 357} 358 359// MergeDaemonConfigurations reads a configuration file, 360// loads the file configuration in an isolated structure, 361// and merges the configuration provided from flags on top 362// if there are no conflicts. 363func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { 364 fileConfig, err := getConflictFreeConfiguration(configFile, flags) 365 if err != nil { 366 return nil, err 367 } 368 369 if err := Validate(fileConfig); err != nil { 370 return nil, errors.Wrap(err, "configuration validation from file failed") 371 } 372 373 // merge flags configuration on top of the file configuration 374 if err := mergo.Merge(fileConfig, flagsConfig); err != nil { 375 return nil, err 376 } 377 378 // We need to validate again once both fileConfig and flagsConfig 379 // have been merged 380 if err := Validate(fileConfig); err != nil { 381 return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed") 382 } 383 384 return fileConfig, nil 385} 386 387// getConflictFreeConfiguration loads the configuration from a JSON file. 388// It compares that configuration with the one provided by the flags, 389// and returns an error if there are conflicts. 390func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { 391 b, err := ioutil.ReadFile(configFile) 392 if err != nil { 393 return nil, err 394 } 395 396 var config Config 397 var reader io.Reader 398 if flags != nil { 399 var jsonConfig map[string]interface{} 400 reader = bytes.NewReader(b) 401 if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil { 402 return nil, err 403 } 404 405 configSet := configValuesSet(jsonConfig) 406 407 if err := findConfigurationConflicts(configSet, flags); err != nil { 408 return nil, err 409 } 410 411 // Override flag values to make sure the values set in the config file with nullable values, like `false`, 412 // are not overridden by default truthy values from the flags that were not explicitly set. 413 // See https://github.com/docker/docker/issues/20289 for an example. 414 // 415 // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. 416 namedOptions := make(map[string]interface{}) 417 for key, value := range configSet { 418 f := flags.Lookup(key) 419 if f == nil { // ignore named flags that don't match 420 namedOptions[key] = value 421 continue 422 } 423 424 if _, ok := f.Value.(boolValue); ok { 425 f.Value.Set(fmt.Sprintf("%v", value)) 426 } 427 } 428 if len(namedOptions) > 0 { 429 // set also default for mergeVal flags that are boolValue at the same time. 430 flags.VisitAll(func(f *pflag.Flag) { 431 if opt, named := f.Value.(opts.NamedOption); named { 432 v, set := namedOptions[opt.Name()] 433 _, boolean := f.Value.(boolValue) 434 if set && boolean { 435 f.Value.Set(fmt.Sprintf("%v", v)) 436 } 437 } 438 }) 439 } 440 441 config.ValuesSet = configSet 442 } 443 444 reader = bytes.NewReader(b) 445 if err := json.NewDecoder(reader).Decode(&config); err != nil { 446 return nil, err 447 } 448 449 if config.RootDeprecated != "" { 450 logrus.Warn(`The "graph" config file option is deprecated. Please use "data-root" instead.`) 451 452 if config.Root != "" { 453 return nil, errors.New(`cannot specify both "graph" and "data-root" config file options`) 454 } 455 456 config.Root = config.RootDeprecated 457 } 458 459 return &config, nil 460} 461 462// configValuesSet returns the configuration values explicitly set in the file. 463func configValuesSet(config map[string]interface{}) map[string]interface{} { 464 flatten := make(map[string]interface{}) 465 for k, v := range config { 466 if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] { 467 for km, vm := range m { 468 flatten[km] = vm 469 } 470 continue 471 } 472 473 flatten[k] = v 474 } 475 return flatten 476} 477 478// findConfigurationConflicts iterates over the provided flags searching for 479// duplicated configurations and unknown keys. It returns an error with all the conflicts if 480// it finds any. 481func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { 482 // 1. Search keys from the file that we don't recognize as flags. 483 unknownKeys := make(map[string]interface{}) 484 for key, value := range config { 485 if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] { 486 unknownKeys[key] = value 487 } 488 } 489 490 // 2. Discard values that implement NamedOption. 491 // Their configuration name differs from their flag name, like `labels` and `label`. 492 if len(unknownKeys) > 0 { 493 unknownNamedConflicts := func(f *pflag.Flag) { 494 if namedOption, ok := f.Value.(opts.NamedOption); ok { 495 delete(unknownKeys, namedOption.Name()) 496 } 497 } 498 flags.VisitAll(unknownNamedConflicts) 499 } 500 501 if len(unknownKeys) > 0 { 502 var unknown []string 503 for key := range unknownKeys { 504 unknown = append(unknown, key) 505 } 506 return fmt.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", ")) 507 } 508 509 var conflicts []string 510 printConflict := func(name string, flagValue, fileValue interface{}) string { 511 return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) 512 } 513 514 // 3. Search keys that are present as a flag and as a file option. 515 duplicatedConflicts := func(f *pflag.Flag) { 516 // search option name in the json configuration payload if the value is a named option 517 if namedOption, ok := f.Value.(opts.NamedOption); ok { 518 if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] { 519 conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) 520 } 521 } else { 522 // search flag name in the json configuration payload 523 for _, name := range []string{f.Name, f.Shorthand} { 524 if value, ok := config[name]; ok && !skipDuplicates[name] { 525 conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) 526 break 527 } 528 } 529 } 530 } 531 532 flags.Visit(duplicatedConflicts) 533 534 if len(conflicts) > 0 { 535 return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", ")) 536 } 537 return nil 538} 539 540// Validate validates some specific configs. 541// such as config.DNS, config.Labels, config.DNSSearch, 542// as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts. 543func Validate(config *Config) error { 544 // validate DNS 545 for _, dns := range config.DNS { 546 if _, err := opts.ValidateIPAddress(dns); err != nil { 547 return err 548 } 549 } 550 551 // validate DNSSearch 552 for _, dnsSearch := range config.DNSSearch { 553 if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { 554 return err 555 } 556 } 557 558 // validate Labels 559 for _, label := range config.Labels { 560 if _, err := opts.ValidateLabel(label); err != nil { 561 return err 562 } 563 } 564 // validate MaxConcurrentDownloads 565 if config.MaxConcurrentDownloads != nil && *config.MaxConcurrentDownloads < 0 { 566 return fmt.Errorf("invalid max concurrent downloads: %d", *config.MaxConcurrentDownloads) 567 } 568 // validate MaxConcurrentUploads 569 if config.MaxConcurrentUploads != nil && *config.MaxConcurrentUploads < 0 { 570 return fmt.Errorf("invalid max concurrent uploads: %d", *config.MaxConcurrentUploads) 571 } 572 if err := ValidateMaxDownloadAttempts(config); err != nil { 573 return err 574 } 575 576 // validate that "default" runtime is not reset 577 if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 { 578 if _, ok := runtimes[StockRuntimeName]; ok { 579 return fmt.Errorf("runtime name '%s' is reserved", StockRuntimeName) 580 } 581 } 582 583 if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { 584 return err 585 } 586 587 if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" { 588 if !builtinRuntimes[defaultRuntime] { 589 runtimes := config.GetAllRuntimes() 590 if _, ok := runtimes[defaultRuntime]; !ok { 591 return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime) 592 } 593 } 594 } 595 596 // validate platform-specific settings 597 return config.ValidatePlatformConfig() 598} 599 600// ValidateMaxDownloadAttempts validates if the max-download-attempts is within the valid range 601func ValidateMaxDownloadAttempts(config *Config) error { 602 if config.MaxDownloadAttempts != nil && *config.MaxDownloadAttempts <= 0 { 603 return fmt.Errorf("invalid max download attempts: %d", *config.MaxDownloadAttempts) 604 } 605 return nil 606} 607 608// ModifiedDiscoverySettings returns whether the discovery configuration has been modified or not. 609func ModifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool { 610 if config.ClusterStore != backendType || config.ClusterAdvertise != advertise { 611 return true 612 } 613 614 if (config.ClusterOpts == nil && clusterOpts == nil) || 615 (config.ClusterOpts == nil && len(clusterOpts) == 0) || 616 (len(config.ClusterOpts) == 0 && clusterOpts == nil) { 617 return false 618 } 619 620 return !reflect.DeepEqual(config.ClusterOpts, clusterOpts) 621} 622