зеркало из https://github.com/microsoft/docker.git
Allow to set daemon and server configurations in a file.
Read configuration after flags making this the priority: 1- Apply configuration from file. 2- Apply configuration from flags. Reload configuration when a signal is received, USR2 in Linux: - Reload router if the debug configuration changes. - Reload daemon labels. - Reload cluster discovery. Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
Родитель
22a81a2c58
Коммит
677a6b3506
|
@ -0,0 +1,30 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// routerSwapper is an http.Handler that allow you to swap
|
||||
// mux routers.
|
||||
type routerSwapper struct {
|
||||
mu sync.Mutex
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// Swap changes the old router with the new one.
|
||||
func (rs *routerSwapper) Swap(newRouter *mux.Router) {
|
||||
rs.mu.Lock()
|
||||
rs.router = newRouter
|
||||
rs.mu.Unlock()
|
||||
}
|
||||
|
||||
// ServeHTTP makes the routerSwapper to implement the http.Handler interface.
|
||||
func (rs *routerSwapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rs.mu.Lock()
|
||||
router := rs.router
|
||||
rs.mu.Unlock()
|
||||
router.ServeHTTP(w, r)
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
@ -42,10 +41,11 @@ type Config struct {
|
|||
|
||||
// Server contains instance details for the server
|
||||
type Server struct {
|
||||
cfg *Config
|
||||
servers []*HTTPServer
|
||||
routers []router.Router
|
||||
authZPlugins []authorization.Plugin
|
||||
cfg *Config
|
||||
servers []*HTTPServer
|
||||
routers []router.Router
|
||||
authZPlugins []authorization.Plugin
|
||||
routerSwapper *routerSwapper
|
||||
}
|
||||
|
||||
// Addr contains string representation of address and its protocol (tcp, unix...).
|
||||
|
@ -80,12 +80,14 @@ func (s *Server) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
// ServeAPI loops through all initialized servers and spawns goroutine
|
||||
// with Server method for each. It sets CreateMux() as Handler also.
|
||||
func (s *Server) ServeAPI() error {
|
||||
// serveAPI loops through all initialized servers and spawns goroutine
|
||||
// with Server method for each. It sets createMux() as Handler also.
|
||||
func (s *Server) serveAPI() error {
|
||||
s.initRouterSwapper()
|
||||
|
||||
var chErrors = make(chan error, len(s.servers))
|
||||
for _, srv := range s.servers {
|
||||
srv.srv.Handler = s.CreateMux()
|
||||
srv.srv.Handler = s.routerSwapper
|
||||
go func(srv *HTTPServer) {
|
||||
var err error
|
||||
logrus.Infof("API listen on %s", srv.l.Addr())
|
||||
|
@ -186,11 +188,11 @@ func (s *Server) addRouter(r router.Router) {
|
|||
s.routers = append(s.routers, r)
|
||||
}
|
||||
|
||||
// CreateMux initializes the main router the server uses.
|
||||
// createMux initializes the main router the server uses.
|
||||
// we keep enableCors just for legacy usage, need to be removed in the future
|
||||
func (s *Server) CreateMux() *mux.Router {
|
||||
func (s *Server) createMux() *mux.Router {
|
||||
m := mux.NewRouter()
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
if utils.IsDebugEnabled() {
|
||||
profilerSetup(m, "/debug/")
|
||||
}
|
||||
|
||||
|
@ -207,3 +209,36 @@ func (s *Server) CreateMux() *mux.Router {
|
|||
|
||||
return m
|
||||
}
|
||||
|
||||
// Wait blocks the server goroutine until it exits.
|
||||
// It sends an error message if there is any error during
|
||||
// the API execution.
|
||||
func (s *Server) Wait(waitChan chan error) {
|
||||
if err := s.serveAPI(); err != nil {
|
||||
logrus.Errorf("ServeAPI error: %v", err)
|
||||
waitChan <- err
|
||||
return
|
||||
}
|
||||
waitChan <- nil
|
||||
}
|
||||
|
||||
func (s *Server) initRouterSwapper() {
|
||||
s.routerSwapper = &routerSwapper{
|
||||
router: s.createMux(),
|
||||
}
|
||||
}
|
||||
|
||||
// Reload reads configuration changes and modifies the
|
||||
// server according to those changes.
|
||||
// Currently, only the --debug configuration is taken into account.
|
||||
func (s *Server) Reload(config *daemon.Config) {
|
||||
debugEnabled := utils.IsDebugEnabled()
|
||||
switch {
|
||||
case debugEnabled && !config.Debug: // disable debug
|
||||
utils.DisableDebug()
|
||||
s.routerSwapper.Swap(s.createMux())
|
||||
case config.Debug && !debugEnabled: // enable debug
|
||||
utils.EnableDebug()
|
||||
s.routerSwapper.Swap(s.createMux())
|
||||
}
|
||||
}
|
||||
|
|
216
daemon/config.go
216
daemon/config.go
|
@ -1,9 +1,19 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/discovery"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -11,42 +21,69 @@ const (
|
|||
disableNetworkBridge = "none"
|
||||
)
|
||||
|
||||
// LogConfig represents the default log configuration.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type LogConfig struct {
|
||||
Type string `json:"log-driver,omitempty"`
|
||||
Config map[string]string `json:"log-opts,omitempty"`
|
||||
}
|
||||
|
||||
// CommonTLSOptions defines TLS configuration for the daemon server.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type CommonTLSOptions struct {
|
||||
CAFile string `json:"tlscacert,omitempty"`
|
||||
CertFile string `json:"tlscert,omitempty"`
|
||||
KeyFile string `json:"tlskey,omitempty"`
|
||||
}
|
||||
|
||||
// CommonConfig defines the configuration of a docker daemon which are
|
||||
// common across platforms.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type CommonConfig struct {
|
||||
AuthorizationPlugins []string // AuthorizationPlugins holds list of authorization plugins
|
||||
AutoRestart bool
|
||||
Bridge bridgeConfig // Bridge holds bridge network specific configuration.
|
||||
Context map[string][]string
|
||||
DisableBridge bool
|
||||
DNS []string
|
||||
DNSOptions []string
|
||||
DNSSearch []string
|
||||
ExecOptions []string
|
||||
ExecRoot string
|
||||
GraphDriver string
|
||||
GraphOptions []string
|
||||
Labels []string
|
||||
LogConfig container.LogConfig
|
||||
Mtu int
|
||||
Pidfile string
|
||||
RemappedRoot string
|
||||
Root string
|
||||
TrustKeyPath string
|
||||
AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
|
||||
AutoRestart bool `json:"-"`
|
||||
Bridge bridgeConfig `json:"-"` // Bridge holds bridge network specific configuration.
|
||||
Context map[string][]string `json:"-"`
|
||||
DisableBridge bool `json:"-"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
DNSOptions []string `json:"dns-opts,omitempty"`
|
||||
DNSSearch []string `json:"dns-search,omitempty"`
|
||||
ExecOptions []string `json:"exec-opts,omitempty"`
|
||||
ExecRoot string `json:"exec-root,omitempty"`
|
||||
GraphDriver string `json:"storage-driver,omitempty"`
|
||||
GraphOptions []string `json:"storage-opts,omitempty"`
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
LogConfig LogConfig `json:"log-config,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
Pidfile string `json:"pidfile,omitempty"`
|
||||
Root string `json:"graph,omitempty"`
|
||||
TrustKeyPath string `json:"-"`
|
||||
|
||||
// ClusterStore is the storage backend used for the cluster information. It is used by both
|
||||
// multihost networking (to store networks and endpoints information) and by the node discovery
|
||||
// mechanism.
|
||||
ClusterStore string
|
||||
ClusterStore string `json:"cluster-store,omitempty"`
|
||||
|
||||
// ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such
|
||||
// as TLS configuration settings.
|
||||
ClusterOpts map[string]string
|
||||
ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"`
|
||||
|
||||
// ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node
|
||||
// discovery. This should be a 'host:port' combination on which that daemon instance is
|
||||
// reachable by other hosts.
|
||||
ClusterAdvertise string
|
||||
ClusterAdvertise string `json:"cluster-advertise,omitempty"`
|
||||
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
LogLevel string `json:"log-level,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
TLSVerify bool `json:"tls-verify,omitempty"`
|
||||
TLSOptions CommonTLSOptions `json:"tls-opts,omitempty"`
|
||||
|
||||
reloadLock sync.Mutex
|
||||
}
|
||||
|
||||
// InstallCommonFlags adds command-line options to the top-level flag parser for
|
||||
|
@ -54,9 +91,9 @@ type CommonConfig struct {
|
|||
// Subsequent calls to `flag.Parse` will populate config with values parsed
|
||||
// from the command-line.
|
||||
func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string) string) {
|
||||
cmd.Var(opts.NewListOptsRef(&config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("storage-opts", &config.GraphOptions, nil), []string{"-storage-opt"}, usageFn("Set storage driver options"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("authorization-plugins", &config.AuthorizationPlugins, nil), []string{"-authorization-plugin"}, usageFn("List authorization plugins in order from first evaluator to last"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
|
||||
cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
|
||||
cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
|
||||
cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver"))
|
||||
|
@ -65,12 +102,131 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string)
|
|||
cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))
|
||||
// FIXME: why the inconsistency between "hosts" and "sockets"?
|
||||
cmd.Var(opts.NewListOptsRef(&config.DNS, opts.ValidateIPAddress), []string{"#dns", "-dns"}, usageFn("DNS server to use"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("dns-opts", &config.DNSOptions, nil), []string{"-dns-opt"}, usageFn("DNS options to use"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.DNSSearch, opts.ValidateDNSSearch), []string{"-dns-search"}, usageFn("DNS search domains to use"))
|
||||
cmd.Var(opts.NewListOptsRef(&config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
|
||||
cmd.Var(opts.NewNamedListOptsRef("labels", &config.Labels, opts.ValidateLabel), []string{"-label"}, usageFn("Set key=value labels to the daemon"))
|
||||
cmd.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", usageFn("Default driver for container logs"))
|
||||
cmd.Var(opts.NewMapOpts(config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
|
||||
cmd.Var(opts.NewNamedMapOpts("log-opts", config.LogConfig.Config, nil), []string{"-log-opt"}, usageFn("Set log driver options"))
|
||||
cmd.StringVar(&config.ClusterAdvertise, []string{"-cluster-advertise"}, "", usageFn("Address or interface name to advertise"))
|
||||
cmd.StringVar(&config.ClusterStore, []string{"-cluster-store"}, "", usageFn("Set the cluster store"))
|
||||
cmd.Var(opts.NewMapOpts(config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
|
||||
cmd.Var(opts.NewNamedMapOpts("cluster-store-opts", config.ClusterOpts, nil), []string{"-cluster-store-opt"}, usageFn("Set cluster store options"))
|
||||
}
|
||||
|
||||
func parseClusterAdvertiseSettings(clusterStore, clusterAdvertise string) (string, error) {
|
||||
if clusterAdvertise == "" {
|
||||
return "", errDiscoveryDisabled
|
||||
}
|
||||
if clusterStore == "" {
|
||||
return "", fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
|
||||
}
|
||||
|
||||
advertise, err := discovery.ParseAdvertise(clusterAdvertise)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("discovery advertise parsing failed (%v)", err)
|
||||
}
|
||||
return advertise, nil
|
||||
}
|
||||
|
||||
// ReloadConfiguration reads the configuration in the host and reloads the daemon and server.
|
||||
func ReloadConfiguration(configFile string, flags *flag.FlagSet, reload func(*Config)) {
|
||||
logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile)
|
||||
newConfig, err := getConflictFreeConfiguration(configFile, flags)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
} else {
|
||||
reload(newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// MergeDaemonConfigurations reads a configuration file,
|
||||
// loads the file configuration in an isolated structure,
|
||||
// and merges the configuration provided from flags on top
|
||||
// if there are no conflicts.
|
||||
func MergeDaemonConfigurations(flagsConfig *Config, flags *flag.FlagSet, configFile string) (*Config, error) {
|
||||
fileConfig, err := getConflictFreeConfiguration(configFile, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// merge flags configuration on top of the file configuration
|
||||
if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileConfig, nil
|
||||
}
|
||||
|
||||
// getConflictFreeConfiguration loads the configuration from a JSON file.
|
||||
// It compares that configuration with the one provided by the flags,
|
||||
// and returns an error if there are conflicts.
|
||||
func getConflictFreeConfiguration(configFile string, flags *flag.FlagSet) (*Config, error) {
|
||||
b, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
if flags != nil {
|
||||
var jsonConfig map[string]interface{}
|
||||
reader = bytes.NewReader(b)
|
||||
if err := json.NewDecoder(reader).Decode(&jsonConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := findConfigurationConflicts(jsonConfig, flags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var config Config
|
||||
reader = bytes.NewReader(b)
|
||||
err = json.NewDecoder(reader).Decode(&config)
|
||||
return &config, err
|
||||
}
|
||||
|
||||
// findConfigurationConflicts iterates over the provided flags searching for
|
||||
// duplicated configurations. It returns an error with all the conflicts if
|
||||
// it finds any.
|
||||
func findConfigurationConflicts(config map[string]interface{}, flags *flag.FlagSet) error {
|
||||
var conflicts []string
|
||||
flatten := make(map[string]interface{})
|
||||
for k, v := range config {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
for km, vm := range m {
|
||||
flatten[km] = vm
|
||||
}
|
||||
} else {
|
||||
flatten[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
printConflict := func(name string, flagValue, fileValue interface{}) string {
|
||||
return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
|
||||
}
|
||||
|
||||
collectConflicts := func(f *flag.Flag) {
|
||||
// search option name in the json configuration payload if the value is a named option
|
||||
if namedOption, ok := f.Value.(opts.NamedOption); ok {
|
||||
if optsValue, ok := flatten[namedOption.Name()]; ok {
|
||||
conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
|
||||
}
|
||||
} else {
|
||||
// search flag name in the json configuration payload without trailing dashes
|
||||
for _, name := range f.Names {
|
||||
name = strings.TrimLeft(name, "-")
|
||||
|
||||
if value, ok := flatten[name]; ok {
|
||||
conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flags.Visit(collectConflicts)
|
||||
|
||||
if len(conflicts) > 0 {
|
||||
return fmt.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
)
|
||||
|
||||
func TestDaemonConfigurationMerge(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"debug": true}`))
|
||||
f.Close()
|
||||
|
||||
c := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
AutoRestart: true,
|
||||
LogConfig: LogConfig{
|
||||
Type: "syslog",
|
||||
Config: map[string]string{"tag": "test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc, err := MergeDaemonConfigurations(c, nil, configFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !cc.Debug {
|
||||
t.Fatalf("expected %v, got %v\n", true, cc.Debug)
|
||||
}
|
||||
if !cc.AutoRestart {
|
||||
t.Fatalf("expected %v, got %v\n", true, cc.AutoRestart)
|
||||
}
|
||||
if cc.LogConfig.Type != "syslog" {
|
||||
t.Fatalf("expected syslog config, got %q\n", cc.LogConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationNotFound(t *testing.T) {
|
||||
_, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker")
|
||||
if err == nil || !os.IsNotExist(err) {
|
||||
t.Fatalf("expected does not exist error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonBrokenConfiguration(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"Debug": tru`))
|
||||
f.Close()
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, nil, configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseClusterAdvertiseSettings(t *testing.T) {
|
||||
_, err := parseClusterAdvertiseSettings("something", "")
|
||||
if err != errDiscoveryDisabled {
|
||||
t.Fatalf("expected discovery disabled error, got %v\n", err)
|
||||
}
|
||||
|
||||
_, err = parseClusterAdvertiseSettings("", "something")
|
||||
if err == nil {
|
||||
t.Fatalf("expected discovery store error, got %v\n", err)
|
||||
}
|
||||
|
||||
_, err = parseClusterAdvertiseSettings("etcd", "127.0.0.1:8080")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflicts(t *testing.T) {
|
||||
config := map[string]interface{}{"authorization-plugins": "foobar"}
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
flags.String([]string{"authorization-plugins"}, "", "")
|
||||
if err := flags.Set("authorization-plugins", "asdf"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "authorization-plugins") {
|
||||
t.Fatalf("expected authorization-plugins conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
|
||||
config := map[string]interface{}{"hosts": []string{"qwer"}}
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
|
||||
var hosts []string
|
||||
flags.Var(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
|
||||
if err := flags.Set("-host", "tcp://127.0.0.1:4444"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := flags.Set("H", "unix:///var/run/docker.sock"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err := findConfigurationConflicts(config, flags)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "hosts") {
|
||||
t.Fatalf("expected hosts conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationMergeConflicts(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"debug": true}`))
|
||||
f.Close()
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
flags.Bool([]string{"debug"}, false, "")
|
||||
flags.Set("debug", "false")
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "debug") {
|
||||
t.Fatalf("expected debug conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"tlscacert": "/etc/certificates/ca.pem"}`))
|
||||
f.Close()
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
flags.String([]string{"tlscacert"}, "", "")
|
||||
flags.Set("tlscacert", "~/.docker/ca.pem")
|
||||
|
||||
_, err = MergeDaemonConfigurations(&Config{}, flags, configFile)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "tlscacert") {
|
||||
t.Fatalf("expected tlscacert conflict, got %v", err)
|
||||
}
|
||||
}
|
|
@ -18,18 +18,20 @@ var (
|
|||
)
|
||||
|
||||
// Config defines the configuration of a docker daemon.
|
||||
// It includes json tags to deserialize configuration from a file
|
||||
// using the same names that the flags in the command line uses.
|
||||
type Config struct {
|
||||
CommonConfig
|
||||
|
||||
// Fields below here are platform specific.
|
||||
|
||||
CorsHeaders string
|
||||
EnableCors bool
|
||||
EnableSelinuxSupport bool
|
||||
RemappedRoot string
|
||||
SocketGroup string
|
||||
CgroupParent string
|
||||
Ulimits map[string]*units.Ulimit
|
||||
CorsHeaders string `json:"api-cors-headers,omitempty"`
|
||||
EnableCors bool `json:"api-enable-cors,omitempty"`
|
||||
EnableSelinuxSupport bool `json:"selinux-enabled,omitempty"`
|
||||
RemappedRoot string `json:"userns-remap,omitempty"`
|
||||
SocketGroup string `json:"group,omitempty"`
|
||||
CgroupParent string `json:"cgroup-parent,omitempty"`
|
||||
Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
|
||||
}
|
||||
|
||||
// bridgeConfig stores all the bridge driver specific
|
||||
|
|
|
@ -46,7 +46,6 @@ import (
|
|||
"github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/migrate/v1"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/discovery"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/graphdb"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
|
@ -155,7 +154,7 @@ type Daemon struct {
|
|||
EventsService *events.Events
|
||||
netController libnetwork.NetworkController
|
||||
volumes *store.VolumeStore
|
||||
discoveryWatcher discovery.Watcher
|
||||
discoveryWatcher discoveryReloader
|
||||
root string
|
||||
seccompEnabled bool
|
||||
shutdown bool
|
||||
|
@ -292,7 +291,7 @@ func (daemon *Daemon) Register(container *container.Container) error {
|
|||
|
||||
func (daemon *Daemon) restore() error {
|
||||
var (
|
||||
debug = os.Getenv("DEBUG") != ""
|
||||
debug = utils.IsDebugEnabled()
|
||||
currentDriver = daemon.GraphDriverName()
|
||||
containers = make(map[string]*container.Container)
|
||||
)
|
||||
|
@ -772,19 +771,8 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
|||
|
||||
// Discovery is only enabled when the daemon is launched with an address to advertise. When
|
||||
// initialized, the daemon is registered and we can store the discovery backend as its read-only
|
||||
// DiscoveryWatcher version.
|
||||
if config.ClusterStore != "" && config.ClusterAdvertise != "" {
|
||||
advertise, err := discovery.ParseAdvertise(config.ClusterStore, config.ClusterAdvertise)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("discovery advertise parsing failed (%v)", err)
|
||||
}
|
||||
config.ClusterAdvertise = advertise
|
||||
d.discoveryWatcher, err = initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("discovery initialization failed (%v)", err)
|
||||
}
|
||||
} else if config.ClusterAdvertise != "" {
|
||||
return nil, fmt.Errorf("invalid cluster configuration. --cluster-advertise must be accompanied by --cluster-store configuration")
|
||||
if err := d.initDiscovery(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.netController, err = d.initNetworkController(config)
|
||||
|
@ -815,7 +803,10 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
|
|||
d.configStore = config
|
||||
d.execDriver = ed
|
||||
d.statsCollector = d.newStatsCollector(1 * time.Second)
|
||||
d.defaultLogConfig = config.LogConfig
|
||||
d.defaultLogConfig = containertypes.LogConfig{
|
||||
Type: config.LogConfig.Type,
|
||||
Config: config.LogConfig.Config,
|
||||
}
|
||||
d.RegistryService = registryService
|
||||
d.EventsService = eventsService
|
||||
d.volumes = volStore
|
||||
|
@ -1521,6 +1512,76 @@ func (daemon *Daemon) newBaseContainer(id string) *container.Container {
|
|||
return container.NewBaseContainer(id, daemon.containerRoot(id))
|
||||
}
|
||||
|
||||
// initDiscovery initializes the discovery watcher for this daemon.
|
||||
func (daemon *Daemon) initDiscovery(config *Config) error {
|
||||
advertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
|
||||
if err != nil {
|
||||
if err == errDiscoveryDisabled {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
config.ClusterAdvertise = advertise
|
||||
discoveryWatcher, err := initDiscovery(config.ClusterStore, config.ClusterAdvertise, config.ClusterOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("discovery initialization failed (%v)", err)
|
||||
}
|
||||
|
||||
daemon.discoveryWatcher = discoveryWatcher
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload reads configuration changes and modifies the
|
||||
// daemon according to those changes.
|
||||
// This are the settings that Reload changes:
|
||||
// - Daemon labels.
|
||||
// - Cluster discovery (reconfigure and restart).
|
||||
func (daemon *Daemon) Reload(config *Config) error {
|
||||
daemon.configStore.reloadLock.Lock()
|
||||
defer daemon.configStore.reloadLock.Unlock()
|
||||
|
||||
daemon.configStore.Labels = config.Labels
|
||||
return daemon.reloadClusterDiscovery(config)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) reloadClusterDiscovery(config *Config) error {
|
||||
newAdvertise, err := parseClusterAdvertiseSettings(config.ClusterStore, config.ClusterAdvertise)
|
||||
if err != nil && err != errDiscoveryDisabled {
|
||||
return err
|
||||
}
|
||||
|
||||
// check discovery modifications
|
||||
if !modifiedDiscoverySettings(daemon.configStore, newAdvertise, config.ClusterStore, config.ClusterOpts) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// enable discovery for the first time if it was not previously enabled
|
||||
if daemon.discoveryWatcher == nil {
|
||||
discoveryWatcher, err := initDiscovery(config.ClusterStore, newAdvertise, config.ClusterOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("discovery initialization failed (%v)", err)
|
||||
}
|
||||
daemon.discoveryWatcher = discoveryWatcher
|
||||
} else {
|
||||
if err == errDiscoveryDisabled {
|
||||
// disable discovery if it was previously enabled and it's disabled now
|
||||
daemon.discoveryWatcher.Stop()
|
||||
} else {
|
||||
// reload discovery
|
||||
if err = daemon.discoveryWatcher.Reload(config.ClusterStore, newAdvertise, config.ClusterOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
daemon.configStore.ClusterStore = config.ClusterStore
|
||||
daemon.configStore.ClusterOpts = config.ClusterOpts
|
||||
daemon.configStore.ClusterAdvertise = newAdvertise
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertLnNetworkStats(name string, stats *lntypes.InterfaceStatistics) *libcontainer.NetworkInterface {
|
||||
n := &libcontainer.NetworkInterface{Name: name}
|
||||
n.RxBytes = stats.RxBytes
|
||||
|
|
|
@ -4,9 +4,13 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/pkg/discovery"
|
||||
_ "github.com/docker/docker/pkg/discovery/memory"
|
||||
"github.com/docker/docker/pkg/registrar"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/volume"
|
||||
|
@ -371,3 +375,118 @@ func TestMerge(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonReloadLabels(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
Labels: []string{"foo:bar"},
|
||||
},
|
||||
}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
Labels: []string{"foo:baz"},
|
||||
},
|
||||
}
|
||||
|
||||
daemon.Reload(newConfig)
|
||||
label := daemon.configStore.Labels[0]
|
||||
if label != "foo:baz" {
|
||||
t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonDiscoveryReload(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1",
|
||||
ClusterAdvertise: "127.0.0.1:3333",
|
||||
},
|
||||
}
|
||||
|
||||
if err := daemon.initDiscovery(daemon.configStore); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "3333"},
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1:2222",
|
||||
ClusterAdvertise: "127.0.0.1:5555",
|
||||
},
|
||||
}
|
||||
|
||||
expected = discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
|
||||
}
|
||||
|
||||
if err := daemon.Reload(newConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ch, errCh = daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonDiscoveryReloadFromEmptyDiscovery(t *testing.T) {
|
||||
daemon := &Daemon{}
|
||||
daemon.configStore = &Config{}
|
||||
|
||||
newConfig := &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: "memory://127.0.0.1:2222",
|
||||
ClusterAdvertise: "127.0.0.1:5555",
|
||||
},
|
||||
}
|
||||
|
||||
expected := discovery.Entries{
|
||||
&discovery.Entry{Host: "127.0.0.1", Port: "5555"},
|
||||
}
|
||||
|
||||
if err := daemon.Reload(newConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
ch, errCh := daemon.discoveryWatcher.Watch(stopCh)
|
||||
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("failed to get discovery advertisements in time")
|
||||
case e := <-ch:
|
||||
if !reflect.DeepEqual(e, expected) {
|
||||
t.Fatalf("expected %v, got %v\n", expected, e)
|
||||
}
|
||||
case e := <-errCh:
|
||||
t.Fatal(e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -19,6 +21,24 @@ const (
|
|||
defaultDiscoveryTTLFactor = 3
|
||||
)
|
||||
|
||||
var errDiscoveryDisabled = errors.New("discovery is disabled")
|
||||
|
||||
type discoveryReloader interface {
|
||||
discovery.Watcher
|
||||
Stop()
|
||||
Reload(backend, address string, clusterOpts map[string]string) error
|
||||
}
|
||||
|
||||
type daemonDiscoveryReloader struct {
|
||||
backend discovery.Backend
|
||||
ticker *time.Ticker
|
||||
term chan bool
|
||||
}
|
||||
|
||||
func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
|
||||
return d.backend.Watch(stopCh)
|
||||
}
|
||||
|
||||
func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
|
||||
var (
|
||||
heartbeat = defaultDiscoveryHeartbeat
|
||||
|
@ -57,36 +77,94 @@ func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration,
|
|||
|
||||
// initDiscovery initialized the nodes discovery subsystem by connecting to the specified backend
|
||||
// and start a registration loop to advertise the current node under the specified address.
|
||||
func initDiscovery(backend, address string, clusterOpts map[string]string) (discovery.Backend, error) {
|
||||
|
||||
heartbeat, ttl, err := discoveryOpts(clusterOpts)
|
||||
func initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, error) {
|
||||
heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discoveryBackend, err := discovery.New(backend, heartbeat, ttl, clusterOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
reloader := &daemonDiscoveryReloader{
|
||||
backend: backend,
|
||||
ticker: time.NewTicker(heartbeat),
|
||||
term: make(chan bool),
|
||||
}
|
||||
|
||||
// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
|
||||
// but we never actually Watch() for nodes appearing and disappearing for the moment.
|
||||
go registrationLoop(discoveryBackend, address, heartbeat)
|
||||
return discoveryBackend, nil
|
||||
reloader.advertise(advertiseAddress)
|
||||
return reloader, nil
|
||||
}
|
||||
|
||||
func registerAddr(backend discovery.Backend, addr string) {
|
||||
if err := backend.Register(addr); err != nil {
|
||||
func (d *daemonDiscoveryReloader) advertise(address string) {
|
||||
d.registerAddr(address)
|
||||
go d.advertiseHeartbeat(address)
|
||||
}
|
||||
|
||||
func (d *daemonDiscoveryReloader) registerAddr(addr string) {
|
||||
if err := d.backend.Register(addr); err != nil {
|
||||
log.Warnf("Registering as %q in discovery failed: %v", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// registrationLoop registers the current node against the discovery backend using the specified
|
||||
// advertiseHeartbeat registers the current node against the discovery backend using the specified
|
||||
// address. The function never returns, as registration against the backend comes with a TTL and
|
||||
// requires regular heartbeats.
|
||||
func registrationLoop(discoveryBackend discovery.Backend, address string, heartbeat time.Duration) {
|
||||
registerAddr(discoveryBackend, address)
|
||||
for range time.Tick(heartbeat) {
|
||||
registerAddr(discoveryBackend, address)
|
||||
func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
|
||||
for {
|
||||
select {
|
||||
case <-d.ticker.C:
|
||||
d.registerAddr(address)
|
||||
case <-d.term:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address.
|
||||
func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error {
|
||||
d.Stop()
|
||||
|
||||
heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.backend = backend
|
||||
d.ticker = time.NewTicker(heartbeat)
|
||||
|
||||
d.advertise(advertiseAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates the discovery advertising.
|
||||
func (d *daemonDiscoveryReloader) Stop() {
|
||||
d.ticker.Stop()
|
||||
d.term <- true
|
||||
}
|
||||
|
||||
func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) {
|
||||
heartbeat, ttl, err := discoveryOpts(clusterOpts)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return heartbeat, backend, nil
|
||||
}
|
||||
|
||||
// modifiedDiscoverySettings returns whether the discovery configuration has been modified or not.
|
||||
func modifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool {
|
||||
if config.ClusterStore != backendType || config.ClusterAdvertise != advertise {
|
||||
return true
|
||||
}
|
||||
|
||||
if (config.ClusterOpts == nil && clusterOpts == nil) ||
|
||||
(config.ClusterOpts == nil && len(clusterOpts) == 0) ||
|
||||
(len(config.ClusterOpts) == 0 && clusterOpts == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !reflect.DeepEqual(config.ClusterOpts, clusterOpts)
|
||||
}
|
||||
|
|
|
@ -89,3 +89,64 @@ func TestDiscoveryOpts(t *testing.T) {
|
|||
t.Fatalf("TTL - Expected : %v, Actual : %v", expected, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifiedDiscoverySettings(t *testing.T) {
|
||||
cases := []struct {
|
||||
current *Config
|
||||
modified *Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
modified: discoveryConfig("foo", "bar", nil),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{}),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("baz", "bar", nil),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "baz", nil),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
current: discoveryConfig("foo", "bar", nil),
|
||||
modified: discoveryConfig("foo", "bar", map[string]string{"foo": "bar"}),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
got := modifiedDiscoverySettings(c.current, c.modified.ClusterStore, c.modified.ClusterAdvertise, c.modified.ClusterOpts)
|
||||
if c.expected != got {
|
||||
t.Fatalf("expected %v, got %v: current config %q, new config %q", c.expected, got, c.current, c.modified)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func discoveryConfig(backendAddr, advertiseAddr string, opts map[string]string) *Config {
|
||||
return &Config{
|
||||
CommonConfig: CommonConfig{
|
||||
ClusterStore: backendAddr,
|
||||
ClusterAdvertise: advertiseAddr,
|
||||
ClusterOpts: opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
|||
IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled,
|
||||
BridgeNfIptables: !sysInfo.BridgeNfCallIptablesDisabled,
|
||||
BridgeNfIP6tables: !sysInfo.BridgeNfCallIP6tablesDisabled,
|
||||
Debug: os.Getenv("DEBUG") != "",
|
||||
Debug: utils.IsDebugEnabled(),
|
||||
NFd: fileutils.GetTotalUsedFds(),
|
||||
NGoroutines: runtime.NumGoroutine(),
|
||||
SystemTime: time.Now().Format(time.RFC3339Nano),
|
||||
|
|
|
@ -21,7 +21,6 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
daemonFlags *flag.FlagSet
|
||||
commonFlags = &cli.CommonFlags{FlagSet: new(flag.FlagSet)}
|
||||
|
||||
dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
|
||||
|
@ -50,7 +49,7 @@ func init() {
|
|||
cmd.StringVar(&tlsOptions.CertFile, []string{"-tlscert"}, filepath.Join(dockerCertPath, defaultCertFile), "Path to TLS certificate file")
|
||||
cmd.StringVar(&tlsOptions.KeyFile, []string{"-tlskey"}, filepath.Join(dockerCertPath, defaultKeyFile), "Path to TLS key file")
|
||||
|
||||
cmd.Var(opts.NewListOptsRef(&commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
|
||||
cmd.Var(opts.NewNamedListOptsRef("hosts", &commonFlags.Hosts, opts.ValidateHost), []string{"H", "-host"}, "Daemon socket(s) to connect to")
|
||||
}
|
||||
|
||||
func postParseCommon() {
|
||||
|
@ -67,11 +66,6 @@ func postParseCommon() {
|
|||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
|
||||
if commonFlags.Debug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
// Regardless of whether the user sets it to true or false, if they
|
||||
// specify --tlsverify at all then we need to turn on tls
|
||||
// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need to check that here as well
|
||||
|
|
119
docker/daemon.go
119
docker/daemon.go
|
@ -30,23 +30,34 @@ import (
|
|||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
const daemonUsage = " docker daemon [ --help | ... ]\n"
|
||||
const (
|
||||
daemonUsage = " docker daemon [ --help | ... ]\n"
|
||||
daemonConfigFileFlag = "-config-file"
|
||||
)
|
||||
|
||||
var (
|
||||
daemonCli cli.Handler = NewDaemonCli()
|
||||
)
|
||||
|
||||
// DaemonCli represents the daemon CLI.
|
||||
type DaemonCli struct {
|
||||
*daemon.Config
|
||||
registryOptions *registry.Options
|
||||
flags *flag.FlagSet
|
||||
}
|
||||
|
||||
func presentInHelp(usage string) string { return usage }
|
||||
func absentFromHelp(string) string { return "" }
|
||||
|
||||
// NewDaemonCli returns a pre-configured daemon CLI
|
||||
func NewDaemonCli() *DaemonCli {
|
||||
daemonFlags = cli.Subcmd("daemon", nil, "Enable daemon mode", true)
|
||||
daemonFlags := cli.Subcmd("daemon", nil, "Enable daemon mode", true)
|
||||
|
||||
// TODO(tiborvass): remove InstallFlags?
|
||||
daemonConfig := new(daemon.Config)
|
||||
daemonConfig.LogConfig.Config = make(map[string]string)
|
||||
daemonConfig.ClusterOpts = make(map[string]string)
|
||||
|
||||
daemonConfig.InstallFlags(daemonFlags, presentInHelp)
|
||||
daemonConfig.InstallFlags(flag.CommandLine, absentFromHelp)
|
||||
registryOptions := new(registry.Options)
|
||||
|
@ -57,6 +68,7 @@ func NewDaemonCli() *DaemonCli {
|
|||
return &DaemonCli{
|
||||
Config: daemonConfig,
|
||||
registryOptions: registryOptions,
|
||||
flags: daemonFlags,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,12 +113,6 @@ func migrateKey() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DaemonCli represents the daemon CLI.
|
||||
type DaemonCli struct {
|
||||
*daemon.Config
|
||||
registryOptions *registry.Options
|
||||
}
|
||||
|
||||
func getGlobalFlag() (globalFlag *flag.Flag) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
|
@ -136,15 +142,27 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
|
|||
os.Exit(1)
|
||||
} else {
|
||||
// allow new form `docker daemon -D`
|
||||
flag.Merge(daemonFlags, commonFlags.FlagSet)
|
||||
flag.Merge(cli.flags, commonFlags.FlagSet)
|
||||
}
|
||||
|
||||
daemonFlags.ParseFlags(args, true)
|
||||
configFile := cli.flags.String([]string{daemonConfigFileFlag}, defaultDaemonConfigFile, "Daemon configuration file")
|
||||
|
||||
cli.flags.ParseFlags(args, true)
|
||||
commonFlags.PostParse()
|
||||
|
||||
if commonFlags.TrustKey == "" {
|
||||
commonFlags.TrustKey = filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
|
||||
}
|
||||
cliConfig, err := loadDaemonCliConfig(cli.Config, cli.flags, commonFlags, *configFile)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cli.Config = cliConfig
|
||||
|
||||
if cli.Config.Debug {
|
||||
utils.EnableDebug()
|
||||
}
|
||||
|
||||
if utils.ExperimentalBuild() {
|
||||
logrus.Warn("Running experimental build")
|
||||
|
@ -184,12 +202,18 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
|
|||
serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
|
||||
|
||||
defaultHost := opts.DefaultHost
|
||||
if commonFlags.TLSOptions != nil {
|
||||
if !commonFlags.TLSOptions.InsecureSkipVerify {
|
||||
// server requires and verifies client's certificate
|
||||
commonFlags.TLSOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
if cli.Config.TLS {
|
||||
tlsOptions := tlsconfig.Options{
|
||||
CAFile: cli.Config.TLSOptions.CAFile,
|
||||
CertFile: cli.Config.TLSOptions.CertFile,
|
||||
KeyFile: cli.Config.TLSOptions.KeyFile,
|
||||
}
|
||||
tlsConfig, err := tlsconfig.Server(*commonFlags.TLSOptions)
|
||||
|
||||
if cli.Config.TLSVerify {
|
||||
// server requires and verifies client's certificate
|
||||
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
tlsConfig, err := tlsconfig.Server(tlsOptions)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -197,22 +221,23 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
|
|||
defaultHost = opts.DefaultTLSHost
|
||||
}
|
||||
|
||||
if len(commonFlags.Hosts) == 0 {
|
||||
commonFlags.Hosts = make([]string, 1)
|
||||
if len(cli.Config.Hosts) == 0 {
|
||||
cli.Config.Hosts = make([]string, 1)
|
||||
}
|
||||
for i := 0; i < len(commonFlags.Hosts); i++ {
|
||||
for i := 0; i < len(cli.Config.Hosts); i++ {
|
||||
var err error
|
||||
if commonFlags.Hosts[i], err = opts.ParseHost(defaultHost, commonFlags.Hosts[i]); err != nil {
|
||||
logrus.Fatalf("error parsing -H %s : %v", commonFlags.Hosts[i], err)
|
||||
if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
|
||||
logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
|
||||
}
|
||||
}
|
||||
for _, protoAddr := range commonFlags.Hosts {
|
||||
|
||||
protoAddr := cli.Config.Hosts[i]
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if len(protoAddrParts) != 2 {
|
||||
logrus.Fatalf("bad format %s, expected PROTO://ADDR", protoAddr)
|
||||
}
|
||||
serverConfig.Addrs = append(serverConfig.Addrs, apiserver.Addr{Proto: protoAddrParts[0], Addr: protoAddrParts[1]})
|
||||
}
|
||||
|
||||
api, err := apiserver.New(serverConfig)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
|
@ -245,18 +270,21 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
|
|||
|
||||
api.InitRouters(d)
|
||||
|
||||
reload := func(config *daemon.Config) {
|
||||
if err := d.Reload(config); err != nil {
|
||||
logrus.Errorf("Error reconfiguring the daemon: %v", err)
|
||||
return
|
||||
}
|
||||
api.Reload(config)
|
||||
}
|
||||
|
||||
setupConfigReloadTrap(*configFile, cli.flags, reload)
|
||||
|
||||
// The serve API routine never exits unless an error occurs
|
||||
// We need to start it as a goroutine and wait on it so
|
||||
// daemon doesn't exit
|
||||
serveAPIWait := make(chan error)
|
||||
go func() {
|
||||
if err := api.ServeAPI(); err != nil {
|
||||
logrus.Errorf("ServeAPI error: %v", err)
|
||||
serveAPIWait <- err
|
||||
return
|
||||
}
|
||||
serveAPIWait <- nil
|
||||
}()
|
||||
go api.Wait(serveAPIWait)
|
||||
|
||||
signal.Trap(func() {
|
||||
api.Close()
|
||||
|
@ -303,3 +331,34 @@ func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) {
|
|||
logrus.Error("Force shutdown daemon")
|
||||
}
|
||||
}
|
||||
|
||||
func loadDaemonCliConfig(config *daemon.Config, daemonFlags *flag.FlagSet, commonConfig *cli.CommonFlags, configFile string) (*daemon.Config, error) {
|
||||
config.Debug = commonConfig.Debug
|
||||
config.Hosts = commonConfig.Hosts
|
||||
config.LogLevel = commonConfig.LogLevel
|
||||
config.TLS = commonConfig.TLS
|
||||
config.TLSVerify = commonConfig.TLSVerify
|
||||
config.TLSOptions = daemon.CommonTLSOptions{}
|
||||
|
||||
if commonConfig.TLSOptions != nil {
|
||||
config.TLSOptions.CAFile = commonConfig.TLSOptions.CAFile
|
||||
config.TLSOptions.CertFile = commonConfig.TLSOptions.CertFile
|
||||
config.TLSOptions.KeyFile = commonConfig.TLSOptions.KeyFile
|
||||
}
|
||||
|
||||
if configFile != "" {
|
||||
c, err := daemon.MergeDaemonConfigurations(config, daemonFlags, configFile)
|
||||
if err != nil {
|
||||
if daemonFlags.IsSet(daemonConfigFileFlag) || !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v\n", configFile, err)
|
||||
}
|
||||
}
|
||||
// the merged configuration can be nil if the config file didn't exist.
|
||||
// leave the current configuration as it is if when that happens.
|
||||
if c != nil {
|
||||
config = c
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// +build daemon
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
func TestLoadDaemonCliConfigWithoutOverriding(t *testing.T) {
|
||||
c := &daemon.Config{}
|
||||
common := &cli.CommonFlags{
|
||||
Debug: true,
|
||||
}
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loadedConfig == nil {
|
||||
t.Fatalf("expected configuration %v, got nil", c)
|
||||
}
|
||||
if !loadedConfig.Debug {
|
||||
t.Fatalf("expected debug to be copied from the common flags, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliConfigWithTLS(t *testing.T) {
|
||||
c := &daemon.Config{}
|
||||
common := &cli.CommonFlags{
|
||||
TLS: true,
|
||||
TLSOptions: &tlsconfig.Options{
|
||||
CAFile: "/tmp/ca.pem",
|
||||
},
|
||||
}
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
loadedConfig, err := loadDaemonCliConfig(c, flags, common, "/tmp/fooobarbaz")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loadedConfig == nil {
|
||||
t.Fatalf("expected configuration %v, got nil", c)
|
||||
}
|
||||
if loadedConfig.TLSOptions.CAFile != "/tmp/ca.pem" {
|
||||
t.Fatalf("expected /tmp/ca.pem, got %s: %q", loadedConfig.TLSOptions.CAFile, loadedConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
|
||||
c := &daemon.Config{}
|
||||
common := &cli.CommonFlags{}
|
||||
f, err := ioutil.TempFile("", "docker-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
configFile := f.Name()
|
||||
f.Write([]byte(`{"labels": ["l3=foo"]}`))
|
||||
f.Close()
|
||||
|
||||
var labels []string
|
||||
|
||||
flags := mflag.NewFlagSet("test", mflag.ContinueOnError)
|
||||
flags.String([]string{daemonConfigFileFlag}, "", "")
|
||||
flags.Var(opts.NewNamedListOptsRef("labels", &labels, opts.ValidateLabel), []string{"-label"}, "")
|
||||
|
||||
flags.Set(daemonConfigFileFlag, configFile)
|
||||
if err := flags.Set("-label", "l1=bar"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := flags.Set("-label", "l2=baz"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = loadDaemonCliConfig(c, flags, common, configFile)
|
||||
if err == nil {
|
||||
t.Fatalf("expected configuration error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "labels") {
|
||||
t.Fatalf("expected labels conflict, got %v", err)
|
||||
}
|
||||
}
|
|
@ -5,15 +5,19 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
apiserver "github.com/docker/docker/api/server"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
|
||||
_ "github.com/docker/docker/daemon/execdriver/native"
|
||||
)
|
||||
|
||||
const defaultDaemonConfigFile = "/etc/docker/daemon.json"
|
||||
|
||||
func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
|
||||
serverConfig.SocketGroup = daemonCfg.SocketGroup
|
||||
serverConfig.EnableCors = daemonCfg.EnableCors
|
||||
|
@ -48,3 +52,14 @@ func setDefaultUmask() error {
|
|||
func getDaemonConfDir() string {
|
||||
return "/etc/docker"
|
||||
}
|
||||
|
||||
// setupConfigReloadTrap configures the USR2 signal to reload the configuration.
|
||||
func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP)
|
||||
go func() {
|
||||
for range c {
|
||||
daemon.ReloadConfiguration(configFile, flags, reload)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
apiserver "github.com/docker/docker/api/server"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
var defaultDaemonConfigFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker" + string(os.PathSeparator) + "config" + string(os.PathSeparator) + "daemon.json"
|
||||
|
||||
func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
|
||||
return serverConfig
|
||||
}
|
||||
|
@ -31,3 +38,20 @@ func getDaemonConfDir() string {
|
|||
// notifySystem sends a message to the host when the server is ready to be used
|
||||
func notifySystem() {
|
||||
}
|
||||
|
||||
// setupConfigReloadTrap configures a Win32 event to reload the configuration.
|
||||
func setupConfigReloadTrap(configFile string, flags *mflag.FlagSet, reload func(*daemon.Config)) {
|
||||
go func() {
|
||||
sa := syscall.SecurityAttributes{
|
||||
Length: 0,
|
||||
}
|
||||
ev := "Global\\docker-daemon-config-" + fmt.Sprint(os.Getpid())
|
||||
if h, _ := system.CreateEvent(&sa, false, false, ev); h != 0 {
|
||||
logrus.Debugf("Config reload - waiting signal at %s", ev)
|
||||
for {
|
||||
syscall.WaitForSingleObject(h, syscall.INFINITE)
|
||||
daemon.ReloadConfiguration(configFile, flags, reload)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ weight = -1
|
|||
--cluster-store="" URL of the distributed storage backend
|
||||
--cluster-advertise="" Address of the daemon instance on the cluster
|
||||
--cluster-store-opt=map[] Set cluster options
|
||||
--config-file=/etc/docker/daemon.json Daemon configuration file
|
||||
--dns=[] DNS server to use
|
||||
--dns-opt=[] DNS options to use
|
||||
--dns-search=[] DNS search domains to use
|
||||
|
@ -776,7 +777,7 @@ set like this:
|
|||
/usr/local/bin/docker daemon -D -g /var/lib/docker -H unix:// > /var/lib/docker-machine/docker.log 2>&1
|
||||
|
||||
|
||||
# Default cgroup parent
|
||||
## Default cgroup parent
|
||||
|
||||
The `--cgroup-parent` option allows you to set the default cgroup parent
|
||||
to use for containers. If this option is not set, it defaults to `/docker` for
|
||||
|
@ -794,3 +795,79 @@ creates the cgroup in `/sys/fs/cgroup/memory/daemoncgroup/foobar`
|
|||
This setting can also be set per container, using the `--cgroup-parent`
|
||||
option on `docker create` and `docker run`, and takes precedence over
|
||||
the `--cgroup-parent` option on the daemon.
|
||||
|
||||
## Daemon configuration file
|
||||
|
||||
The `--config-file` option allows you to set any configuration option
|
||||
for the daemon in a JSON format. This file uses the same flag names as keys,
|
||||
except for flags that allow several entries, where it uses the plural
|
||||
of the flag name, e.g., `labels` for the `label` flag. By default,
|
||||
docker tries to load a configuration file from `/etc/docker/daemon.json`
|
||||
on Linux and `%programdata%\docker\config\daemon.json` on Windows.
|
||||
|
||||
The options set in the configuration file must not conflict with options set
|
||||
via flags. The docker daemon fails to start if an option is duplicated between
|
||||
the file and the flags, regardless their value. We do this to avoid
|
||||
silently ignore changes introduced in configuration reloads.
|
||||
For example, the daemon fails to start if you set daemon labels
|
||||
in the configuration file and also set daemon labels via the `--label` flag.
|
||||
|
||||
Options that are not present in the file are ignored when the daemon starts.
|
||||
This is a full example of the allowed configuration options in the file:
|
||||
|
||||
```json
|
||||
{
|
||||
"authorization-plugins": [],
|
||||
"dns": [],
|
||||
"dns-opts": [],
|
||||
"dns-search": [],
|
||||
"exec-opts": [],
|
||||
"exec-root": "",
|
||||
"storage-driver": "",
|
||||
"storage-opts": "",
|
||||
"labels": [],
|
||||
"log-config": {
|
||||
"log-driver": "",
|
||||
"log-opts": []
|
||||
},
|
||||
"mtu": 0,
|
||||
"pidfile": "",
|
||||
"graph": "",
|
||||
"cluster-store": "",
|
||||
"cluster-store-opts": [],
|
||||
"cluster-advertise": "",
|
||||
"debug": true,
|
||||
"hosts": [],
|
||||
"log-level": "",
|
||||
"tls": true,
|
||||
"tls-verify": true,
|
||||
"tls-opts": {
|
||||
"tlscacert": "",
|
||||
"tlscert": "",
|
||||
"tlskey": ""
|
||||
},
|
||||
"api-cors-headers": "",
|
||||
"selinux-enabled": false,
|
||||
"userns-remap": "",
|
||||
"group": "",
|
||||
"cgroup-parent": "",
|
||||
"default-ulimits": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration reloading
|
||||
|
||||
Some options can be reconfigured when the daemon is running without requiring
|
||||
to restart the process. We use the `SIGHUP` signal in Linux to reload, and a global event
|
||||
in Windows with the key `Global\docker-daemon-config-$PID`. The options can
|
||||
be modified in the configuration file but still will check for conflicts with
|
||||
the provided flags. The daemon fails to reconfigure itself
|
||||
if there are conflicts, but it won't stop execution.
|
||||
|
||||
The list of currently supported options that can be reconfigured is this:
|
||||
|
||||
- `debug`: it changes the daemon to debug mode when set to true.
|
||||
- `label`: it replaces the daemon labels with a new set of labels.
|
||||
- `cluster-store`: it reloads the discovery store with the new address.
|
||||
- `cluster-store-opts`: it uses the new options to reload the discovery store.
|
||||
- `cluster-advertise`: it modifies the address advertised after reloading.
|
||||
|
|
|
@ -133,7 +133,7 @@ func (s *DockerSuite) TestHelpTextVerify(c *check.C) {
|
|||
// Check each line for lots of stuff
|
||||
lines := strings.Split(out, "\n")
|
||||
for _, line := range lines {
|
||||
c.Assert(len(line), checker.LessOrEqualThan, 103, check.Commentf("Help for %q is too long:\n%s", cmd, line))
|
||||
c.Assert(len(line), checker.LessOrEqualThan, 107, check.Commentf("Help for %q is too long:\n%s", cmd, line))
|
||||
|
||||
if scanForHome && strings.Contains(line, `"`+home) {
|
||||
c.Fatalf("Help for %q should use ~ instead of %q on:\n%s",
|
||||
|
|
|
@ -14,6 +14,7 @@ docker-daemon - Enable daemon mode
|
|||
[**--cluster-store**[=*[]*]]
|
||||
[**--cluster-advertise**[=*[]*]]
|
||||
[**--cluster-store-opt**[=*map[]*]]
|
||||
[**--config-file**[=*/etc/docker/daemon.json*]]
|
||||
[**-D**|**--debug**]
|
||||
[**--default-gateway**[=*DEFAULT-GATEWAY*]]
|
||||
[**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
|
||||
|
@ -96,6 +97,9 @@ format.
|
|||
**--cluster-store-opt**=""
|
||||
Specifies options for the Key/Value store.
|
||||
|
||||
**--config-file**="/etc/docker/daemon.json"
|
||||
Specifies the JSON file path to load the configuration from.
|
||||
|
||||
**-D**, **--debug**=*true*|*false*
|
||||
Enable debug mode. Default is false.
|
||||
|
||||
|
|
52
opts/opts.go
52
opts/opts.go
|
@ -100,6 +100,35 @@ func (opts *ListOpts) Len() int {
|
|||
return len((*opts.values))
|
||||
}
|
||||
|
||||
// NamedOption is an interface that list and map options
|
||||
// with names implement.
|
||||
type NamedOption interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
// NamedListOpts is a ListOpts with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
type NamedListOpts struct {
|
||||
name string
|
||||
ListOpts
|
||||
}
|
||||
|
||||
var _ NamedOption = &NamedListOpts{}
|
||||
|
||||
// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
|
||||
func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
|
||||
return &NamedListOpts{
|
||||
name: name,
|
||||
ListOpts: *NewListOptsRef(values, validator),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedListOpts in the configuration.
|
||||
func (o *NamedListOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
//MapOpts holds a map of values and a validation function.
|
||||
type MapOpts struct {
|
||||
values map[string]string
|
||||
|
@ -145,6 +174,29 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// NamedMapOpts is a MapOpts struct with a configuration name.
|
||||
// This struct is useful to keep reference to the assigned
|
||||
// field name in the internal configuration struct.
|
||||
type NamedMapOpts struct {
|
||||
name string
|
||||
MapOpts
|
||||
}
|
||||
|
||||
var _ NamedOption = &NamedMapOpts{}
|
||||
|
||||
// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
|
||||
func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
|
||||
return &NamedMapOpts{
|
||||
name: name,
|
||||
MapOpts: *NewMapOpts(values, validator),
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the NamedMapOpts in the configuration.
|
||||
func (o *NamedMapOpts) Name() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
// ValidatorFctType defines a validator function that returns a validated string and/or an error.
|
||||
type ValidatorFctType func(val string) (string, error)
|
||||
|
||||
|
|
|
@ -198,3 +198,35 @@ func logOptsValidator(val string) (string, error) {
|
|||
}
|
||||
return "", fmt.Errorf("invalid key %s", vals[0])
|
||||
}
|
||||
|
||||
func TestNamedListOpts(t *testing.T) {
|
||||
var v []string
|
||||
o := NewNamedListOptsRef("foo-name", &v, nil)
|
||||
|
||||
o.Set("foo")
|
||||
if o.String() != "[foo]" {
|
||||
t.Errorf("%s != [foo]", o.String())
|
||||
}
|
||||
if o.Name() != "foo-name" {
|
||||
t.Errorf("%s != foo-name", o.Name())
|
||||
}
|
||||
if len(v) != 1 {
|
||||
t.Errorf("expected foo to be in the values, got %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedMapOpts(t *testing.T) {
|
||||
tmpMap := make(map[string]string)
|
||||
o := NewNamedMapOpts("max-name", tmpMap, nil)
|
||||
|
||||
o.Set("max-size=1")
|
||||
if o.String() != "map[max-size:1]" {
|
||||
t.Errorf("%s != [map[max-size:1]", o.String())
|
||||
}
|
||||
if o.Name() != "max-name" {
|
||||
t.Errorf("%s != max-name", o.Name())
|
||||
}
|
||||
if _, exist := tmpMap["max-size"]; !exist {
|
||||
t.Errorf("expected map-size to be in the values, got %v", tmpMap)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,8 @@ import (
|
|||
var (
|
||||
// Backends is a global map of discovery backends indexed by their
|
||||
// associated scheme.
|
||||
backends map[string]Backend
|
||||
)
|
||||
|
||||
func init() {
|
||||
backends = make(map[string]Backend)
|
||||
}
|
||||
)
|
||||
|
||||
// Register makes a discovery backend available by the provided scheme.
|
||||
// If Register is called twice with the same scheme an error is returned.
|
||||
|
@ -42,7 +38,7 @@ func parse(rawurl string) (string, string) {
|
|||
|
||||
// ParseAdvertise parses the --cluster-advertise daemon config which accepts
|
||||
// <ip-address>:<port> or <interface-name>:<port>
|
||||
func ParseAdvertise(store, advertise string) (string, error) {
|
||||
func ParseAdvertise(advertise string) (string, error) {
|
||||
var (
|
||||
iface *net.Interface
|
||||
addrs []net.Addr
|
||||
|
|
|
@ -25,6 +25,7 @@ func Init() {
|
|||
// Initialize sets the heartbeat for the memory backend.
|
||||
func (s *Discovery) Initialize(_ string, heartbeat time.Duration, _ time.Duration, _ map[string]string) error {
|
||||
s.heartbeat = heartbeat
|
||||
s.values = make([]string, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// EnableDebug sets the DEBUG env var to true
|
||||
// and makes the logger to log at debug level.
|
||||
func EnableDebug() {
|
||||
os.Setenv("DEBUG", "1")
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
// DisableDebug sets the DEBUG env var to false
|
||||
// and makes the logger to log at info level.
|
||||
func DisableDebug() {
|
||||
os.Setenv("DEBUG", "")
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
}
|
||||
|
||||
// IsDebugEnabled checks whether the debug flag is set or not.
|
||||
func IsDebugEnabled() bool {
|
||||
return os.Getenv("DEBUG") != ""
|
||||
}
|
Загрузка…
Ссылка в новой задаче