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