Browse code

Add daemon option --default-shm-size

This fix fixes issue raised in 29492 where it was not
possible to specify a default `--default-shm-size` in daemon
configuration for each `docker run``.

The flag `--default-shm-size` which is reloadable, has been
added to the daemon configuation.
Related docs has been updated.

This fix fixes 29492.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/12/25 18:11:12
Showing 12 changed files
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"github.com/docker/docker/opts"
12 12
 	runconfigopts "github.com/docker/docker/runconfig/opts"
13 13
 	"github.com/docker/go-connections/nat"
14
-	units "github.com/docker/go-units"
15 14
 	"github.com/spf13/cobra"
16 15
 )
17 16
 
... ...
@@ -19,26 +18,6 @@ type int64Value interface {
19 19
 	Value() int64
20 20
 }
21 21
 
22
-type memBytes int64
23
-
24
-func (m *memBytes) String() string {
25
-	return units.BytesSize(float64(m.Value()))
26
-}
27
-
28
-func (m *memBytes) Set(value string) error {
29
-	val, err := units.RAMInBytes(value)
30
-	*m = memBytes(val)
31
-	return err
32
-}
33
-
34
-func (m *memBytes) Type() string {
35
-	return "bytes"
36
-}
37
-
38
-func (m *memBytes) Value() int64 {
39
-	return int64(*m)
40
-}
41
-
42 22
 // PositiveDurationOpt is an option type for time.Duration that uses a pointer.
43 23
 // It bahave similarly to DurationOpt but only allows positive duration values.
44 24
 type PositiveDurationOpt struct {
... ...
@@ -149,9 +128,9 @@ type updateOptions struct {
149 149
 
150 150
 type resourceOptions struct {
151 151
 	limitCPU      opts.NanoCPUs
152
-	limitMemBytes memBytes
152
+	limitMemBytes opts.MemBytes
153 153
 	resCPU        opts.NanoCPUs
154
-	resMemBytes   memBytes
154
+	resMemBytes   opts.MemBytes
155 155
 }
156 156
 
157 157
 func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
... ...
@@ -11,12 +11,12 @@ import (
11 11
 )
12 12
 
13 13
 func TestMemBytesString(t *testing.T) {
14
-	var mem memBytes = 1048576
14
+	var mem opts.MemBytes = 1048576
15 15
 	assert.Equal(t, mem.String(), "1 MiB")
16 16
 }
17 17
 
18 18
 func TestMemBytesSetAndValue(t *testing.T) {
19
-	var mem memBytes
19
+	var mem opts.MemBytes
20 20
 	assert.NilError(t, mem.Set("5kb"))
21 21
 	assert.Equal(t, mem.Value(), int64(5120))
22 22
 }
... ...
@@ -22,9 +22,7 @@ import (
22 22
 )
23 23
 
24 24
 const (
25
-	// DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container
26
-	DefaultSHMSize           int64 = 67108864
27
-	containerSecretMountPath       = "/run/secrets"
25
+	containerSecretMountPath = "/run/secrets"
28 26
 )
29 27
 
30 28
 // Container holds the fields specific to unixen implementations.
... ...
@@ -14,6 +14,7 @@ var (
14 14
 	defaultPidFile  = "/var/run/docker.pid"
15 15
 	defaultGraph    = "/var/lib/docker"
16 16
 	defaultExecRoot = "/var/run/docker"
17
+	defaultShmSize  = int64(67108864)
17 18
 )
18 19
 
19 20
 // Config defines the configuration of a docker daemon.
... ...
@@ -36,6 +37,7 @@ type Config struct {
36 36
 	Init                 bool                     `json:"init,omitempty"`
37 37
 	InitPath             string                   `json:"init-path,omitempty"`
38 38
 	SeccompProfile       string                   `json:"seccomp-profile,omitempty"`
39
+	ShmSize              opts.MemBytes            `json:"default-shm-size,omitempty"`
39 40
 }
40 41
 
41 42
 // bridgeConfig stores all the bridge driver specific
... ...
@@ -66,6 +68,9 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
66 66
 
67 67
 	config.Ulimits = make(map[string]*units.Ulimit)
68 68
 
69
+	// Set default value for `--default-shm-size`
70
+	config.ShmSize = opts.MemBytes(defaultShmSize)
71
+
69 72
 	// Then platform-specific install flags
70 73
 	flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support")
71 74
 	flags.Var(opts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
... ...
@@ -89,6 +94,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
89 89
 	flags.Int64Var(&config.CPURealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
90 90
 	flags.Int64Var(&config.CPURealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
91 91
 	flags.StringVar(&config.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile")
92
+	flags.Var(&config.ShmSize, "default-shm-size", "Default shm size for containers")
92 93
 
93 94
 	config.attachExperimentalFlags(flags)
94 95
 }
... ...
@@ -4,7 +4,12 @@ package daemon
4 4
 
5 5
 import (
6 6
 	"io/ioutil"
7
+	"runtime"
8
+
7 9
 	"testing"
10
+
11
+	"github.com/docker/docker/pkg/testutil/assert"
12
+	"github.com/spf13/pflag"
8 13
 )
9 14
 
10 15
 func TestDaemonConfigurationMerge(t *testing.T) {
... ...
@@ -78,3 +83,52 @@ func TestDaemonConfigurationMerge(t *testing.T) {
78 78
 		}
79 79
 	}
80 80
 }
81
+
82
+func TestDaemonParseShmSize(t *testing.T) {
83
+	if runtime.GOOS == "solaris" {
84
+		t.Skip("ShmSize not supported on Solaris\n")
85
+	}
86
+	flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
87
+
88
+	config := &Config{}
89
+	config.InstallFlags(flags)
90
+	// By default `--default-shm-size=64M`
91
+	expectedValue := 64 * 1024 * 1024
92
+	if config.ShmSize.Value() != int64(expectedValue) {
93
+		t.Fatalf("expected default shm size %d, got %d", expectedValue, config.ShmSize.Value())
94
+	}
95
+	assert.NilError(t, flags.Set("default-shm-size", "128M"))
96
+	expectedValue = 128 * 1024 * 1024
97
+	if config.ShmSize.Value() != int64(expectedValue) {
98
+		t.Fatalf("expected default shm size %d, got %d", expectedValue, config.ShmSize.Value())
99
+	}
100
+}
101
+
102
+func TestDaemonConfigurationMergeShmSize(t *testing.T) {
103
+	if runtime.GOOS == "solaris" {
104
+		t.Skip("ShmSize not supported on Solaris\n")
105
+	}
106
+	f, err := ioutil.TempFile("", "docker-config-")
107
+	if err != nil {
108
+		t.Fatal(err)
109
+	}
110
+
111
+	configFile := f.Name()
112
+
113
+	f.Write([]byte(`
114
+		{
115
+			"default-shm-size": "1g"
116
+		}`))
117
+
118
+	f.Close()
119
+
120
+	c := &Config{}
121
+	cc, err := MergeDaemonConfigurations(c, nil, configFile)
122
+	if err != nil {
123
+		t.Fatal(err)
124
+	}
125
+	expectedValue := 1 * 1024 * 1024 * 1024
126
+	if cc.ShmSize.Value() != int64(expectedValue) {
127
+		t.Fatalf("expected default shm size %d, got %d", expectedValue, cc.ShmSize.Value())
128
+	}
129
+}
... ...
@@ -117,7 +117,7 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
117 117
 				return err
118 118
 			}
119 119
 
120
-			shmSize := container.DefaultSHMSize
120
+			shmSize := int64(daemon.configStore.ShmSize)
121 121
 			if c.HostConfig.ShmSize != 0 {
122 122
 				shmSize = c.HostConfig.ShmSize
123 123
 			}
... ...
@@ -255,7 +255,10 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
255 255
 		hostConfig.MemorySwap = hostConfig.Memory * 2
256 256
 	}
257 257
 	if hostConfig.ShmSize == 0 {
258
-		hostConfig.ShmSize = container.DefaultSHMSize
258
+		hostConfig.ShmSize = defaultShmSize
259
+		if daemon.configStore != nil {
260
+			hostConfig.ShmSize = int64(daemon.configStore.ShmSize)
261
+		}
259 262
 	}
260 263
 	var err error
261 264
 	opts, err := daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode, hostConfig.Privileged)
... ...
@@ -575,6 +578,10 @@ func (daemon *Daemon) platformReload(config *Config) map[string]string {
575 575
 		daemon.configStore.DefaultRuntime = config.DefaultRuntime
576 576
 	}
577 577
 
578
+	if config.IsValueSet("default-shm-size") {
579
+		daemon.configStore.ShmSize = config.ShmSize
580
+	}
581
+
578 582
 	// Update attributes
579 583
 	var runtimeList bytes.Buffer
580 584
 	for name, rt := range daemon.configStore.Runtimes {
... ...
@@ -585,8 +592,9 @@ func (daemon *Daemon) platformReload(config *Config) map[string]string {
585 585
 	}
586 586
 
587 587
 	return map[string]string{
588
-		"runtimes":        runtimeList.String(),
589
-		"default-runtime": daemon.configStore.DefaultRuntime,
588
+		"runtimes":         runtimeList.String(),
589
+		"default-runtime":  daemon.configStore.DefaultRuntime,
590
+		"default-shm-size": fmt.Sprintf("%d", daemon.configStore.ShmSize),
590 591
 	}
591 592
 }
592 593
 
... ...
@@ -37,6 +37,7 @@ Options:
37 37
       --default-gateway value                 Container default gateway IPv4 address
38 38
       --default-gateway-v6 value              Container default gateway IPv6 address
39 39
       --default-runtime string                Default OCI runtime for containers (default "runc")
40
+      --default-shm-size bytes                Set the default shm size for containers (default 64 MiB)
40 41
       --default-ulimit value                  Default ulimits for containers (default [])
41 42
       --disable-legacy-registry               Disable contacting legacy registries
42 43
       --dns value                             DNS server to use (default [])
... ...
@@ -1161,6 +1162,7 @@ This is a full example of the allowed configuration options on Linux:
1161 1161
 	"cluster-advertise": "",
1162 1162
 	"max-concurrent-downloads": 3,
1163 1163
 	"max-concurrent-uploads": 5,
1164
+	"default-shm-size": "64M",
1164 1165
 	"shutdown-timeout": 15,
1165 1166
 	"debug": true,
1166 1167
 	"hosts": [],
... ...
@@ -2879,3 +2879,60 @@ func (s *DockerDaemonSuite) TestRestartPolicyWithLiveRestore(c *check.C) {
2879 2879
 	out, err = s.d.Cmd("stop", id)
2880 2880
 	c.Assert(err, check.IsNil, check.Commentf("output: %s", out))
2881 2881
 }
2882
+
2883
+func (s *DockerDaemonSuite) TestShmSize(c *check.C) {
2884
+	testRequires(c, DaemonIsLinux)
2885
+
2886
+	size := 67108864 * 2
2887
+	pattern := regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
2888
+
2889
+	s.d.StartWithBusybox(c, "--default-shm-size", fmt.Sprintf("%v", size))
2890
+
2891
+	name := "shm1"
2892
+	out, err := s.d.Cmd("run", "--name", name, "busybox", "mount")
2893
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2894
+	c.Assert(pattern.MatchString(out), checker.True)
2895
+	out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
2896
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2897
+	c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
2898
+}
2899
+
2900
+func (s *DockerDaemonSuite) TestShmSizeReload(c *check.C) {
2901
+	testRequires(c, DaemonIsLinux)
2902
+
2903
+	configPath, err := ioutil.TempDir("", "test-daemon-shm-size-reload-config")
2904
+	c.Assert(err, checker.IsNil, check.Commentf("could not create temp file for config reload"))
2905
+	defer os.RemoveAll(configPath) // clean up
2906
+	configFile := filepath.Join(configPath, "config.json")
2907
+
2908
+	size := 67108864 * 2
2909
+	configData := []byte(fmt.Sprintf(`{"default-shm-size": "%dM"}`, size/1024/1024))
2910
+	c.Assert(ioutil.WriteFile(configFile, configData, 0666), checker.IsNil, check.Commentf("could not write temp file for config reload"))
2911
+	pattern := regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
2912
+
2913
+	s.d.StartWithBusybox(c, "--config-file", configFile)
2914
+
2915
+	name := "shm1"
2916
+	out, err := s.d.Cmd("run", "--name", name, "busybox", "mount")
2917
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2918
+	c.Assert(pattern.MatchString(out), checker.True)
2919
+	out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
2920
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2921
+	c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
2922
+
2923
+	size = 67108864 * 3
2924
+	configData = []byte(fmt.Sprintf(`{"default-shm-size": "%dM"}`, size/1024/1024))
2925
+	c.Assert(ioutil.WriteFile(configFile, configData, 0666), checker.IsNil, check.Commentf("could not write temp file for config reload"))
2926
+	pattern = regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
2927
+
2928
+	err = s.d.ReloadConfig()
2929
+	c.Assert(err, checker.IsNil, check.Commentf("error reloading daemon config"))
2930
+
2931
+	name = "shm2"
2932
+	out, err = s.d.Cmd("run", "--name", name, "busybox", "mount")
2933
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2934
+	c.Assert(pattern.MatchString(out), checker.True)
2935
+	out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
2936
+	c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
2937
+	c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
2938
+}
... ...
@@ -427,7 +427,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
427 427
 	out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
428 428
 	c.Assert(err, checker.IsNil)
429 429
 
430
-	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, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
430
+	c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, default-shm-size=67108864, 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))
431 431
 }
432 432
 
433 433
 func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {
... ...
@@ -21,6 +21,7 @@ dockerd - Enable daemon mode
21 21
 [**--default-gateway**[=*DEFAULT-GATEWAY*]]
22 22
 [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
23 23
 [**--default-runtime**[=*runc*]]
24
+[**--default-shm-size**[=*64MiB*]]
24 25
 [**--default-ulimit**[=*[]*]]
25 26
 [**--disable-legacy-registry**]
26 27
 [**--dns**[=*[]*]]
... ...
@@ -164,6 +165,9 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru
164 164
 **--default-runtime**="runc"
165 165
   Set default runtime if there're more than one specified by `--add-runtime`.
166 166
 
167
+**--default-shm-size**=*64MiB*
168
+  Set the daemon-wide default shm size for containers. Default is `64MiB`.
169
+
167 170
 **--default-ulimit**=[]
168 171
   Default ulimits for containers.
169 172
 
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"strings"
10 10
 
11 11
 	"github.com/docker/docker/api/types/filters"
12
+	units "github.com/docker/go-units"
12 13
 )
13 14
 
14 15
 var (
... ...
@@ -402,3 +403,38 @@ func ValidateLink(val string) (string, error) {
402 402
 	_, _, err := ParseLink(val)
403 403
 	return val, err
404 404
 }
405
+
406
+// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc)
407
+type MemBytes int64
408
+
409
+// String returns the string format of the human readable memory bytes
410
+func (m *MemBytes) String() string {
411
+	return units.BytesSize(float64(m.Value()))
412
+}
413
+
414
+// Set sets the value of the MemBytes by passing a string
415
+func (m *MemBytes) Set(value string) error {
416
+	val, err := units.RAMInBytes(value)
417
+	*m = MemBytes(val)
418
+	return err
419
+}
420
+
421
+// Type returns the type
422
+func (m *MemBytes) Type() string {
423
+	return "bytes"
424
+}
425
+
426
+// Value returns the value in int64
427
+func (m *MemBytes) Value() int64 {
428
+	return int64(*m)
429
+}
430
+
431
+// UnmarshalJSON is the customized unmarshaler for MemBytes
432
+func (m *MemBytes) UnmarshalJSON(s []byte) error {
433
+	if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' {
434
+		return fmt.Errorf("invalid size: %q", s)
435
+	}
436
+	val, err := units.RAMInBytes(string(s[1 : len(s)-1]))
437
+	*m = MemBytes(val)
438
+	return err
439
+}