Implement private and shareable ipc modes
| ... | ... |
@@ -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 |
|
| 94 |
- |
|
| 95 |
- c.ShmPath, err = c.ShmResourcePath() |
|
| 96 |
- if err != nil {
|
|
| 97 |
- return err |
|
| 98 |
- } |
|
| 93 |
+ ipcMode := c.HostConfig.IpcMode |
|
| 99 | 94 |
|
| 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() |
| ... | ... |
@@ -120,19 +142,18 @@ func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
|
| 120 | 120 |
return err |
| 121 | 121 |
} |
| 122 | 122 |
|
| 123 |
- shmSize := int64(daemon.configStore.ShmSize) |
|
| 124 |
- if c.HostConfig.ShmSize != 0 {
|
|
| 125 |
- shmSize = c.HostConfig.ShmSize |
|
| 126 |
- } |
|
| 127 |
- shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10) |
|
| 123 |
+ shmproperty := "mode=1777,size=" + strconv.FormatInt(c.HostConfig.ShmSize, 10) |
|
| 128 | 124 |
if err := unix.Mount("shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil {
|
| 129 | 125 |
return fmt.Errorf("mounting shm tmpfs: %s", err)
|
| 130 | 126 |
} |
| 131 | 127 |
if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil {
|
| 132 | 128 |
return err |
| 133 | 129 |
} |
| 130 |
+ c.ShmPath = shmPath |
|
| 134 | 131 |
} |
| 135 | 132 |
|
| 133 |
+ default: |
|
| 134 |
+ return fmt.Errorf("invalid IPC mode: %v", ipcMode)
|
|
| 136 | 135 |
} |
| 137 | 136 |
|
| 138 | 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 |
|
| 23 | 26 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,222 @@ |
| 0 |
+// build +linux |
|
| 1 |
+package main |
|
| 2 |
+ |
|
| 3 |
+import ( |
|
| 4 |
+ "bufio" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io/ioutil" |
|
| 7 |
+ "os" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api/types" |
|
| 11 |
+ "github.com/docker/docker/api/types/container" |
|
| 12 |
+ "github.com/docker/docker/integration-cli/checker" |
|
| 13 |
+ "github.com/docker/docker/integration-cli/cli" |
|
| 14 |
+ "github.com/docker/docker/integration-cli/request" |
|
| 15 |
+ "github.com/go-check/check" |
|
| 16 |
+ "golang.org/x/net/context" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+/* testIpcCheckDevExists checks whether a given mount (identified by its |
|
| 20 |
+ * major:minor pair from /proc/self/mountinfo) exists on the host system. |
|
| 21 |
+ * |
|
| 22 |
+ * The format of /proc/self/mountinfo is like: |
|
| 23 |
+ * |
|
| 24 |
+ * 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw |
|
| 25 |
+ * ^^^^\ |
|
| 26 |
+ * - this is the minor:major we look for |
|
| 27 |
+ */ |
|
| 28 |
+func testIpcCheckDevExists(mm string) (bool, error) {
|
|
| 29 |
+ f, err := os.Open("/proc/self/mountinfo")
|
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ return false, err |
|
| 32 |
+ } |
|
| 33 |
+ defer f.Close() |
|
| 34 |
+ |
|
| 35 |
+ s := bufio.NewScanner(f) |
|
| 36 |
+ for s.Scan() {
|
|
| 37 |
+ fields := strings.Fields(s.Text()) |
|
| 38 |
+ if len(fields) < 7 {
|
|
| 39 |
+ continue |
|
| 40 |
+ } |
|
| 41 |
+ if fields[2] == mm {
|
|
| 42 |
+ return true, nil |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ if err := s.Err(); err != nil {
|
|
| 47 |
+ return false, err |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ return false, nil |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+// testIpcNonePrivateShareable is a helper function to test "none", |
|
| 54 |
+// "private" and "shareable" modes. |
|
| 55 |
+func testIpcNonePrivateShareable(c *check.C, mode string, mustBeMounted bool, mustBeShared bool) {
|
|
| 56 |
+ cfg := container.Config{
|
|
| 57 |
+ Image: "busybox", |
|
| 58 |
+ Cmd: []string{"top"},
|
|
| 59 |
+ } |
|
| 60 |
+ hostCfg := container.HostConfig{
|
|
| 61 |
+ IpcMode: container.IpcMode(mode), |
|
| 62 |
+ } |
|
| 63 |
+ ctx := context.Background() |
|
| 64 |
+ |
|
| 65 |
+ client, err := request.NewClient() |
|
| 66 |
+ c.Assert(err, checker.IsNil) |
|
| 67 |
+ |
|
| 68 |
+ resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") |
|
| 69 |
+ c.Assert(err, checker.IsNil) |
|
| 70 |
+ c.Assert(len(resp.Warnings), checker.Equals, 0) |
|
| 71 |
+ |
|
| 72 |
+ err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
|
| 73 |
+ c.Assert(err, checker.IsNil) |
|
| 74 |
+ |
|
| 75 |
+ // get major:minor pair for /dev/shm from container's /proc/self/mountinfo |
|
| 76 |
+ cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
|
|
| 77 |
+ mm := cli.DockerCmd(c, "exec", "-i", resp.ID, "sh", "-c", cmd).Combined() |
|
| 78 |
+ if !mustBeMounted {
|
|
| 79 |
+ c.Assert(mm, checker.Equals, "") |
|
| 80 |
+ // no more checks to perform |
|
| 81 |
+ return |
|
| 82 |
+ } |
|
| 83 |
+ c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$") |
|
| 84 |
+ |
|
| 85 |
+ shared, err := testIpcCheckDevExists(mm) |
|
| 86 |
+ c.Assert(err, checker.IsNil) |
|
| 87 |
+ c.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
|
|
| 88 |
+ c.Assert(shared, checker.Equals, mustBeShared) |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+/* TestAPIIpcModeNone checks the container "none" IPC mode |
|
| 92 |
+ * (--ipc none) works as expected. It makes sure there is no |
|
| 93 |
+ * /dev/shm mount inside the container. |
|
| 94 |
+ */ |
|
| 95 |
+func (s *DockerSuite) TestAPIIpcModeNone(c *check.C) {
|
|
| 96 |
+ testRequires(c, DaemonIsLinux) |
|
| 97 |
+ testIpcNonePrivateShareable(c, "none", false, false) |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+/* TestAPIIpcModePrivate checks the container private IPC mode |
|
| 101 |
+ * (--ipc private) works as expected. It gets the minor:major pair |
|
| 102 |
+ * of /dev/shm mount from the container, and makes sure there is no |
|
| 103 |
+ * such pair on the host. |
|
| 104 |
+ */ |
|
| 105 |
+func (s *DockerSuite) TestAPIIpcModePrivate(c *check.C) {
|
|
| 106 |
+ testRequires(c, DaemonIsLinux) |
|
| 107 |
+ testIpcNonePrivateShareable(c, "private", true, false) |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+/* TestAPIIpcModeShareable checks the container shareable IPC mode |
|
| 111 |
+ * (--ipc shareable) works as expected. It gets the minor:major pair |
|
| 112 |
+ * of /dev/shm mount from the container, and makes sure such pair |
|
| 113 |
+ * also exists on the host. |
|
| 114 |
+ */ |
|
| 115 |
+func (s *DockerSuite) TestAPIIpcModeShareable(c *check.C) {
|
|
| 116 |
+ testRequires(c, DaemonIsLinux) |
|
| 117 |
+ testIpcNonePrivateShareable(c, "shareable", true, true) |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 120 |
+// testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios |
|
| 121 |
+func testIpcContainer(s *DockerSuite, c *check.C, donorMode string, mustWork bool) {
|
|
| 122 |
+ cfg := container.Config{
|
|
| 123 |
+ Image: "busybox", |
|
| 124 |
+ Cmd: []string{"top"},
|
|
| 125 |
+ } |
|
| 126 |
+ hostCfg := container.HostConfig{
|
|
| 127 |
+ IpcMode: container.IpcMode(donorMode), |
|
| 128 |
+ } |
|
| 129 |
+ ctx := context.Background() |
|
| 130 |
+ |
|
| 131 |
+ client, err := request.NewClient() |
|
| 132 |
+ c.Assert(err, checker.IsNil) |
|
| 133 |
+ |
|
| 134 |
+ // create and start the "donor" container |
|
| 135 |
+ resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") |
|
| 136 |
+ c.Assert(err, checker.IsNil) |
|
| 137 |
+ c.Assert(len(resp.Warnings), checker.Equals, 0) |
|
| 138 |
+ name1 := resp.ID |
|
| 139 |
+ |
|
| 140 |
+ err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
|
|
| 141 |
+ c.Assert(err, checker.IsNil) |
|
| 142 |
+ |
|
| 143 |
+ // create and start the second container |
|
| 144 |
+ hostCfg.IpcMode = container.IpcMode("container:" + name1)
|
|
| 145 |
+ resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") |
|
| 146 |
+ c.Assert(err, checker.IsNil) |
|
| 147 |
+ c.Assert(len(resp.Warnings), checker.Equals, 0) |
|
| 148 |
+ name2 := resp.ID |
|
| 149 |
+ |
|
| 150 |
+ err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
|
|
| 151 |
+ if !mustWork {
|
|
| 152 |
+ // start should fail with a specific error |
|
| 153 |
+ c.Assert(err, checker.NotNil) |
|
| 154 |
+ c.Assert(fmt.Sprintf("%v", err), checker.Contains, "non-shareable IPC")
|
|
| 155 |
+ // no more checks to perform here |
|
| 156 |
+ return |
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ // start should succeed |
|
| 160 |
+ c.Assert(err, checker.IsNil) |
|
| 161 |
+ |
|
| 162 |
+ // check that IPC is shared |
|
| 163 |
+ // 1. create a file in the first container |
|
| 164 |
+ cli.DockerCmd(c, "exec", name1, "sh", "-c", "printf covfefe > /dev/shm/bar") |
|
| 165 |
+ // 2. check it's the same file in the second one |
|
| 166 |
+ out := cli.DockerCmd(c, "exec", "-i", name2, "cat", "/dev/shm/bar").Combined() |
|
| 167 |
+ c.Assert(out, checker.Matches, "^covfefe$") |
|
| 168 |
+} |
|
| 169 |
+ |
|
| 170 |
+/* TestAPIIpcModeShareableAndContainer checks that a container created with |
|
| 171 |
+ * --ipc container:ID can use IPC of another shareable container. |
|
| 172 |
+ */ |
|
| 173 |
+func (s *DockerSuite) TestAPIIpcModeShareableAndContainer(c *check.C) {
|
|
| 174 |
+ testRequires(c, DaemonIsLinux) |
|
| 175 |
+ testIpcContainer(s, c, "shareable", true) |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 178 |
+/* TestAPIIpcModePrivateAndContainer checks that a container created with |
|
| 179 |
+ * --ipc container:ID can NOT use IPC of another private container. |
|
| 180 |
+ */ |
|
| 181 |
+func (s *DockerSuite) TestAPIIpcModePrivateAndContainer(c *check.C) {
|
|
| 182 |
+ testRequires(c, DaemonIsLinux) |
|
| 183 |
+ testIpcContainer(s, c, "private", false) |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 186 |
+/* TestAPIIpcModeHost checks that a container created with --ipc host |
|
| 187 |
+ * can use IPC of the host system. |
|
| 188 |
+ */ |
|
| 189 |
+func (s *DockerSuite) TestAPIIpcModeHost(c *check.C) {
|
|
| 190 |
+ testRequires(c, DaemonIsLinux) |
|
| 191 |
+ |
|
| 192 |
+ cfg := container.Config{
|
|
| 193 |
+ Image: "busybox", |
|
| 194 |
+ Cmd: []string{"top"},
|
|
| 195 |
+ } |
|
| 196 |
+ hostCfg := container.HostConfig{
|
|
| 197 |
+ IpcMode: container.IpcMode("host"),
|
|
| 198 |
+ } |
|
| 199 |
+ ctx := context.Background() |
|
| 200 |
+ |
|
| 201 |
+ client, err := request.NewClient() |
|
| 202 |
+ c.Assert(err, checker.IsNil) |
|
| 203 |
+ |
|
| 204 |
+ resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") |
|
| 205 |
+ c.Assert(err, checker.IsNil) |
|
| 206 |
+ c.Assert(len(resp.Warnings), checker.Equals, 0) |
|
| 207 |
+ name := resp.ID |
|
| 208 |
+ |
|
| 209 |
+ err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
|
|
| 210 |
+ c.Assert(err, checker.IsNil) |
|
| 211 |
+ |
|
| 212 |
+ // check that IPC is shared |
|
| 213 |
+ // 1. create a file inside container |
|
| 214 |
+ cli.DockerCmd(c, "exec", name, "sh", "-c", "printf covfefe > /dev/shm/."+name) |
|
| 215 |
+ // 2. check it's the same on the host |
|
| 216 |
+ bytes, err := ioutil.ReadFile("/dev/shm/." + name)
|
|
| 217 |
+ c.Assert(err, checker.IsNil) |
|
| 218 |
+ c.Assert(string(bytes), checker.Matches, "^covfefe$") |
|
| 219 |
+ // 3. clean up |
|
| 220 |
+ cli.DockerCmd(c, "exec", name, "rm", "-f", "/dev/shm/."+name) |
|
| 221 |
+} |
| ... | ... |
@@ -2985,6 +2985,165 @@ func (s *DockerDaemonSuite) TestShmSizeReload(c *check.C) {
|
| 2985 | 2985 |
c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size))
|
| 2986 | 2986 |
} |
| 2987 | 2987 |
|
| 2988 |
+// this is used to test both "private" and "shareable" daemon default ipc modes |
|
| 2989 |
+func testDaemonIpcPrivateShareable(d *daemon.Daemon, c *check.C, mustExist bool) {
|
|
| 2990 |
+ name := "test-ipcmode" |
|
| 2991 |
+ _, err := d.Cmd("run", "-d", "--name", name, "busybox", "top")
|
|
| 2992 |
+ c.Assert(err, checker.IsNil) |
|
| 2993 |
+ |
|
| 2994 |
+ // get major:minor pair for /dev/shm from container's /proc/self/mountinfo |
|
| 2995 |
+ cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
|
|
| 2996 |
+ mm, err := d.Cmd("exec", "-i", name, "sh", "-c", cmd)
|
|
| 2997 |
+ c.Assert(err, checker.IsNil) |
|
| 2998 |
+ c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$") |
|
| 2999 |
+ |
|
| 3000 |
+ exists, err := testIpcCheckDevExists(mm) |
|
| 3001 |
+ c.Assert(err, checker.IsNil) |
|
| 3002 |
+ c.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, exists: %v, mustExist: %v\n", mm, exists, mustExist)
|
|
| 3003 |
+ c.Assert(exists, checker.Equals, mustExist) |
|
| 3004 |
+} |
|
| 3005 |
+ |
|
| 3006 |
+// TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. |
|
| 3007 |
+func (s *DockerDaemonSuite) TestDaemonIpcModeShareable(c *check.C) {
|
|
| 3008 |
+ testRequires(c, DaemonIsLinux) |
|
| 3009 |
+ |
|
| 3010 |
+ s.d.StartWithBusybox(c, "--default-ipc-mode", "shareable") |
|
| 3011 |
+ testDaemonIpcPrivateShareable(s.d, c, true) |
|
| 3012 |
+} |
|
| 3013 |
+ |
|
| 3014 |
+// TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended. |
|
| 3015 |
+func (s *DockerDaemonSuite) TestDaemonIpcModePrivate(c *check.C) {
|
|
| 3016 |
+ testRequires(c, DaemonIsLinux) |
|
| 3017 |
+ |
|
| 3018 |
+ s.d.StartWithBusybox(c, "--default-ipc-mode", "private") |
|
| 3019 |
+ testDaemonIpcPrivateShareable(s.d, c, false) |
|
| 3020 |
+} |
|
| 3021 |
+ |
|
| 3022 |
+// used to check if an IpcMode given in config works as intended |
|
| 3023 |
+func testDaemonIpcFromConfig(s *DockerDaemonSuite, c *check.C, mode string, mustExist bool) {
|
|
| 3024 |
+ f, err := ioutil.TempFile("", "test-daemon-ipc-config")
|
|
| 3025 |
+ c.Assert(err, checker.IsNil) |
|
| 3026 |
+ defer os.Remove(f.Name()) |
|
| 3027 |
+ |
|
| 3028 |
+ config := `{"default-ipc-mode": "` + mode + `"}`
|
|
| 3029 |
+ _, err = f.WriteString(config) |
|
| 3030 |
+ c.Assert(f.Close(), checker.IsNil) |
|
| 3031 |
+ c.Assert(err, checker.IsNil) |
|
| 3032 |
+ |
|
| 3033 |
+ s.d.StartWithBusybox(c, "--config-file", f.Name()) |
|
| 3034 |
+ testDaemonIpcPrivateShareable(s.d, c, mustExist) |
|
| 3035 |
+} |
|
| 3036 |
+ |
|
| 3037 |
+// TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended. |
|
| 3038 |
+func (s *DockerDaemonSuite) TestDaemonIpcModePrivateFromConfig(c *check.C) {
|
|
| 3039 |
+ testDaemonIpcFromConfig(s, c, "private", false) |
|
| 3040 |
+} |
|
| 3041 |
+ |
|
| 3042 |
+// TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. |
|
| 3043 |
+func (s *DockerDaemonSuite) TestDaemonIpcModeShareableFromConfig(c *check.C) {
|
|
| 3044 |
+ testDaemonIpcFromConfig(s, c, "shareable", true) |
|
| 3045 |
+} |
|
| 3046 |
+ |
|
| 3047 |
+func testDaemonStartIpcMode(c *check.C, from, mode string, valid bool) {
|
|
| 3048 |
+ testRequires(c, DaemonIsLinux) |
|
| 3049 |
+ |
|
| 3050 |
+ d := daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
|
|
| 3051 |
+ Experimental: testEnv.ExperimentalDaemon(), |
|
| 3052 |
+ }) |
|
| 3053 |
+ c.Logf("Checking IpcMode %s set from %s\n", mode, from)
|
|
| 3054 |
+ var serr error |
|
| 3055 |
+ switch from {
|
|
| 3056 |
+ case "config": |
|
| 3057 |
+ f, err := ioutil.TempFile("", "test-daemon-ipc-config")
|
|
| 3058 |
+ c.Assert(err, checker.IsNil) |
|
| 3059 |
+ defer os.Remove(f.Name()) |
|
| 3060 |
+ config := `{"default-ipc-mode": "` + mode + `"}`
|
|
| 3061 |
+ _, err = f.WriteString(config) |
|
| 3062 |
+ c.Assert(f.Close(), checker.IsNil) |
|
| 3063 |
+ c.Assert(err, checker.IsNil) |
|
| 3064 |
+ |
|
| 3065 |
+ serr = d.StartWithError("--config-file", f.Name())
|
|
| 3066 |
+ case "cli": |
|
| 3067 |
+ serr = d.StartWithError("--default-ipc-mode", mode)
|
|
| 3068 |
+ default: |
|
| 3069 |
+ c.Fatalf("testDaemonStartIpcMode: invalid 'from' argument")
|
|
| 3070 |
+ } |
|
| 3071 |
+ if serr == nil {
|
|
| 3072 |
+ d.Stop(c) |
|
| 3073 |
+ } |
|
| 3074 |
+ |
|
| 3075 |
+ if valid {
|
|
| 3076 |
+ c.Assert(serr, check.IsNil) |
|
| 3077 |
+ } else {
|
|
| 3078 |
+ c.Assert(serr, check.NotNil) |
|
| 3079 |
+ icmd.RunCommand("grep", "-E", "IPC .* is (invalid|not supported)", d.LogFileName()).Assert(c, icmd.Success)
|
|
| 3080 |
+ } |
|
| 3081 |
+} |
|
| 3082 |
+ |
|
| 3083 |
+// TestDaemonStartWithIpcModes checks that daemon starts fine given correct |
|
| 3084 |
+// arguments for default IPC mode, and bails out with incorrect ones. |
|
| 3085 |
+// Both CLI option (--default-ipc-mode) and config parameter are tested. |
|
| 3086 |
+func (s *DockerDaemonSuite) TestDaemonStartWithIpcModes(c *check.C) {
|
|
| 3087 |
+ ipcModes := []struct {
|
|
| 3088 |
+ mode string |
|
| 3089 |
+ valid bool |
|
| 3090 |
+ }{
|
|
| 3091 |
+ {"private", true},
|
|
| 3092 |
+ {"shareable", true},
|
|
| 3093 |
+ |
|
| 3094 |
+ {"host", false},
|
|
| 3095 |
+ {"container:123", false},
|
|
| 3096 |
+ {"nosuchmode", false},
|
|
| 3097 |
+ } |
|
| 3098 |
+ |
|
| 3099 |
+ for _, from := range []string{"config", "cli"} {
|
|
| 3100 |
+ for _, m := range ipcModes {
|
|
| 3101 |
+ testDaemonStartIpcMode(c, from, m.mode, m.valid) |
|
| 3102 |
+ } |
|
| 3103 |
+ } |
|
| 3104 |
+} |
|
| 3105 |
+ |
|
| 3106 |
+// TestDaemonRestartIpcMode makes sure a container keeps its ipc mode |
|
| 3107 |
+// (derived from daemon default) even after the daemon is restarted |
|
| 3108 |
+// with a different default ipc mode. |
|
| 3109 |
+func (s *DockerDaemonSuite) TestDaemonRestartIpcMode(c *check.C) {
|
|
| 3110 |
+ f, err := ioutil.TempFile("", "test-daemon-ipc-config-restart")
|
|
| 3111 |
+ c.Assert(err, checker.IsNil) |
|
| 3112 |
+ file := f.Name() |
|
| 3113 |
+ defer os.Remove(file) |
|
| 3114 |
+ c.Assert(f.Close(), checker.IsNil) |
|
| 3115 |
+ |
|
| 3116 |
+ config := []byte(`{"default-ipc-mode": "private"}`)
|
|
| 3117 |
+ c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil) |
|
| 3118 |
+ s.d.StartWithBusybox(c, "--config-file", file) |
|
| 3119 |
+ |
|
| 3120 |
+ // check the container is created with private ipc mode as per daemon default |
|
| 3121 |
+ name := "ipc1" |
|
| 3122 |
+ _, err = s.d.Cmd("run", "-d", "--name", name, "--restart=always", "busybox", "top")
|
|
| 3123 |
+ c.Assert(err, checker.IsNil) |
|
| 3124 |
+ m, err := s.d.InspectField(name, ".HostConfig.IpcMode") |
|
| 3125 |
+ c.Assert(err, check.IsNil) |
|
| 3126 |
+ c.Assert(m, checker.Equals, "private") |
|
| 3127 |
+ |
|
| 3128 |
+ // restart the daemon with shareable default ipc mode |
|
| 3129 |
+ config = []byte(`{"default-ipc-mode": "shareable"}`)
|
|
| 3130 |
+ c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil) |
|
| 3131 |
+ s.d.Restart(c, "--config-file", file) |
|
| 3132 |
+ |
|
| 3133 |
+ // check the container is still having private ipc mode |
|
| 3134 |
+ m, err = s.d.InspectField(name, ".HostConfig.IpcMode") |
|
| 3135 |
+ c.Assert(err, check.IsNil) |
|
| 3136 |
+ c.Assert(m, checker.Equals, "private") |
|
| 3137 |
+ |
|
| 3138 |
+ // check a new container is created with shareable ipc mode as per new daemon default |
|
| 3139 |
+ name = "ipc2" |
|
| 3140 |
+ _, err = s.d.Cmd("run", "-d", "--name", name, "busybox", "top")
|
|
| 3141 |
+ c.Assert(err, checker.IsNil) |
|
| 3142 |
+ m, err = s.d.InspectField(name, ".HostConfig.IpcMode") |
|
| 3143 |
+ c.Assert(err, check.IsNil) |
|
| 3144 |
+ c.Assert(m, checker.Equals, "shareable") |
|
| 3145 |
+} |
|
| 3146 |
+ |
|
| 2988 | 3147 |
// TestFailedPluginRemove makes sure that a failed plugin remove does not block |
| 2989 | 3148 |
// the daemon from starting |
| 2990 | 3149 |
func (s *DockerDaemonSuite) TestFailedPluginRemove(c *check.C) {
|
| ... | ... |
@@ -428,7 +428,32 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) {
|
| 428 | 428 |
out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c))
|
| 429 | 429 |
c.Assert(err, checker.IsNil) |
| 430 | 430 |
|
| 431 |
- c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (allow-nondistributable-artifacts=[], 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, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName))
|
|
| 431 |
+ // only check for values known (daemon ID/name) or explicitly set above, |
|
| 432 |
+ // otherwise just check for names being present. |
|
| 433 |
+ expectedSubstrings := []string{
|
|
| 434 |
+ " daemon reload " + daemonID + " ", |
|
| 435 |
+ "(allow-nondistributable-artifacts=[", |
|
| 436 |
+ " cluster-advertise=, ", |
|
| 437 |
+ " cluster-store=, ", |
|
| 438 |
+ " cluster-store-opts={",
|
|
| 439 |
+ " debug=true, ", |
|
| 440 |
+ " default-ipc-mode=", |
|
| 441 |
+ " default-runtime=", |
|
| 442 |
+ " default-shm-size=", |
|
| 443 |
+ " insecure-registries=[", |
|
| 444 |
+ " labels=[\"bar=foo\"], ", |
|
| 445 |
+ " live-restore=", |
|
| 446 |
+ " max-concurrent-downloads=1, ", |
|
| 447 |
+ " max-concurrent-uploads=5, ", |
|
| 448 |
+ " name=" + daemonName, |
|
| 449 |
+ " registry-mirrors=[", |
|
| 450 |
+ " runtimes=", |
|
| 451 |
+ " shutdown-timeout=10)", |
|
| 452 |
+ } |
|
| 453 |
+ |
|
| 454 |
+ for _, s := range expectedSubstrings {
|
|
| 455 |
+ c.Assert(out, checker.Contains, s) |
|
| 456 |
+ } |
|
| 432 | 457 |
} |
| 433 | 458 |
|
| 434 | 459 |
func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {
|
| ... | ... |
@@ -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 |
|