Browse code

support insecure registry in configuration reload

Signed-off-by: allencloud <allen.sun@daocloud.io>

allencloud authored on 2016/04/25 11:51:28
Showing 8 changed files
... ...
@@ -992,6 +992,7 @@ func (daemon *Daemon) initDiscovery(config *Config) error {
992 992
 // These are the settings that Reload changes:
993 993
 // - Daemon labels.
994 994
 // - Daemon debug log level.
995
+// - Daemon insecure registries.
995 996
 // - Daemon max concurrent downloads
996 997
 // - Daemon max concurrent uploads
997 998
 // - Cluster discovery (reconfigure and restart).
... ...
@@ -1023,6 +1024,12 @@ func (daemon *Daemon) Reload(config *Config) (err error) {
1023 1023
 	if config.IsValueSet("debug") {
1024 1024
 		daemon.configStore.Debug = config.Debug
1025 1025
 	}
1026
+	if config.IsValueSet("insecure-registries") {
1027
+		daemon.configStore.InsecureRegistries = config.InsecureRegistries
1028
+		if err := daemon.RegistryService.LoadInsecureRegistries(config.InsecureRegistries); err != nil {
1029
+			return err
1030
+		}
1031
+	}
1026 1032
 	if config.IsValueSet("live-restore") {
1027 1033
 		daemon.configStore.LiveRestoreEnabled = config.LiveRestoreEnabled
1028 1034
 		if err := daemon.containerdRemote.UpdateOptions(libcontainerd.WithLiveRestore(config.LiveRestoreEnabled)); err != nil {
... ...
@@ -1065,20 +1072,39 @@ func (daemon *Daemon) Reload(config *Config) (err error) {
1065 1065
 	// We emit daemon reload event here with updatable configurations
1066 1066
 	attributes["debug"] = fmt.Sprintf("%t", daemon.configStore.Debug)
1067 1067
 	attributes["live-restore"] = fmt.Sprintf("%t", daemon.configStore.LiveRestoreEnabled)
1068
+
1069
+	if daemon.configStore.InsecureRegistries != nil {
1070
+		insecureRegistries, err := json.Marshal(daemon.configStore.InsecureRegistries)
1071
+		if err != nil {
1072
+			return err
1073
+		}
1074
+		attributes["insecure-registries"] = string(insecureRegistries)
1075
+	} else {
1076
+		attributes["insecure-registries"] = "[]"
1077
+	}
1078
+
1068 1079
 	attributes["cluster-store"] = daemon.configStore.ClusterStore
1069 1080
 	if daemon.configStore.ClusterOpts != nil {
1070
-		opts, _ := json.Marshal(daemon.configStore.ClusterOpts)
1081
+		opts, err := json.Marshal(daemon.configStore.ClusterOpts)
1082
+		if err != nil {
1083
+			return err
1084
+		}
1071 1085
 		attributes["cluster-store-opts"] = string(opts)
1072 1086
 	} else {
1073 1087
 		attributes["cluster-store-opts"] = "{}"
1074 1088
 	}
1075 1089
 	attributes["cluster-advertise"] = daemon.configStore.ClusterAdvertise
1090
+
1076 1091
 	if daemon.configStore.Labels != nil {
1077
-		labels, _ := json.Marshal(daemon.configStore.Labels)
1092
+		labels, err := json.Marshal(daemon.configStore.Labels)
1093
+		if err != nil {
1094
+			return err
1095
+		}
1078 1096
 		attributes["labels"] = string(labels)
1079 1097
 	} else {
1080 1098
 		attributes["labels"] = "[]"
1081 1099
 	}
1100
+
1082 1101
 	attributes["max-concurrent-downloads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentDownloads)
1083 1102
 	attributes["max-concurrent-uploads"] = fmt.Sprintf("%d", *daemon.configStore.MaxConcurrentUploads)
1084 1103
 	attributes["shutdown-timeout"] = fmt.Sprintf("%d", daemon.configStore.ShutdownTimeout)
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	_ "github.com/docker/docker/pkg/discovery/memory"
15 15
 	"github.com/docker/docker/pkg/registrar"
16 16
 	"github.com/docker/docker/pkg/truncindex"
17
+	"github.com/docker/docker/registry"
17 18
 	"github.com/docker/docker/volume"
18 19
 	volumedrivers "github.com/docker/docker/volume/drivers"
19 20
 	"github.com/docker/docker/volume/local"
... ...
@@ -328,13 +329,102 @@ func TestDaemonReloadLabels(t *testing.T) {
328 328
 		},
329 329
 	}
330 330
 
331
-	daemon.Reload(newConfig)
331
+	if err := daemon.Reload(newConfig); err != nil {
332
+		t.Fatal(err)
333
+	}
334
+
332 335
 	label := daemon.configStore.Labels[0]
333 336
 	if label != "foo:baz" {
334 337
 		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
335 338
 	}
336 339
 }
337 340
 
341
+func TestDaemonReloadInsecureRegistries(t *testing.T) {
342
+	daemon := &Daemon{}
343
+	// initialize daemon with existing insecure registries: "127.0.0.0/8", "10.10.1.11:5000", "10.10.1.22:5000"
344
+	daemon.RegistryService = registry.NewService(registry.ServiceOptions{
345
+		InsecureRegistries: []string{
346
+			"127.0.0.0/8",
347
+			"10.10.1.11:5000",
348
+			"10.10.1.22:5000", // this will be removed when reloading
349
+			"docker1.com",
350
+			"docker2.com", // this will be removed when reloading
351
+		},
352
+	})
353
+
354
+	daemon.configStore = &Config{}
355
+
356
+	insecureRegistries := []string{
357
+		"127.0.0.0/8",     // this will be kept
358
+		"10.10.1.11:5000", // this will be kept
359
+		"10.10.1.33:5000", // this will be newly added
360
+		"docker1.com",     // this will be kept
361
+		"docker3.com",     // this will be newly added
362
+	}
363
+
364
+	valuesSets := make(map[string]interface{})
365
+	valuesSets["insecure-registries"] = insecureRegistries
366
+
367
+	newConfig := &Config{
368
+		CommonConfig: CommonConfig{
369
+			ServiceOptions: registry.ServiceOptions{
370
+				InsecureRegistries: insecureRegistries,
371
+			},
372
+			valuesSet: valuesSets,
373
+		},
374
+	}
375
+
376
+	if err := daemon.Reload(newConfig); err != nil {
377
+		t.Fatal(err)
378
+	}
379
+
380
+	// After Reload, daemon.RegistryService will be changed which is useful
381
+	// for registry communication in daemon.
382
+	registries := daemon.RegistryService.ServiceConfig()
383
+
384
+	// After Reload(), newConfig has come to registries.InsecureRegistryCIDRs and registries.IndexConfigs in daemon.
385
+	// Then collect registries.InsecureRegistryCIDRs in dataMap.
386
+	// When collecting, we need to convert CIDRS into string as a key,
387
+	// while the times of key appears as value.
388
+	dataMap := map[string]int{}
389
+	for _, value := range registries.InsecureRegistryCIDRs {
390
+		if _, ok := dataMap[value.String()]; !ok {
391
+			dataMap[value.String()] = 1
392
+		} else {
393
+			dataMap[value.String()]++
394
+		}
395
+	}
396
+
397
+	for _, value := range registries.IndexConfigs {
398
+		if _, ok := dataMap[value.Name]; !ok {
399
+			dataMap[value.Name] = 1
400
+		} else {
401
+			dataMap[value.Name]++
402
+		}
403
+	}
404
+
405
+	// Finally compare dataMap with the original insecureRegistries.
406
+	// Each value in insecureRegistries should appear in daemon's insecure registries,
407
+	// and each can only appear exactly ONCE.
408
+	for _, r := range insecureRegistries {
409
+		if value, ok := dataMap[r]; !ok {
410
+			t.Fatalf("Expected daemon insecure registry %s, got none", r)
411
+		} else if value != 1 {
412
+			t.Fatalf("Expected only 1 daemon insecure registry %s, got %d", r, value)
413
+		}
414
+	}
415
+
416
+	// assert if "10.10.1.22:5000" is removed when reloading
417
+	if value, ok := dataMap["10.10.1.22:5000"]; ok {
418
+		t.Fatalf("Expected no insecure registry of 10.10.1.22:5000, got %d", value)
419
+	}
420
+
421
+	// assert if "docker2.com" is removed when reloading
422
+	if value, ok := dataMap["docker2.com"]; ok {
423
+		t.Fatalf("Expected no insecure registry of docker2.com, got %d", value)
424
+	}
425
+}
426
+
338 427
 func TestDaemonReloadNotAffectOthers(t *testing.T) {
339 428
 	daemon := &Daemon{}
340 429
 	daemon.configStore = &Config{
... ...
@@ -353,7 +443,10 @@ func TestDaemonReloadNotAffectOthers(t *testing.T) {
353 353
 		},
354 354
 	}
355 355
 
356
-	daemon.Reload(newConfig)
356
+	if err := daemon.Reload(newConfig); err != nil {
357
+		t.Fatal(err)
358
+	}
359
+
357 360
 	label := daemon.configStore.Labels[0]
358 361
 	if label != "foo:baz" {
359 362
 		t.Fatalf("Expected daemon label `foo:baz`, got %s", label)
... ...
@@ -1241,12 +1241,13 @@ The list of currently supported options that can be reconfigured is this:
1241 1241
 - `runtimes`: it updates the list of available OCI runtimes that can
1242 1242
   be used to run containers
1243 1243
 - `authorization-plugin`: specifies the authorization plugins to use.
1244
+- `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config.
1244 1245
 
1245 1246
 Updating and reloading the cluster configurations such as `--cluster-store`,
1246 1247
 `--cluster-advertise` and `--cluster-store-opts` will take effect only if
1247 1248
 these configurations were not previously configured. If `--cluster-store`
1248 1249
 has been provided in flags and `cluster-advertise` not, `cluster-advertise`
1249
-can be added in the configuration file without accompanied by `--cluster-store`
1250
+can be added in the configuration file without accompanied by `--cluster-store`.
1250 1251
 Configuration reload will log a warning message if it detects a change in
1251 1252
 previously configured cluster configurations.
1252 1253
 
... ...
@@ -429,7 +429,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
429 429
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
430 430
 	c.Assert(err, checker.IsNil)
431 431
 
432
-	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
432
+	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
433 433
 }
434 434
 
435 435
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {
... ...
@@ -82,13 +82,6 @@ func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
82 82
 
83 83
 // newServiceConfig returns a new instance of ServiceConfig
84 84
 func newServiceConfig(options ServiceOptions) *serviceConfig {
85
-	// Localhost is by default considered as an insecure registry
86
-	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
87
-	//
88
-	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
89
-	// daemon flags on boot2docker?
90
-	options.InsecureRegistries = append(options.InsecureRegistries, "127.0.0.0/8")
91
-
92 85
 	config := &serviceConfig{
93 86
 		ServiceConfig: registrytypes.ServiceConfig{
94 87
 			InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
... ...
@@ -99,13 +92,51 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
99 99
 		},
100 100
 		V2Only: options.V2Only,
101 101
 	}
102
-	// Split --insecure-registry into CIDR and registry-specific settings.
103
-	for _, r := range options.InsecureRegistries {
102
+
103
+	config.LoadInsecureRegistries(options.InsecureRegistries)
104
+
105
+	return config
106
+}
107
+
108
+// LoadInsecureRegistries loads insecure registries to config
109
+func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
110
+	// Localhost is by default considered as an insecure registry
111
+	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
112
+	//
113
+	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
114
+	// daemon flags on boot2docker?
115
+	registries = append(registries, "127.0.0.0/8")
116
+
117
+	// Store original InsecureRegistryCIDRs and IndexConfigs
118
+	// Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
119
+	originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
120
+	originalIndexInfos := config.ServiceConfig.IndexConfigs
121
+
122
+	config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
123
+	config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo, 0)
124
+
125
+skip:
126
+	for _, r := range registries {
127
+		// validate insecure registry
128
+		if _, err := ValidateIndexName(r); err != nil {
129
+			// before returning err, roll back to original data
130
+			config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
131
+			config.ServiceConfig.IndexConfigs = originalIndexInfos
132
+			return err
133
+		}
104 134
 		// Check if CIDR was passed to --insecure-registry
105 135
 		_, ipnet, err := net.ParseCIDR(r)
106 136
 		if err == nil {
107
-			// Valid CIDR.
108
-			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*registrytypes.NetIPNet)(ipnet))
137
+			// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
138
+			data := (*registrytypes.NetIPNet)(ipnet)
139
+			for _, value := range config.InsecureRegistryCIDRs {
140
+				if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
141
+					continue skip
142
+				}
143
+			}
144
+			// ipnet is not found, add it in config.InsecureRegistryCIDRs
145
+			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
146
+
109 147
 		} else {
110 148
 			// Assume `host:port` if not CIDR.
111 149
 			config.IndexConfigs[r] = &registrytypes.IndexInfo{
... ...
@@ -125,7 +156,7 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
125 125
 		Official: true,
126 126
 	}
127 127
 
128
-	return config
128
+	return nil
129 129
 }
130 130
 
131 131
 // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"net/http"
7 7
 	"net/url"
8 8
 	"strings"
9
+	"sync"
9 10
 
10 11
 	"golang.org/x/net/context"
11 12
 
... ...
@@ -30,12 +31,14 @@ type Service interface {
30 30
 	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
31 31
 	ServiceConfig() *registrytypes.ServiceConfig
32 32
 	TLSConfig(hostname string) (*tls.Config, error)
33
+	LoadInsecureRegistries([]string) error
33 34
 }
34 35
 
35 36
 // DefaultService is a registry service. It tracks configuration data such as a list
36 37
 // of mirrors.
37 38
 type DefaultService struct {
38 39
 	config *serviceConfig
40
+	mu     sync.Mutex
39 41
 }
40 42
 
41 43
 // NewService returns a new instance of DefaultService ready to be
... ...
@@ -48,7 +51,34 @@ func NewService(options ServiceOptions) *DefaultService {
48 48
 
49 49
 // ServiceConfig returns the public registry service configuration.
50 50
 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
51
-	return &s.config.ServiceConfig
51
+	s.mu.Lock()
52
+	defer s.mu.Unlock()
53
+
54
+	servConfig := registrytypes.ServiceConfig{
55
+		InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
56
+		IndexConfigs:          make(map[string]*(registrytypes.IndexInfo)),
57
+		Mirrors:               make([]string, 0),
58
+	}
59
+
60
+	// construct a new ServiceConfig which will not retrieve s.Config directly,
61
+	// and look up items in s.config with mu locked
62
+	servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
63
+
64
+	for key, value := range s.config.ServiceConfig.IndexConfigs {
65
+		servConfig.IndexConfigs[key] = value
66
+	}
67
+
68
+	servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
69
+
70
+	return &servConfig
71
+}
72
+
73
+// LoadInsecureRegistries loads insecure registries for Service
74
+func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
75
+	s.mu.Lock()
76
+	defer s.mu.Unlock()
77
+
78
+	return s.config.LoadInsecureRegistries(registries)
52 79
 }
53 80
 
54 81
 // Auth contacts the public registry with the provided credentials,
... ...
@@ -121,7 +151,11 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
121 121
 
122 122
 	indexName, remoteName := splitReposSearchTerm(term)
123 123
 
124
+	// Search is a long-running operation, just lock s.config to avoid block others.
125
+	s.mu.Lock()
124 126
 	index, err := newIndexInfo(s.config, indexName)
127
+	s.mu.Unlock()
128
+
125 129
 	if err != nil {
126 130
 		return nil, err
127 131
 	}
... ...
@@ -185,6 +219,8 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
185 185
 // ResolveRepository splits a repository name into its components
186 186
 // and configuration of the associated registry.
187 187
 func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
188
+	s.mu.Lock()
189
+	defer s.mu.Unlock()
188 190
 	return newRepositoryInfo(s.config, name)
189 191
 }
190 192
 
... ...
@@ -205,17 +241,28 @@ func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V
205 205
 
206 206
 // TLSConfig constructs a client TLS configuration based on server defaults
207 207
 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
208
+	s.mu.Lock()
209
+	defer s.mu.Unlock()
210
+
211
+	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
212
+}
213
+
214
+// tlsConfig constructs a client TLS configuration based on server defaults
215
+func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
208 216
 	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
209 217
 }
210 218
 
211 219
 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
212
-	return s.TLSConfig(mirrorURL.Host)
220
+	return s.tlsConfig(mirrorURL.Host)
213 221
 }
214 222
 
215 223
 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
216 224
 // It gives preference to v2 endpoints over v1, mirrors over the actual
217 225
 // registry, and HTTPS over plain HTTP.
218 226
 func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
227
+	s.mu.Lock()
228
+	defer s.mu.Unlock()
229
+
219 230
 	return s.lookupEndpoints(hostname)
220 231
 }
221 232
 
... ...
@@ -223,6 +270,9 @@ func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEn
223 223
 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
224 224
 // Mirrors are not included.
225 225
 func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
226
+	s.mu.Lock()
227
+	defer s.mu.Unlock()
228
+
226 229
 	allEndpoints, err := s.lookupEndpoints(hostname)
227 230
 	if err == nil {
228 231
 		for _, endpoint := range allEndpoints {
... ...
@@ -19,7 +19,7 @@ func (s *DefaultService) lookupV1Endpoints(hostname string) (endpoints []APIEndp
19 19
 		return endpoints, nil
20 20
 	}
21 21
 
22
-	tlsConfig, err = s.TLSConfig(hostname)
22
+	tlsConfig, err = s.tlsConfig(hostname)
23 23
 	if err != nil {
24 24
 		return nil, err
25 25
 	}
... ...
@@ -44,7 +44,7 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
44 44
 		return endpoints, nil
45 45
 	}
46 46
 
47
-	tlsConfig, err = s.TLSConfig(hostname)
47
+	tlsConfig, err = s.tlsConfig(hostname)
48 48
 	if err != nil {
49 49
 		return nil, err
50 50
 	}