Skip to content

Commit 497fc88

Browse files
committed
Allow IPC namespace to be shared between containers or with the host
Some workloads rely on IPC for communications with other processes. We would like to split workloads between two container but still allow them to communicate though shared IPC. This patch mimics the --net code to allow --ipc=host to not split off the IPC Namespace. ipc=container:CONTAINERID to share ipc between containers If you share IPC between containers, then you need to make sure SELinux labels match. Docker-DCO-1.1-Signed-off-by: Dan Walsh <dwalsh@redhat.com> (github: rhatdan)
1 parent 6ad1cd5 commit 497fc88

9 files changed

Lines changed: 298 additions & 4 deletions

File tree

daemon/container.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ func populateCommand(c *Container, env []string) error {
233233
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
234234
}
235235

236+
ipc := &execdriver.Ipc{}
237+
238+
if c.hostConfig.IpcMode.IsContainer() {
239+
ic, err := c.getIpcContainer()
240+
if err != nil {
241+
return err
242+
}
243+
ipc.ContainerID = ic.ID
244+
} else {
245+
ipc.HostIpc = c.hostConfig.IpcMode.IsHost()
246+
}
247+
236248
// Build lists of devices allowed and created within the container.
237249
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
238250
for i, deviceMapping := range c.hostConfig.Devices {
@@ -274,6 +286,7 @@ func populateCommand(c *Container, env []string) error {
274286
InitPath: "/.dockerinit",
275287
WorkingDir: c.Config.WorkingDir,
276288
Network: en,
289+
Ipc: ipc,
277290
Resources: resources,
278291
AllowedDevices: allowedDevices,
279292
AutoCreatedDevices: autoCreatedDevices,
@@ -1250,10 +1263,25 @@ func (container *Container) GetMountLabel() string {
12501263
return container.MountLabel
12511264
}
12521265

1266+
func (container *Container) getIpcContainer() (*Container, error) {
1267+
containerID := container.hostConfig.IpcMode.Container()
1268+
c := container.daemon.Get(containerID)
1269+
if c == nil {
1270+
return nil, fmt.Errorf("no such container to join IPC: %s", containerID)
1271+
}
1272+
if !c.IsRunning() {
1273+
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", containerID)
1274+
}
1275+
return c, nil
1276+
}
1277+
12531278
func (container *Container) getNetworkedContainer() (*Container, error) {
12541279
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
12551280
switch parts[0] {
12561281
case "container":
1282+
if len(parts) != 2 {
1283+
return nil, fmt.Errorf("no container specified to join network")
1284+
}
12571285
nc := container.daemon.Get(parts[1])
12581286
if nc == nil {
12591287
return nil, fmt.Errorf("no such container to join network: %s", parts[1])

daemon/create.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package daemon
22

33
import (
4+
"fmt"
5+
46
"github.com/docker/docker/engine"
57
"github.com/docker/docker/graph"
68
"github.com/docker/docker/pkg/parsers"
79
"github.com/docker/docker/runconfig"
10+
"github.com/docker/libcontainer/label"
811
)
912

1013
func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
@@ -80,6 +83,12 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
8083
if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil {
8184
return nil, nil, err
8285
}
86+
if hostConfig != nil && config.SecurityOpt == nil {
87+
config.SecurityOpt, err = daemon.GenerateSecurityOpt(hostConfig.IpcMode)
88+
if err != nil {
89+
return nil, nil, err
90+
}
91+
}
8392
if container, err = daemon.newContainer(name, config, img); err != nil {
8493
return nil, nil, err
8594
}
@@ -99,3 +108,20 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
99108
}
100109
return container, warnings, nil
101110
}
111+
func (daemon *Daemon) GenerateSecurityOpt(ipcMode runconfig.IpcMode) ([]string, error) {
112+
if ipcMode.IsHost() {
113+
return label.DisableSecOpt(), nil
114+
}
115+
if ipcContainer := ipcMode.Container(); ipcContainer != "" {
116+
c := daemon.Get(ipcContainer)
117+
if c == nil {
118+
return nil, fmt.Errorf("no such container to join IPC: %s", ipcContainer)
119+
}
120+
if !c.IsRunning() {
121+
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", ipcContainer)
122+
}
123+
124+
return label.DupSecOpt(c.ProcessLabel), nil
125+
}
126+
return nil, nil
127+
}

daemon/execdriver/driver.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ type Network struct {
6262
HostNetworking bool `json:"host_networking"`
6363
}
6464

65+
// IPC settings of the container
66+
type Ipc struct {
67+
ContainerID string `json:"container_id"` // id of the container to join ipc.
68+
HostIpc bool `json:"host_ipc"`
69+
}
70+
6571
type NetworkInterface struct {
6672
Gateway string `json:"gateway"`
6773
IPAddress string `json:"ip"`
@@ -106,6 +112,7 @@ type Command struct {
106112
WorkingDir string `json:"working_dir"`
107113
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
108114
Network *Network `json:"network"`
115+
Ipc *Ipc `json:"ipc"`
109116
Resources *Resources `json:"resources"`
110117
Mounts []Mount `json:"mounts"`
111118
AllowedDevices []*devices.Device `json:"allowed_devices"`

daemon/execdriver/native/create.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Config, e
3636
container.MountConfig.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
3737
container.RestrictSys = true
3838

39+
if err := d.createIpc(container, c); err != nil {
40+
return nil, err
41+
}
42+
3943
if err := d.createNetwork(container, c); err != nil {
4044
return nil, err
4145
}
@@ -124,6 +128,28 @@ func (d *driver) createNetwork(container *libcontainer.Config, c *execdriver.Com
124128
return nil
125129
}
126130

131+
func (d *driver) createIpc(container *libcontainer.Config, c *execdriver.Command) error {
132+
if c.Ipc.HostIpc {
133+
container.Namespaces["NEWIPC"] = false
134+
return nil
135+
}
136+
137+
if c.Ipc.ContainerID != "" {
138+
d.Lock()
139+
active := d.activeContainers[c.Ipc.ContainerID]
140+
d.Unlock()
141+
142+
if active == nil || active.cmd.Process == nil {
143+
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
144+
}
145+
cmd := active.cmd
146+
147+
container.IpcNsPath = filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "ipc")
148+
}
149+
150+
return nil
151+
}
152+
127153
func (d *driver) setPrivileged(container *libcontainer.Config) (err error) {
128154
container.Capabilities = capabilities.GetAllCapabilities()
129155
container.Cgroups.AllowAllDevices = true

docs/man/docker-run.1.md

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ docker-run - Run a command in a new container
2323
[**--expose**[=*[]*]]
2424
[**-h**|**--hostname**[=*HOSTNAME*]]
2525
[**-i**|**--interactive**[=*false*]]
26+
[**--ipc**[=*[]*]]
2627
[**--security-opt**[=*[]*]]
2728
[**--link**[=*[]*]]
2829
[**--lxc-conf**[=*[]*]]
@@ -142,6 +143,12 @@ ENTRYPOINT.
142143
**-i**, **--interactive**=*true*|*false*
143144
When set to true, keep stdin open even if not attached. The default is false.
144145

146+
**--ipc**=[]
147+
Set the IPC mode for the container
148+
**container**:<*name*|*id*>: reuses another container's IPC stack
149+
**host**: use the host's IPC stack inside the container.
150+
Note: the host mode gives the container full access to local IPC and is therefore considered insecure.
151+
145152
**--security-opt**=*secdriver*:*name*:*value*
146153
"label:user:USER" : Set the label user for the container
147154
"label:role:ROLE" : Set the label role for the container
@@ -183,10 +190,11 @@ and foreground Docker containers.
183190

184191
**--net**="bridge"
185192
Set the Network mode for the container
186-
'bridge': creates a new network stack for the container on the docker bridge
187-
'none': no networking for this container
188-
'container:<name|id>': reuses another container network stack
189-
'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
193+
**bridge**: creates a new network stack for the container on the docker bridge
194+
**none**: no networking for this container
195+
**container**:<*name*|*id*>: reuses another container's network stack
196+
**host**: use the host network stack inside the container.
197+
Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
190198

191199
**--mac-address**=*macaddress*
192200
Set the MAC address for the container's Ethernet device:
@@ -310,6 +318,71 @@ you’d like to connect instead, as in:
310318

311319
# docker run -a stdin -a stdout -i -t fedora /bin/bash
312320

321+
## Sharing IPC between containers
322+
323+
Using shm_server.c available here: http://www.cs.cf.ac.uk/Dave/C/node27.html
324+
325+
Testing `--ipc=host` mode:
326+
327+
Host shows a shared memory segment with 7 pids attached, happens to be from httpd:
328+
329+
```
330+
$ sudo ipcs -m
331+
332+
------ Shared Memory Segments --------
333+
key shmid owner perms bytes nattch status
334+
0x01128e25 0 root 600 1000 7
335+
```
336+
337+
Now run a regular container, and it correctly does NOT see the shared memory segment from the host:
338+
339+
```
340+
$ sudo docker run -it shm ipcs -m
341+
342+
------ Shared Memory Segments --------
343+
key shmid owner perms bytes nattch status
344+
```
345+
346+
Run a container with the new `--ipc=host` option, and it now sees the shared memory segment from the host httpd:
347+
348+
```
349+
$ sudo docker run -it --ipc=host shm ipcs -m
350+
351+
------ Shared Memory Segments --------
352+
key shmid owner perms bytes nattch status
353+
0x01128e25 0 root 600 1000 7
354+
```
355+
Testing `--ipc=container:CONTAINERID` mode:
356+
357+
Start a container with a program to create a shared memory segment:
358+
```
359+
sudo docker run -it shm bash
360+
$ sudo shm/shm_server &
361+
$ sudo ipcs -m
362+
363+
------ Shared Memory Segments --------
364+
key shmid owner perms bytes nattch status
365+
0x0000162e 0 root 666 27 1
366+
```
367+
Create a 2nd container correctly shows no shared memory segment from 1st container:
368+
```
369+
$ sudo docker run shm ipcs -m
370+
371+
------ Shared Memory Segments --------
372+
key shmid owner perms bytes nattch status
373+
```
374+
375+
Create a 3rd container using the new --ipc=container:CONTAINERID option, now it shows the shared memory segment from the first:
376+
377+
```
378+
$ sudo docker run -it --ipc=container:ed735b2264ac shm ipcs -m
379+
$ sudo ipcs -m
380+
381+
------ Shared Memory Segments --------
382+
key shmid owner perms bytes nattch status
383+
0x0000162e 0 root 666 27 1
384+
```
385+
313386
## Linking Containers
314387

315388
The link feature allows multiple containers to communicate with each other. For

docs/sources/reference/run.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ following options.
5050
- [Container Identification](#container-identification)
5151
- [Name (--name)](#name-name)
5252
- [PID Equivalent](#pid-equivalent)
53+
- [IPC Settings](#ipc-settings)
5354
- [Network Settings](#network-settings)
5455
- [Clean Up (--rm)](#clean-up-rm)
5556
- [Runtime Constraints on CPU and Memory](#runtime-constraints-on-cpu-and-memory)
@@ -131,6 +132,22 @@ While not strictly a means of identifying a container, you can specify a version
131132
image you'd like to run the container with by adding `image[:tag]` to the command. For
132133
example, `docker run ubuntu:14.04`.
133134

135+
## IPC Settings
136+
--ipc="" : Set the IPC mode for the container,
137+
'container:<name|id>': reuses another container's IPC namespace
138+
'host': use the host's IPC namespace inside the container
139+
By default, all containers have the IPC namespace enabled
140+
141+
IPC (POSIX/SysV IPC) namespace provides separation of named shared memory segments, semaphores and message queues.
142+
143+
Shared memory segments are used to accelerate inter-process communication at
144+
memory speed, rather than through pipes or through the network stack. Shared
145+
memory is commonly used by databases and custom-built (typically C/OpenMPI,
146+
C++/using boost libraries) high performance applications for scientific
147+
computing and financial services industries. If these types of applications
148+
are broken into multiple containers, you might need to share the IPC mechanisms
149+
of the containers.
150+
134151
## Network settings
135152

136153
--dns=[] : Set custom dns servers for the container

integration-cli/docker_cli_run_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2568,3 +2568,73 @@ func TestRunUnknownCommand(t *testing.T) {
25682568

25692569
logDone("run - Unknown Command")
25702570
}
2571+
2572+
func TestRunModeIpcHost(t *testing.T) {
2573+
hostIpc, err := os.Readlink("/proc/1/ns/ipc")
2574+
if err != nil {
2575+
t.Fatal(err)
2576+
}
2577+
2578+
cmd := exec.Command(dockerBinary, "run", "--ipc=host", "busybox", "readlink", "/proc/self/ns/ipc")
2579+
out2, _, err := runCommandWithOutput(cmd)
2580+
if err != nil {
2581+
t.Fatal(err, out2)
2582+
}
2583+
2584+
out2 = strings.Trim(out2, "\n")
2585+
if hostIpc != out2 {
2586+
t.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2)
2587+
}
2588+
2589+
cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/ipc")
2590+
out2, _, err = runCommandWithOutput(cmd)
2591+
if err != nil {
2592+
t.Fatal(err, out2)
2593+
}
2594+
2595+
out2 = strings.Trim(out2, "\n")
2596+
if hostIpc == out2 {
2597+
t.Fatalf("IPC should be different without --ipc=host %s != %s\n", hostIpc, out2)
2598+
}
2599+
deleteAllContainers()
2600+
2601+
logDone("run - hostname and several network modes")
2602+
}
2603+
2604+
func TestRunModeIpcContainer(t *testing.T) {
2605+
cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
2606+
out, _, err := runCommandWithOutput(cmd)
2607+
if err != nil {
2608+
t.Fatal(err, out)
2609+
}
2610+
id := strings.TrimSpace(out)
2611+
state, err := inspectField(id, "State.Running")
2612+
if err != nil {
2613+
t.Fatal(err)
2614+
}
2615+
if state != "true" {
2616+
t.Fatal("Container state is 'not running'")
2617+
}
2618+
pid1, err := inspectField(id, "State.Pid")
2619+
if err != nil {
2620+
t.Fatal(err)
2621+
}
2622+
2623+
parentContainerIpc, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/ipc", pid1))
2624+
if err != nil {
2625+
t.Fatal(err)
2626+
}
2627+
cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox", "readlink", "/proc/self/ns/ipc")
2628+
out2, _, err := runCommandWithOutput(cmd)
2629+
if err != nil {
2630+
t.Fatal(err, out2)
2631+
}
2632+
2633+
out2 = strings.Trim(out2, "\n")
2634+
if parentContainerIpc != out2 {
2635+
t.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2)
2636+
}
2637+
deleteAllContainers()
2638+
2639+
logDone("run - hostname and several network modes")
2640+
}

0 commit comments

Comments
 (0)