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 |
|