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>
This commit is contained in:
Yong Tang 2016-12-25 01:11:12 -08:00
parent 81cf5a1834
commit db575ef626
12 changed files with 177 additions and 33 deletions

View file

@ -11,7 +11,6 @@ import (
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
@ -19,26 +18,6 @@ type int64Value interface {
Value() int64
}
type memBytes int64
func (m *memBytes) String() string {
return units.BytesSize(float64(m.Value()))
}
func (m *memBytes) Set(value string) error {
val, err := units.RAMInBytes(value)
*m = memBytes(val)
return err
}
func (m *memBytes) Type() string {
return "bytes"
}
func (m *memBytes) Value() int64 {
return int64(*m)
}
// PositiveDurationOpt is an option type for time.Duration that uses a pointer.
// It bahave similarly to DurationOpt but only allows positive duration values.
type PositiveDurationOpt struct {
@ -149,9 +128,9 @@ type updateOptions struct {
type resourceOptions struct {
limitCPU opts.NanoCPUs
limitMemBytes memBytes
limitMemBytes opts.MemBytes
resCPU opts.NanoCPUs
resMemBytes memBytes
resMemBytes opts.MemBytes
}
func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {

View file

@ -11,12 +11,12 @@ import (
)
func TestMemBytesString(t *testing.T) {
var mem memBytes = 1048576
var mem opts.MemBytes = 1048576
assert.Equal(t, mem.String(), "1 MiB")
}
func TestMemBytesSetAndValue(t *testing.T) {
var mem memBytes
var mem opts.MemBytes
assert.NilError(t, mem.Set("5kb"))
assert.Equal(t, mem.Value(), int64(5120))
}

View file

@ -22,9 +22,7 @@ import (
)
const (
// DefaultSHMSize is the default size (64MB) of the SHM which will be mounted in the container
DefaultSHMSize int64 = 67108864
containerSecretMountPath = "/run/secrets"
containerSecretMountPath = "/run/secrets"
)
// Container holds the fields specific to unixen implementations.

View file

@ -14,6 +14,7 @@ var (
defaultPidFile = "/var/run/docker.pid"
defaultGraph = "/var/lib/docker"
defaultExecRoot = "/var/run/docker"
defaultShmSize = int64(67108864)
)
// Config defines the configuration of a docker daemon.
@ -36,6 +37,7 @@ type Config struct {
Init bool `json:"init,omitempty"`
InitPath string `json:"init-path,omitempty"`
SeccompProfile string `json:"seccomp-profile,omitempty"`
ShmSize opts.MemBytes `json:"default-shm-size,omitempty"`
}
// bridgeConfig stores all the bridge driver specific
@ -66,6 +68,9 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
config.Ulimits = make(map[string]*units.Ulimit)
// Set default value for `--default-shm-size`
config.ShmSize = opts.MemBytes(defaultShmSize)
// Then platform-specific install flags
flags.BoolVar(&config.EnableSelinuxSupport, "selinux-enabled", false, "Enable selinux support")
flags.Var(opts.NewUlimitOpt(&config.Ulimits), "default-ulimit", "Default ulimits for containers")
@ -89,6 +94,7 @@ func (config *Config) InstallFlags(flags *pflag.FlagSet) {
flags.Int64Var(&config.CPURealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
flags.Int64Var(&config.CPURealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
flags.StringVar(&config.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile")
flags.Var(&config.ShmSize, "default-shm-size", "Default shm size for containers")
config.attachExperimentalFlags(flags)
}

View file

@ -4,7 +4,12 @@ package daemon
import (
"io/ioutil"
"runtime"
"testing"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/spf13/pflag"
)
func TestDaemonConfigurationMerge(t *testing.T) {
@ -78,3 +83,52 @@ func TestDaemonConfigurationMerge(t *testing.T) {
}
}
}
func TestDaemonParseShmSize(t *testing.T) {
if runtime.GOOS == "solaris" {
t.Skip("ShmSize not supported on Solaris\n")
}
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
config := &Config{}
config.InstallFlags(flags)
// By default `--default-shm-size=64M`
expectedValue := 64 * 1024 * 1024
if config.ShmSize.Value() != int64(expectedValue) {
t.Fatalf("expected default shm size %d, got %d", expectedValue, config.ShmSize.Value())
}
assert.NilError(t, flags.Set("default-shm-size", "128M"))
expectedValue = 128 * 1024 * 1024
if config.ShmSize.Value() != int64(expectedValue) {
t.Fatalf("expected default shm size %d, got %d", expectedValue, config.ShmSize.Value())
}
}
func TestDaemonConfigurationMergeShmSize(t *testing.T) {
if runtime.GOOS == "solaris" {
t.Skip("ShmSize not supported on Solaris\n")
}
f, err := ioutil.TempFile("", "docker-config-")
if err != nil {
t.Fatal(err)
}
configFile := f.Name()
f.Write([]byte(`
{
"default-shm-size": "1g"
}`))
f.Close()
c := &Config{}
cc, err := MergeDaemonConfigurations(c, nil, configFile)
if err != nil {
t.Fatal(err)
}
expectedValue := 1 * 1024 * 1024 * 1024
if cc.ShmSize.Value() != int64(expectedValue) {
t.Fatalf("expected default shm size %d, got %d", expectedValue, cc.ShmSize.Value())
}
}

View file

@ -117,7 +117,7 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
return err
}
shmSize := container.DefaultSHMSize
shmSize := int64(daemon.configStore.ShmSize)
if c.HostConfig.ShmSize != 0 {
shmSize = c.HostConfig.ShmSize
}

View file

@ -255,7 +255,10 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf
hostConfig.MemorySwap = hostConfig.Memory * 2
}
if hostConfig.ShmSize == 0 {
hostConfig.ShmSize = container.DefaultSHMSize
hostConfig.ShmSize = defaultShmSize
if daemon.configStore != nil {
hostConfig.ShmSize = int64(daemon.configStore.ShmSize)
}
}
var err error
opts, err := daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode, hostConfig.Privileged)
@ -575,6 +578,10 @@ func (daemon *Daemon) platformReload(config *Config) map[string]string {
daemon.configStore.DefaultRuntime = config.DefaultRuntime
}
if config.IsValueSet("default-shm-size") {
daemon.configStore.ShmSize = config.ShmSize
}
// Update attributes
var runtimeList bytes.Buffer
for name, rt := range daemon.configStore.Runtimes {
@ -585,8 +592,9 @@ func (daemon *Daemon) platformReload(config *Config) map[string]string {
}
return map[string]string{
"runtimes": runtimeList.String(),
"default-runtime": daemon.configStore.DefaultRuntime,
"runtimes": runtimeList.String(),
"default-runtime": daemon.configStore.DefaultRuntime,
"default-shm-size": fmt.Sprintf("%d", daemon.configStore.ShmSize),
}
}

View file

@ -37,6 +37,7 @@ Options:
--default-gateway value Container default gateway IPv4 address
--default-gateway-v6 value Container default gateway IPv6 address
--default-runtime string Default OCI runtime for containers (default "runc")
--default-shm-size bytes Set the default shm size for containers (default 64 MiB)
--default-ulimit value Default ulimits for containers (default [])
--disable-legacy-registry Disable contacting legacy registries
--dns value DNS server to use (default [])
@ -1161,6 +1162,7 @@ This is a full example of the allowed configuration options on Linux:
"cluster-advertise": "",
"max-concurrent-downloads": 3,
"max-concurrent-uploads": 5,
"default-shm-size": "64M",
"shutdown-timeout": 15,
"debug": true,
"hosts": [],

View file

@ -2879,3 +2879,60 @@ func (s *DockerDaemonSuite) TestRestartPolicyWithLiveRestore(c *check.C) {
out, err = s.d.Cmd("stop", id)
c.Assert(err, check.IsNil, check.Commentf("output: %s", out))
}
func (s *DockerDaemonSuite) TestShmSize(c *check.C) {
testRequires(c, DaemonIsLinux)
size := 67108864 * 2
pattern := regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
s.d.StartWithBusybox(c, "--default-shm-size", fmt.Sprintf("%v", size))
name := "shm1"
out, err := s.d.Cmd("run", "--name", name, "busybox", "mount")
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(pattern.MatchString(out), checker.True)
out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
}
func (s *DockerDaemonSuite) TestShmSizeReload(c *check.C) {
testRequires(c, DaemonIsLinux)
configPath, err := ioutil.TempDir("", "test-daemon-shm-size-reload-config")
c.Assert(err, checker.IsNil, check.Commentf("could not create temp file for config reload"))
defer os.RemoveAll(configPath) // clean up
configFile := filepath.Join(configPath, "config.json")
size := 67108864 * 2
configData := []byte(fmt.Sprintf(`{"default-shm-size": "%dM"}`, size/1024/1024))
c.Assert(ioutil.WriteFile(configFile, configData, 0666), checker.IsNil, check.Commentf("could not write temp file for config reload"))
pattern := regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
s.d.StartWithBusybox(c, "--config-file", configFile)
name := "shm1"
out, err := s.d.Cmd("run", "--name", name, "busybox", "mount")
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(pattern.MatchString(out), checker.True)
out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
size = 67108864 * 3
configData = []byte(fmt.Sprintf(`{"default-shm-size": "%dM"}`, size/1024/1024))
c.Assert(ioutil.WriteFile(configFile, configData, 0666), checker.IsNil, check.Commentf("could not write temp file for config reload"))
pattern = regexp.MustCompile(fmt.Sprintf("shm on /dev/shm type tmpfs(.*)size=%dk", size/1024))
err = s.d.ReloadConfig()
c.Assert(err, checker.IsNil, check.Commentf("error reloading daemon config"))
name = "shm2"
out, err = s.d.Cmd("run", "--name", name, "busybox", "mount")
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(pattern.MatchString(out), checker.True)
out, err = s.d.Cmd("inspect", "--format", "{{.HostConfig.ShmSize}}", name)
c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
}

View file

@ -427,7 +427,7 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
c.Assert(err, checker.IsNil)
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))
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))
}
func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {

View file

@ -21,6 +21,7 @@ dockerd - Enable daemon mode
[**--default-gateway**[=*DEFAULT-GATEWAY*]]
[**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
[**--default-runtime**[=*runc*]]
[**--default-shm-size**[=*64MiB*]]
[**--default-ulimit**[=*[]*]]
[**--disable-legacy-registry**]
[**--dns**[=*[]*]]
@ -164,6 +165,9 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru
**--default-runtime**="runc"
Set default runtime if there're more than one specified by `--add-runtime`.
**--default-shm-size**=*64MiB*
Set the daemon-wide default shm size for containers. Default is `64MiB`.
**--default-ulimit**=[]
Default ulimits for containers.

View file

@ -9,6 +9,7 @@ import (
"strings"
"github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units"
)
var (
@ -402,3 +403,38 @@ func ValidateLink(val string) (string, error) {
_, _, err := ParseLink(val)
return val, err
}
// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc)
type MemBytes int64
// String returns the string format of the human readable memory bytes
func (m *MemBytes) String() string {
return units.BytesSize(float64(m.Value()))
}
// Set sets the value of the MemBytes by passing a string
func (m *MemBytes) Set(value string) error {
val, err := units.RAMInBytes(value)
*m = MemBytes(val)
return err
}
// Type returns the type
func (m *MemBytes) Type() string {
return "bytes"
}
// Value returns the value in int64
func (m *MemBytes) Value() int64 {
return int64(*m)
}
// UnmarshalJSON is the customized unmarshaler for MemBytes
func (m *MemBytes) UnmarshalJSON(s []byte) error {
if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' {
return fmt.Errorf("invalid size: %q", s)
}
val, err := units.RAMInBytes(string(s[1 : len(s)-1]))
*m = MemBytes(val)
return err
}