Since the commit d88fe447df0e8 ("Add support for sharing /dev/shm/ and
/dev/mqueue between containers") container's /dev/shm is mounted on the
host first, then bind-mounted inside the container. This is done that
way in order to be able to share this container's IPC namespace
(and the /dev/shm mount point) with another container.
Unfortunately, this functionality breaks container checkpoint/restore
(even if IPC is not shared). Since /dev/shm is an external mount, its
contents is not saved by `criu checkpoint`, and so upon restore any
application that tries to access data under /dev/shm is severily
disappointed (which usually results in a fatal crash).
This commit solves the issue by introducing new IPC modes for containers
(in addition to 'host' and 'container:ID'). The new modes are:
- 'shareable': enables sharing this container's IPC with others
(this used to be the implicit default);
- 'private': disables sharing this container's IPC.
In 'private' mode, container's /dev/shm is truly mounted inside the
container, without any bind-mounting from the host, which solves the
issue.
While at it, let's also implement 'none' mode. The motivation, as
eloquently put by Justin Cormack, is:
> I wondered a while back about having a none shm mode, as currently it is
> not possible to have a totally unwriteable container as there is always
> a /dev/shm writeable mount. It is a bit of a niche case (and clearly
> should never be allowed to be daemon default) but it would be trivial to
> add now so maybe we should...
...so here's yet yet another mode:
- 'none': no /dev/shm mount inside the container (though it still
has its own private IPC namespace).
Now, to ultimately solve the abovementioned checkpoint/restore issue, we'd
need to make 'private' the default mode, but unfortunately it breaks the
backward compatibility. So, let's make the default container IPC mode
per-daemon configurable (with the built-in default set to 'shareable'
for now). The default can be changed either via a daemon CLI option
(--default-shm-mode) or a daemon.json configuration file parameter
of the same name.
Note one can only set either 'shareable' or 'private' IPC modes as a
daemon default (i.e. in this context 'host', 'container', or 'none'
do not make much sense).
Some other changes this patch introduces are:
1. A mount for /dev/shm is added to default OCI Linux spec.
2. IpcMode.Valid() is simplified to remove duplicated code that parsed
'container:ID' form. Note the old version used to check that ID does
not contain a semicolon -- this is no longer the case (tests are
modified accordingly). The motivation is we should either do a
proper check for container ID validity, or don't check it at all
(since it is checked in other places anyway). I chose the latter.
3. IpcMode.Container() is modified to not return container ID if the
mode value does not start with "container:", unifying the check to
be the same as in IpcMode.IsContainer().
3. IPC mode unit tests (runconfig/hostconfig_test.go) are modified
to add checks for newly added values.
[v2: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-51345997]
[v3: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-53902833]
[v4: addressed the case of upgrading from older daemon, in this case
container.HostConfig.IpcMode is unset and this is valid]
[v5: document old and new IpcMode values in api/swagger.yaml]
[v6: add the 'none' mode, changelog entry to docs/api/version-history.md]
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
... | ... |
@@ -665,7 +665,17 @@ definitions: |
665 | 665 |
type: "string" |
666 | 666 |
IpcMode: |
667 | 667 |
type: "string" |
668 |
- description: "IPC namespace to use for the container." |
|
668 |
+ description: | |
|
669 |
+ IPC sharing mode for the container. Possible values are: |
|
670 |
+ |
|
671 |
+ - `"none"`: own private IPC namespace, with /dev/shm not mounted |
|
672 |
+ - `"private"`: own private IPC namespace |
|
673 |
+ - `"shareable"`: own private IPC namespace, with a possibility to share it with other containers |
|
674 |
+ - `"container:<name|id>"`: join another (shareable) container's IPC namespace |
|
675 |
+ - `"host"`: use the host system's IPC namespace |
|
676 |
+ |
|
677 |
+ If not specified, daemon default is used, which can either be `"private"` |
|
678 |
+ or `"shareable"`, depending on daemon version and configuration. |
|
669 | 679 |
Cgroup: |
670 | 680 |
type: "string" |
671 | 681 |
description: "Cgroup to use for the container." |
... | ... |
@@ -23,41 +23,46 @@ func (i Isolation) IsDefault() bool { |
23 | 23 |
// IpcMode represents the container ipc stack. |
24 | 24 |
type IpcMode string |
25 | 25 |
|
26 |
-// IsPrivate indicates whether the container uses its private ipc stack. |
|
26 |
+// IsPrivate indicates whether the container uses its own private ipc namespace which can not be shared. |
|
27 | 27 |
func (n IpcMode) IsPrivate() bool { |
28 |
- return !(n.IsHost() || n.IsContainer()) |
|
28 |
+ return n == "private" |
|
29 | 29 |
} |
30 | 30 |
|
31 |
-// IsHost indicates whether the container uses the host's ipc stack. |
|
31 |
+// IsHost indicates whether the container shares the host's ipc namespace. |
|
32 | 32 |
func (n IpcMode) IsHost() bool { |
33 | 33 |
return n == "host" |
34 | 34 |
} |
35 | 35 |
|
36 |
-// IsContainer indicates whether the container uses a container's ipc stack. |
|
36 |
+// IsShareable indicates whether the container's ipc namespace can be shared with another container. |
|
37 |
+func (n IpcMode) IsShareable() bool { |
|
38 |
+ return n == "shareable" |
|
39 |
+} |
|
40 |
+ |
|
41 |
+// IsContainer indicates whether the container uses another container's ipc namespace. |
|
37 | 42 |
func (n IpcMode) IsContainer() bool { |
38 | 43 |
parts := strings.SplitN(string(n), ":", 2) |
39 | 44 |
return len(parts) > 1 && parts[0] == "container" |
40 | 45 |
} |
41 | 46 |
|
42 |
-// Valid indicates whether the ipc stack is valid. |
|
47 |
+// IsNone indicates whether container IpcMode is set to "none". |
|
48 |
+func (n IpcMode) IsNone() bool { |
|
49 |
+ return n == "none" |
|
50 |
+} |
|
51 |
+ |
|
52 |
+// IsEmpty indicates whether container IpcMode is empty |
|
53 |
+func (n IpcMode) IsEmpty() bool { |
|
54 |
+ return n == "" |
|
55 |
+} |
|
56 |
+ |
|
57 |
+// Valid indicates whether the ipc mode is valid. |
|
43 | 58 |
func (n IpcMode) Valid() bool { |
44 |
- parts := strings.Split(string(n), ":") |
|
45 |
- switch mode := parts[0]; mode { |
|
46 |
- case "", "host": |
|
47 |
- case "container": |
|
48 |
- if len(parts) != 2 || parts[1] == "" { |
|
49 |
- return false |
|
50 |
- } |
|
51 |
- default: |
|
52 |
- return false |
|
53 |
- } |
|
54 |
- return true |
|
59 |
+ return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer() |
|
55 | 60 |
} |
56 | 61 |
|
57 | 62 |
// Container returns the name of the container ipc stack is going to be used. |
58 | 63 |
func (n IpcMode) Container() string { |
59 | 64 |
parts := strings.SplitN(string(n), ":", 2) |
60 |
- if len(parts) > 1 { |
|
65 |
+ if len(parts) > 1 && parts[0] == "container" { |
|
61 | 66 |
return parts[1] |
62 | 67 |
} |
63 | 68 |
return "" |
... | ... |
@@ -47,6 +47,7 @@ func installConfigFlags(conf *config.Config, flags *pflag.FlagSet) { |
47 | 47 |
flags.StringVar(&conf.SeccompProfile, "seccomp-profile", "", "Path to seccomp profile") |
48 | 48 |
flags.Var(&conf.ShmSize, "default-shm-size", "Default shm size for containers") |
49 | 49 |
flags.BoolVar(&conf.NoNewPrivileges, "no-new-privileges", false, "Set no-new-privileges by default for new containers") |
50 |
+ flags.StringVar(&conf.IpcMode, "default-ipc-mode", config.DefaultIpcMode, `Default mode for containers ipc ("shareable" | "private")`) |
|
50 | 51 |
|
51 | 52 |
attachExperimentalFlags(conf, flags) |
52 | 53 |
} |
... | ... |
@@ -7,7 +7,6 @@ import ( |
7 | 7 |
"io/ioutil" |
8 | 8 |
"os" |
9 | 9 |
"path/filepath" |
10 |
- "strings" |
|
11 | 10 |
|
12 | 11 |
"github.com/docker/docker/api/types" |
13 | 12 |
containertypes "github.com/docker/docker/api/types/container" |
... | ... |
@@ -19,6 +18,7 @@ import ( |
19 | 19 |
"github.com/docker/docker/pkg/system" |
20 | 20 |
"github.com/docker/docker/volume" |
21 | 21 |
"github.com/opencontainers/selinux/go-selinux/label" |
22 |
+ "github.com/pkg/errors" |
|
22 | 23 |
"github.com/sirupsen/logrus" |
23 | 24 |
"golang.org/x/sys/unix" |
24 | 25 |
) |
... | ... |
@@ -171,48 +171,48 @@ func (container *Container) HasMountFor(path string) bool { |
171 | 171 |
return exists |
172 | 172 |
} |
173 | 173 |
|
174 |
-// UnmountIpcMounts uses the provided unmount function to unmount shm and mqueue if they were mounted |
|
175 |
-func (container *Container) UnmountIpcMounts(unmount func(pth string) error) { |
|
176 |
- if container.HostConfig.IpcMode.IsContainer() || container.HostConfig.IpcMode.IsHost() { |
|
177 |
- return |
|
174 |
+// UnmountIpcMount uses the provided unmount function to unmount shm if it was mounted |
|
175 |
+func (container *Container) UnmountIpcMount(unmount func(pth string) error) error { |
|
176 |
+ if container.HasMountFor("/dev/shm") { |
|
177 |
+ return nil |
|
178 | 178 |
} |
179 | 179 |
|
180 |
- var warnings []string |
|
181 |
- |
|
182 |
- if !container.HasMountFor("/dev/shm") { |
|
183 |
- shmPath, err := container.ShmResourcePath() |
|
184 |
- if err != nil { |
|
185 |
- logrus.Error(err) |
|
186 |
- warnings = append(warnings, err.Error()) |
|
187 |
- } else if shmPath != "" { |
|
188 |
- if err := unmount(shmPath); err != nil && !os.IsNotExist(err) { |
|
189 |
- if mounted, mErr := mount.Mounted(shmPath); mounted || mErr != nil { |
|
190 |
- warnings = append(warnings, fmt.Sprintf("failed to umount %s: %v", shmPath, err)) |
|
191 |
- } |
|
192 |
- } |
|
193 |
- |
|
194 |
- } |
|
180 |
+ // container.ShmPath should not be used here as it may point |
|
181 |
+ // to the host's or other container's /dev/shm |
|
182 |
+ shmPath, err := container.ShmResourcePath() |
|
183 |
+ if err != nil { |
|
184 |
+ return err |
|
195 | 185 |
} |
196 |
- |
|
197 |
- if len(warnings) > 0 { |
|
198 |
- logrus.Warnf("failed to cleanup ipc mounts:\n%v", strings.Join(warnings, "\n")) |
|
186 |
+ if shmPath == "" { |
|
187 |
+ return nil |
|
199 | 188 |
} |
189 |
+ if err = unmount(shmPath); err != nil && !os.IsNotExist(err) { |
|
190 |
+ if mounted, mErr := mount.Mounted(shmPath); mounted || mErr != nil { |
|
191 |
+ return errors.Wrapf(err, "umount %s", shmPath) |
|
192 |
+ } |
|
193 |
+ } |
|
194 |
+ return nil |
|
200 | 195 |
} |
201 | 196 |
|
202 | 197 |
// IpcMounts returns the list of IPC mounts |
203 | 198 |
func (container *Container) IpcMounts() []Mount { |
204 | 199 |
var mounts []Mount |
205 | 200 |
|
206 |
- if !container.HasMountFor("/dev/shm") { |
|
207 |
- label.SetFileLabel(container.ShmPath, container.MountLabel) |
|
208 |
- mounts = append(mounts, Mount{ |
|
209 |
- Source: container.ShmPath, |
|
210 |
- Destination: "/dev/shm", |
|
211 |
- Writable: true, |
|
212 |
- Propagation: string(volume.DefaultPropagationMode), |
|
213 |
- }) |
|
201 |
+ if container.HasMountFor("/dev/shm") { |
|
202 |
+ return mounts |
|
203 |
+ } |
|
204 |
+ if container.ShmPath == "" { |
|
205 |
+ return mounts |
|
214 | 206 |
} |
215 | 207 |
|
208 |
+ label.SetFileLabel(container.ShmPath, container.MountLabel) |
|
209 |
+ mounts = append(mounts, Mount{ |
|
210 |
+ Source: container.ShmPath, |
|
211 |
+ Destination: "/dev/shm", |
|
212 |
+ Writable: true, |
|
213 |
+ Propagation: string(volume.DefaultPropagationMode), |
|
214 |
+ }) |
|
215 |
+ |
|
216 | 216 |
return mounts |
217 | 217 |
} |
218 | 218 |
|
... | ... |
@@ -24,9 +24,10 @@ type ExitStatus struct { |
24 | 24 |
ExitCode int |
25 | 25 |
} |
26 | 26 |
|
27 |
-// UnmountIpcMounts unmounts Ipc related mounts. |
|
27 |
+// UnmountIpcMount unmounts Ipc related mounts. |
|
28 | 28 |
// This is a NOOP on windows. |
29 |
-func (container *Container) UnmountIpcMounts(unmount func(pth string) error) { |
|
29 |
+func (container *Container) UnmountIpcMount(unmount func(pth string) error) error { |
|
30 |
+ return nil |
|
30 | 31 |
} |
31 | 32 |
|
32 | 33 |
// IpcMounts returns the list of Ipc related mounts. |
... | ... |
@@ -27,3 +27,8 @@ type BridgeConfig struct { |
27 | 27 |
func (conf *Config) IsSwarmCompatible() error { |
28 | 28 |
return nil |
29 | 29 |
} |
30 |
+ |
|
31 |
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid. |
|
32 |
+func (conf *Config) ValidatePlatformConfig() error { |
|
33 |
+ return nil |
|
34 |
+} |
... | ... |
@@ -5,10 +5,16 @@ package config |
5 | 5 |
import ( |
6 | 6 |
"fmt" |
7 | 7 |
|
8 |
+ containertypes "github.com/docker/docker/api/types/container" |
|
8 | 9 |
"github.com/docker/docker/opts" |
9 | 10 |
units "github.com/docker/go-units" |
10 | 11 |
) |
11 | 12 |
|
13 |
+const ( |
|
14 |
+ // DefaultIpcMode is default for container's IpcMode, if not set otherwise |
|
15 |
+ DefaultIpcMode = "shareable" // TODO: change to private |
|
16 |
+) |
|
17 |
+ |
|
12 | 18 |
// Config defines the configuration of a docker daemon. |
13 | 19 |
// It includes json tags to deserialize configuration from a file |
14 | 20 |
// using the same names that the flags in the command line uses. |
... | ... |
@@ -31,6 +37,7 @@ type Config struct { |
31 | 31 |
SeccompProfile string `json:"seccomp-profile,omitempty"` |
32 | 32 |
ShmSize opts.MemBytes `json:"default-shm-size,omitempty"` |
33 | 33 |
NoNewPrivileges bool `json:"no-new-privileges,omitempty"` |
34 |
+ IpcMode string `json:"default-ipc-mode,omitempty"` |
|
34 | 35 |
} |
35 | 36 |
|
36 | 37 |
// BridgeConfig stores all the bridge driver specific |
... | ... |
@@ -61,3 +68,21 @@ func (conf *Config) IsSwarmCompatible() error { |
61 | 61 |
} |
62 | 62 |
return nil |
63 | 63 |
} |
64 |
+ |
|
65 |
+func verifyDefaultIpcMode(mode string) error { |
|
66 |
+ const hint = "Use \"shareable\" or \"private\"." |
|
67 |
+ |
|
68 |
+ dm := containertypes.IpcMode(mode) |
|
69 |
+ if !dm.Valid() { |
|
70 |
+ return fmt.Errorf("Default IPC mode setting (%v) is invalid. "+hint, dm) |
|
71 |
+ } |
|
72 |
+ if dm != "" && !dm.IsPrivate() && !dm.IsShareable() { |
|
73 |
+ return fmt.Errorf("IPC mode \"%v\" is not supported as default value. "+hint, dm) |
|
74 |
+ } |
|
75 |
+ return nil |
|
76 |
+} |
|
77 |
+ |
|
78 |
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid. |
|
79 |
+func (conf *Config) ValidatePlatformConfig() error { |
|
80 |
+ return verifyDefaultIpcMode(conf.IpcMode) |
|
81 |
+} |
... | ... |
@@ -50,3 +50,8 @@ func (conf *Config) GetExecRoot() string { |
50 | 50 |
func (conf *Config) IsSwarmCompatible() error { |
51 | 51 |
return nil |
52 | 52 |
} |
53 |
+ |
|
54 |
+// ValidatePlatformConfig checks if any platform-specific configuration settings are invalid. |
|
55 |
+func (conf *Config) ValidatePlatformConfig() error { |
|
56 |
+ return nil |
|
57 |
+} |
... | ... |
@@ -57,13 +57,27 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s |
57 | 57 |
return env, nil |
58 | 58 |
} |
59 | 59 |
|
60 |
-func (daemon *Daemon) getIpcContainer(container *container.Container) (*container.Container, error) { |
|
61 |
- containerID := container.HostConfig.IpcMode.Container() |
|
62 |
- container, err := daemon.GetContainer(containerID) |
|
60 |
+func (daemon *Daemon) getIpcContainer(id string) (*container.Container, error) { |
|
61 |
+ errMsg := "can't join IPC of container " + id |
|
62 |
+ // Check the container exists |
|
63 |
+ container, err := daemon.GetContainer(id) |
|
63 | 64 |
if err != nil { |
64 |
- return nil, errors.Wrapf(err, "cannot join IPC of a non running container: %s", container.ID) |
|
65 |
+ return nil, errors.Wrap(err, errMsg) |
|
65 | 66 |
} |
66 |
- return container, daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting) |
|
67 |
+ // Check the container is running and not restarting |
|
68 |
+ if err := daemon.checkContainer(container, containerIsRunning, containerIsNotRestarting); err != nil { |
|
69 |
+ return nil, errors.Wrap(err, errMsg) |
|
70 |
+ } |
|
71 |
+ // Check the container ipc is shareable |
|
72 |
+ if st, err := os.Stat(container.ShmPath); err != nil || !st.IsDir() { |
|
73 |
+ if err == nil || os.IsNotExist(err) { |
|
74 |
+ return nil, errors.New(errMsg + ": non-shareable IPC") |
|
75 |
+ } |
|
76 |
+ // stat() failed? |
|
77 |
+ return nil, errors.Wrap(err, errMsg+": unexpected error from stat "+container.ShmPath) |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ return container, nil |
|
67 | 81 |
} |
68 | 82 |
|
69 | 83 |
func (daemon *Daemon) getPidContainer(container *container.Container) (*container.Container, error) { |
... | ... |
@@ -90,25 +104,33 @@ func containerIsNotRestarting(c *container.Container) error { |
90 | 90 |
} |
91 | 91 |
|
92 | 92 |
func (daemon *Daemon) setupIpcDirs(c *container.Container) error { |
93 |
- var err error |
|
93 |
+ ipcMode := c.HostConfig.IpcMode |
|
94 | 94 |
|
95 |
- c.ShmPath, err = c.ShmResourcePath() |
|
96 |
- if err != nil { |
|
97 |
- return err |
|
98 |
- } |
|
99 |
- |
|
100 |
- if c.HostConfig.IpcMode.IsContainer() { |
|
101 |
- ic, err := daemon.getIpcContainer(c) |
|
95 |
+ switch { |
|
96 |
+ case ipcMode.IsContainer(): |
|
97 |
+ ic, err := daemon.getIpcContainer(ipcMode.Container()) |
|
102 | 98 |
if err != nil { |
103 | 99 |
return err |
104 | 100 |
} |
105 | 101 |
c.ShmPath = ic.ShmPath |
106 |
- } else if c.HostConfig.IpcMode.IsHost() { |
|
102 |
+ |
|
103 |
+ case ipcMode.IsHost(): |
|
107 | 104 |
if _, err := os.Stat("/dev/shm"); err != nil { |
108 | 105 |
return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host") |
109 | 106 |
} |
110 | 107 |
c.ShmPath = "/dev/shm" |
111 |
- } else { |
|
108 |
+ |
|
109 |
+ case ipcMode.IsPrivate(), ipcMode.IsNone(): |
|
110 |
+ // c.ShmPath will/should not be used, so make it empty. |
|
111 |
+ // Container's /dev/shm mount comes from OCI spec. |
|
112 |
+ c.ShmPath = "" |
|
113 |
+ |
|
114 |
+ case ipcMode.IsEmpty(): |
|
115 |
+ // A container was created by an older version of the daemon. |
|
116 |
+ // The default behavior used to be what is now called "shareable". |
|
117 |
+ fallthrough |
|
118 |
+ |
|
119 |
+ case ipcMode.IsShareable(): |
|
112 | 120 |
rootIDs := daemon.idMappings.RootPair() |
113 | 121 |
if !c.HasMountFor("/dev/shm") { |
114 | 122 |
shmPath, err := c.ShmResourcePath() |
... | ... |
@@ -127,8 +149,11 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error { |
127 | 127 |
if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil { |
128 | 128 |
return err |
129 | 129 |
} |
130 |
+ c.ShmPath = shmPath |
|
130 | 131 |
} |
131 | 132 |
|
133 |
+ default: |
|
134 |
+ return fmt.Errorf("invalid IPC mode: %v", ipcMode) |
|
132 | 135 |
} |
133 | 136 |
|
134 | 137 |
return nil |
... | ... |
@@ -308,7 +308,8 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
308 | 308 |
|
309 | 309 |
// reloadPlatform updates configuration with platform specific options |
310 | 310 |
// and updates the passed attributes |
311 |
-func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) { |
|
311 |
+func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) error { |
|
312 |
+ return nil |
|
312 | 313 |
} |
313 | 314 |
|
314 | 315 |
// verifyDaemonSettings performs validation of daemon config struct |
... | ... |
@@ -276,6 +276,15 @@ func (daemon *Daemon) adaptContainerSettings(hostConfig *containertypes.HostConf |
276 | 276 |
hostConfig.ShmSize = int64(daemon.configStore.ShmSize) |
277 | 277 |
} |
278 | 278 |
} |
279 |
+ // Set default IPC mode, if unset for container |
|
280 |
+ if hostConfig.IpcMode.IsEmpty() { |
|
281 |
+ m := config.DefaultIpcMode |
|
282 |
+ if daemon.configStore != nil { |
|
283 |
+ m = daemon.configStore.IpcMode |
|
284 |
+ } |
|
285 |
+ hostConfig.IpcMode = containertypes.IpcMode(m) |
|
286 |
+ } |
|
287 |
+ |
|
279 | 288 |
var err error |
280 | 289 |
opts, err := daemon.generateSecurityOpt(hostConfig) |
281 | 290 |
if err != nil { |
... | ... |
@@ -581,7 +590,11 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
581 | 581 |
|
582 | 582 |
// reloadPlatform updates configuration with platform specific options |
583 | 583 |
// and updates the passed attributes |
584 |
-func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) { |
|
584 |
+func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string]string) error { |
|
585 |
+ if err := conf.ValidatePlatformConfig(); err != nil { |
|
586 |
+ return err |
|
587 |
+ } |
|
588 |
+ |
|
585 | 589 |
if conf.IsValueSet("runtimes") { |
586 | 590 |
daemon.configStore.Runtimes = conf.Runtimes |
587 | 591 |
// Always set the default one |
... | ... |
@@ -596,6 +609,10 @@ func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string] |
596 | 596 |
daemon.configStore.ShmSize = conf.ShmSize |
597 | 597 |
} |
598 | 598 |
|
599 |
+ if conf.IpcMode != "" { |
|
600 |
+ daemon.configStore.IpcMode = conf.IpcMode |
|
601 |
+ } |
|
602 |
+ |
|
599 | 603 |
// Update attributes |
600 | 604 |
var runtimeList bytes.Buffer |
601 | 605 |
for name, rt := range daemon.configStore.Runtimes { |
... | ... |
@@ -608,6 +625,9 @@ func (daemon *Daemon) reloadPlatform(conf *config.Config, attributes map[string] |
608 | 608 |
attributes["runtimes"] = runtimeList.String() |
609 | 609 |
attributes["default-runtime"] = daemon.configStore.DefaultRuntime |
610 | 610 |
attributes["default-shm-size"] = fmt.Sprintf("%d", daemon.configStore.ShmSize) |
611 |
+ attributes["default-ipc-mode"] = daemon.configStore.IpcMode |
|
612 |
+ |
|
613 |
+ return nil |
|
611 | 614 |
} |
612 | 615 |
|
613 | 616 |
// verifyDaemonSettings performs validation of daemon config struct |
... | ... |
@@ -231,7 +231,8 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
231 | 231 |
|
232 | 232 |
// reloadPlatform updates configuration with platform specific options |
233 | 233 |
// and updates the passed attributes |
234 |
-func (daemon *Daemon) reloadPlatform(config *config.Config, attributes map[string]string) { |
|
234 |
+func (daemon *Daemon) reloadPlatform(config *config.Config, attributes map[string]string) error { |
|
235 |
+ return nil |
|
235 | 236 |
} |
236 | 237 |
|
237 | 238 |
// verifyDaemonSettings performs validation of daemon config struct |
... | ... |
@@ -301,10 +301,13 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error |
301 | 301 |
} |
302 | 302 |
setNamespace(s, ns) |
303 | 303 |
} |
304 |
+ |
|
304 | 305 |
// ipc |
305 |
- if c.HostConfig.IpcMode.IsContainer() { |
|
306 |
+ ipcMode := c.HostConfig.IpcMode |
|
307 |
+ switch { |
|
308 |
+ case ipcMode.IsContainer(): |
|
306 | 309 |
ns := specs.LinuxNamespace{Type: "ipc"} |
307 |
- ic, err := daemon.getIpcContainer(c) |
|
310 |
+ ic, err := daemon.getIpcContainer(ipcMode.Container()) |
|
308 | 311 |
if err != nil { |
309 | 312 |
return err |
310 | 313 |
} |
... | ... |
@@ -316,12 +319,19 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error |
316 | 316 |
nsUser.Path = fmt.Sprintf("/proc/%d/ns/user", ic.State.GetPID()) |
317 | 317 |
setNamespace(s, nsUser) |
318 | 318 |
} |
319 |
- } else if c.HostConfig.IpcMode.IsHost() { |
|
319 |
+ case ipcMode.IsHost(): |
|
320 | 320 |
oci.RemoveNamespace(s, specs.LinuxNamespaceType("ipc")) |
321 |
- } else { |
|
321 |
+ case ipcMode.IsEmpty(): |
|
322 |
+ // A container was created by an older version of the daemon. |
|
323 |
+ // The default behavior used to be what is now called "shareable". |
|
324 |
+ fallthrough |
|
325 |
+ case ipcMode.IsPrivate(), ipcMode.IsShareable(), ipcMode.IsNone(): |
|
322 | 326 |
ns := specs.LinuxNamespace{Type: "ipc"} |
323 | 327 |
setNamespace(s, ns) |
328 |
+ default: |
|
329 |
+ return fmt.Errorf("Invalid IPC mode: %v", ipcMode) |
|
324 | 330 |
} |
331 |
+ |
|
325 | 332 |
// pid |
326 | 333 |
if c.HostConfig.PidMode.IsContainer() { |
327 | 334 |
ns := specs.LinuxNamespace{Type: "pid"} |
... | ... |
@@ -486,10 +496,16 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
486 | 486 |
userMounts[m.Destination] = struct{}{} |
487 | 487 |
} |
488 | 488 |
|
489 |
- // Filter out mounts that are overridden by user supplied mounts |
|
489 |
+ // Filter out mounts from spec |
|
490 |
+ noIpc := c.HostConfig.IpcMode.IsNone() |
|
490 | 491 |
var defaultMounts []specs.Mount |
491 | 492 |
_, mountDev := userMounts["/dev"] |
492 | 493 |
for _, m := range s.Mounts { |
494 |
+ // filter out /dev/shm mount if case IpcMode is none |
|
495 |
+ if noIpc && m.Destination == "/dev/shm" { |
|
496 |
+ continue |
|
497 |
+ } |
|
498 |
+ // filter out mount overridden by a user supplied mount |
|
493 | 499 |
if _, ok := userMounts[m.Destination]; !ok { |
494 | 500 |
if mountDev && strings.HasPrefix(m.Destination, "/dev/") { |
495 | 501 |
continue |
... | ... |
@@ -589,6 +605,14 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
589 | 589 |
s.Linux.MaskedPaths = nil |
590 | 590 |
} |
591 | 591 |
|
592 |
+ // Set size for /dev/shm mount that comes from spec (IpcMode: private only) |
|
593 |
+ for i, m := range s.Mounts { |
|
594 |
+ if m.Destination == "/dev/shm" { |
|
595 |
+ sizeOpt := "size=" + strconv.FormatInt(c.HostConfig.ShmSize, 10) |
|
596 |
+ s.Mounts[i].Options = append(s.Mounts[i].Options, sizeOpt) |
|
597 |
+ } |
|
598 |
+ } |
|
599 |
+ |
|
592 | 600 |
// TODO: until a kernel/mount solution exists for handling remount in a user namespace, |
593 | 601 |
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs) |
594 | 602 |
if uidMap := daemon.idMappings.UIDs(); uidMap != nil || c.HostConfig.Privileged { |
... | ... |
@@ -745,7 +769,9 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { |
745 | 745 |
return nil, err |
746 | 746 |
} |
747 | 747 |
|
748 |
- ms = append(ms, c.IpcMounts()...) |
|
748 |
+ if !c.HostConfig.IpcMode.IsPrivate() && !c.HostConfig.IpcMode.IsEmpty() { |
|
749 |
+ ms = append(ms, c.IpcMounts()...) |
|
750 |
+ } |
|
749 | 751 |
|
750 | 752 |
tmpfsMounts, err := c.TmpfsMounts() |
751 | 753 |
if err != nil { |
... | ... |
@@ -37,7 +37,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) { |
37 | 37 |
} |
38 | 38 |
}() |
39 | 39 |
|
40 |
- daemon.reloadPlatform(conf, attributes) |
|
40 |
+ if err := daemon.reloadPlatform(conf, attributes); err != nil { |
|
41 |
+ return err |
|
42 |
+ } |
|
41 | 43 |
daemon.reloadDebug(conf, attributes) |
42 | 44 |
daemon.reloadMaxConcurrentDownloadsAndUploads(conf, attributes) |
43 | 45 |
daemon.reloadShutdownTimeout(conf, attributes) |
... | ... |
@@ -198,7 +198,9 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint |
198 | 198 |
func (daemon *Daemon) Cleanup(container *container.Container) { |
199 | 199 |
daemon.releaseNetwork(container) |
200 | 200 |
|
201 |
- container.UnmountIpcMounts(detachMounted) |
|
201 |
+ if err := container.UnmountIpcMount(detachMounted); err != nil { |
|
202 |
+ logrus.Warnf("%s cleanup: failed to unmount IPC: %s", container.ID, err) |
|
203 |
+ } |
|
202 | 204 |
|
203 | 205 |
if err := daemon.conditionalUnmountOnCleanup(container); err != nil { |
204 | 206 |
// FIXME: remove once reference counting for graphdrivers has been refactored |
... | ... |
@@ -17,6 +17,9 @@ keywords: "API, Docker, rcli, REST, documentation" |
17 | 17 |
|
18 | 18 |
[Docker Engine API v1.32](https://docs.docker.com/engine/api/v1.32/) documentation |
19 | 19 |
|
20 |
+* `POST /containers/create` now accepts additional values for the |
|
21 |
+ `HostConfig.IpcMode` property. New values are `private`, `shareable`, |
|
22 |
+ and `none`. |
|
20 | 23 |
|
21 | 24 |
## v1.31 API changes |
22 | 25 |
|
... | ... |
@@ -117,6 +117,12 @@ func DefaultLinuxSpec() specs.Spec { |
117 | 117 |
Source: "mqueue", |
118 | 118 |
Options: []string{"nosuid", "noexec", "nodev"}, |
119 | 119 |
}, |
120 |
+ { |
|
121 |
+ Destination: "/dev/shm", |
|
122 |
+ Type: "tmpfs", |
|
123 |
+ Source: "shm", |
|
124 |
+ Options: []string{"nosuid", "noexec", "nodev", "mode=1777"}, |
|
125 |
+ }, |
|
120 | 126 |
} |
121 | 127 |
s.Process.Capabilities = &specs.LinuxCapabilities{ |
122 | 128 |
Bounding: defaultCapabilities(), |
... | ... |
@@ -10,6 +10,7 @@ import ( |
10 | 10 |
|
11 | 11 |
"github.com/docker/docker/api/types/container" |
12 | 12 |
"github.com/docker/docker/pkg/sysinfo" |
13 |
+ "github.com/stretchr/testify/assert" |
|
13 | 14 |
) |
14 | 15 |
|
15 | 16 |
// TODO Windows: This will need addressing for a Windows daemon. |
... | ... |
@@ -61,43 +62,33 @@ func TestNetworkModeTest(t *testing.T) { |
61 | 61 |
} |
62 | 62 |
|
63 | 63 |
func TestIpcModeTest(t *testing.T) { |
64 |
- ipcModes := map[container.IpcMode][]bool{ |
|
65 |
- // private, host, container, valid |
|
66 |
- "": {true, false, false, true}, |
|
67 |
- "something:weird": {true, false, false, false}, |
|
68 |
- ":weird": {true, false, false, true}, |
|
69 |
- "host": {false, true, false, true}, |
|
70 |
- "container:name": {false, false, true, true}, |
|
71 |
- "container:name:something": {false, false, true, false}, |
|
72 |
- "container:": {false, false, true, false}, |
|
64 |
+ ipcModes := map[container.IpcMode]struct { |
|
65 |
+ private bool |
|
66 |
+ host bool |
|
67 |
+ container bool |
|
68 |
+ shareable bool |
|
69 |
+ valid bool |
|
70 |
+ ctrName string |
|
71 |
+ }{ |
|
72 |
+ "": {valid: true}, |
|
73 |
+ "private": {private: true, valid: true}, |
|
74 |
+ "something:weird": {}, |
|
75 |
+ ":weird": {}, |
|
76 |
+ "host": {host: true, valid: true}, |
|
77 |
+ "container": {}, |
|
78 |
+ "container:": {container: true, valid: true, ctrName: ""}, |
|
79 |
+ "container:name": {container: true, valid: true, ctrName: "name"}, |
|
80 |
+ "container:name1:name2": {container: true, valid: true, ctrName: "name1:name2"}, |
|
81 |
+ "shareable": {shareable: true, valid: true}, |
|
73 | 82 |
} |
83 |
+ |
|
74 | 84 |
for ipcMode, state := range ipcModes { |
75 |
- if ipcMode.IsPrivate() != state[0] { |
|
76 |
- t.Fatalf("IpcMode.IsPrivate for %v should have been %v but was %v", ipcMode, state[0], ipcMode.IsPrivate()) |
|
77 |
- } |
|
78 |
- if ipcMode.IsHost() != state[1] { |
|
79 |
- t.Fatalf("IpcMode.IsHost for %v should have been %v but was %v", ipcMode, state[1], ipcMode.IsHost()) |
|
80 |
- } |
|
81 |
- if ipcMode.IsContainer() != state[2] { |
|
82 |
- t.Fatalf("IpcMode.IsContainer for %v should have been %v but was %v", ipcMode, state[2], ipcMode.IsContainer()) |
|
83 |
- } |
|
84 |
- if ipcMode.Valid() != state[3] { |
|
85 |
- t.Fatalf("IpcMode.Valid for %v should have been %v but was %v", ipcMode, state[3], ipcMode.Valid()) |
|
86 |
- } |
|
87 |
- } |
|
88 |
- containerIpcModes := map[container.IpcMode]string{ |
|
89 |
- "": "", |
|
90 |
- "something": "", |
|
91 |
- "something:weird": "weird", |
|
92 |
- "container": "", |
|
93 |
- "container:": "", |
|
94 |
- "container:name": "name", |
|
95 |
- "container:name1:name2": "name1:name2", |
|
96 |
- } |
|
97 |
- for ipcMode, container := range containerIpcModes { |
|
98 |
- if ipcMode.Container() != container { |
|
99 |
- t.Fatalf("Expected %v for %v but was %v", container, ipcMode, ipcMode.Container()) |
|
100 |
- } |
|
85 |
+ assert.Equal(t, state.private, ipcMode.IsPrivate(), "IpcMode.IsPrivate() parsing failed for %q", ipcMode) |
|
86 |
+ assert.Equal(t, state.host, ipcMode.IsHost(), "IpcMode.IsHost() parsing failed for %q", ipcMode) |
|
87 |
+ assert.Equal(t, state.container, ipcMode.IsContainer(), "IpcMode.IsContainer() parsing failed for %q", ipcMode) |
|
88 |
+ assert.Equal(t, state.shareable, ipcMode.IsShareable(), "IpcMode.IsShareable() parsing failed for %q", ipcMode) |
|
89 |
+ assert.Equal(t, state.valid, ipcMode.Valid(), "IpcMode.Valid() parsing failed for %q", ipcMode) |
|
90 |
+ assert.Equal(t, state.ctrName, ipcMode.Container(), "IpcMode.Container() parsing failed for %q", ipcMode) |
|
101 | 91 |
} |
102 | 92 |
} |
103 | 93 |
|