Skip to content

Commit b452c64

Browse files
committed
Support --ipc=container:<id> option
Signed-off-by: Min Uk Lee <[email protected]> - support ipc namespace - support /dev/shm
1 parent 2410e1f commit b452c64

13 files changed

+558
-35
lines changed

cmd/nerdctl/container_restart_linux_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ func TestRestartPIDContainer(t *testing.T) {
7171
assert.Equal(t, baseOutput, sharedOutput)
7272
}
7373

74+
func TestRestartIPCContainer(t *testing.T) {
75+
t.Parallel()
76+
base := testutil.NewBase(t)
77+
78+
const shmSize = "32m"
79+
baseContainerName := testutil.Identifier(t)
80+
defer base.Cmd("rm", "-f", baseContainerName).Run()
81+
base.Cmd("run", "-d", "--shm-size", shmSize, "--ipc", "shareable", "--name", baseContainerName, testutil.AlpineImage, "sleep", "infinity").AssertOK()
82+
83+
sharedContainerName := fmt.Sprintf("%s-shared", baseContainerName)
84+
defer base.Cmd("rm", "-f", sharedContainerName).Run()
85+
base.Cmd("run", "-d", "--name", sharedContainerName, fmt.Sprintf("--ipc=container:%s", baseContainerName), testutil.AlpineImage, "sleep", "infinity").AssertOK()
86+
87+
base.Cmd("stop", baseContainerName).Run()
88+
base.Cmd("stop", sharedContainerName).Run()
89+
90+
base.Cmd("restart", baseContainerName).AssertOK()
91+
base.Cmd("restart", sharedContainerName).AssertOK()
92+
93+
baseShmSizeResult := base.Cmd("exec", baseContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run()
94+
baseOutput := strings.TrimSpace(baseShmSizeResult.Stdout())
95+
sharedShmSizeResult := base.Cmd("exec", sharedContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run()
96+
sharedOutput := strings.TrimSpace(sharedShmSizeResult.Stdout())
97+
98+
assert.Equal(t, baseOutput, sharedOutput)
99+
}
100+
74101
func TestRestartWithTime(t *testing.T) {
75102
t.Parallel()
76103
base := testutil.NewBase(t)

cmd/nerdctl/container_run_linux_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,64 @@ func TestRunShmSize(t *testing.T) {
7070
base.Cmd("run", "--rm", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
7171
}
7272

73+
func TestRunShmSizeIPCShareable(t *testing.T) {
74+
t.Parallel()
75+
base := testutil.NewBase(t)
76+
const shmSize = "32m"
77+
78+
container := testutil.Identifier(t)
79+
base.Cmd("run", "--rm", "--name", container, "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
80+
defer base.Cmd("rm", "-f", container)
81+
}
82+
83+
func TestRunIPCShareableRemoveMount(t *testing.T) {
84+
t.Parallel()
85+
base := testutil.NewBase(t)
86+
container := testutil.Identifier(t)
87+
88+
base.Cmd("run", "--name", container, "--ipc", "shareable", testutil.AlpineImage, "sleep", "0").AssertOK()
89+
base.Cmd("rm", container).AssertOK()
90+
}
91+
92+
func TestRunIPCContainerNotExists(t *testing.T) {
93+
t.Parallel()
94+
base := testutil.NewBase(t)
95+
96+
container := testutil.Identifier(t)
97+
result := base.Cmd("run", "--name", container, "--ipc", "container:abcd1234", testutil.AlpineImage, "sleep", "infinity").Run()
98+
defer base.Cmd("rm", "-f", container)
99+
combined := result.Combined()
100+
if !strings.Contains(strings.ToLower(combined), "no such container: abcd1234") {
101+
t.Fatalf("unexpected output: %s", combined)
102+
}
103+
}
104+
105+
func TestRunShmSizeIPCContainer(t *testing.T) {
106+
t.Parallel()
107+
base := testutil.NewBase(t)
108+
109+
const shmSize = "32m"
110+
sharedContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run()
111+
baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())
112+
defer base.Cmd("rm", "-f", baseContainerID).Run()
113+
114+
base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", baseContainerID),
115+
testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
116+
}
117+
118+
func TestRunIPCContainer(t *testing.T) {
119+
t.Parallel()
120+
base := testutil.NewBase(t)
121+
122+
const shmSize = "32m"
123+
victimContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run()
124+
victimContainerID := strings.TrimSpace(victimContainerResult.Stdout())
125+
defer base.Cmd("rm", "-f", victimContainerID).Run()
126+
127+
base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", victimContainerID),
128+
testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
129+
}
130+
73131
func TestRunPidHost(t *testing.T) {
74132
t.Parallel()
75133
base := testutil.NewBase(t)

docs/command-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ Logging flags:
351351

352352
Shared memory flags:
353353

354-
- :whale: `--ipc`: IPC namespace to use
354+
- :whale: `--ipc=(host|private|shareable|container:<container>)`: IPC namespace to use and mount `/dev/shm`. Default: "private". Only implemented on Linux.
355355
- :whale: `--shm-size`: Size of `/dev/shm`
356356

357357
GPU flags:

pkg/cmd/container/create.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"net/url"
2525
"os"
2626
"os/exec"
27-
"path"
2827
"path/filepath"
2928
"runtime"
3029
"strconv"
@@ -44,6 +43,7 @@ import (
4443
"github.com/containerd/nerdctl/v2/pkg/idgen"
4544
"github.com/containerd/nerdctl/v2/pkg/imgutil"
4645
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
46+
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
4747
"github.com/containerd/nerdctl/v2/pkg/labels"
4848
"github.com/containerd/nerdctl/v2/pkg/logging"
4949
"github.com/containerd/nerdctl/v2/pkg/mountutil"
@@ -387,23 +387,6 @@ func generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage,
387387
return opts, cOpts, nil
388388
}
389389

390-
// withBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind.
391-
// Required for --ipc=host on rootless.
392-
func withBindMountHostIPC(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
393-
for i, m := range s.Mounts {
394-
switch p := path.Clean(m.Destination); p {
395-
case "/dev/shm", "/dev/mqueue":
396-
s.Mounts[i] = specs.Mount{
397-
Destination: p,
398-
Type: "bind",
399-
Source: p,
400-
Options: []string{"rbind", "nosuid", "noexec", "nodev"},
401-
}
402-
}
403-
}
404-
return nil
405-
}
406-
407390
// GenerateLogURI generates a log URI for the current container store
408391
func GenerateLogURI(dataStore string) (*url.URL, error) {
409392
selfExe, err := os.Executable()
@@ -516,6 +499,8 @@ type internalLabels struct {
516499
anonVolumes []string
517500
// pid namespace
518501
pidContainer string
502+
// ipc namespace & dev/shm
503+
ipc string
519504
// log
520505
logURI string
521506
}
@@ -591,6 +576,10 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
591576
m[labels.PIDContainer] = internalLabels.pidContainer
592577
}
593578

579+
if internalLabels.ipc != "" {
580+
m[labels.IPC] = internalLabels.ipc
581+
}
582+
594583
return containerd.WithAdditionalContainerLabels(m), nil
595584
}
596585

@@ -717,6 +706,13 @@ func generateGcFunc(ctx context.Context, container containerd.Container, ns, id,
717706
}
718707
}
719708

709+
ipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc)
710+
if ipcErr != nil {
711+
log.G(ctx).WithError(ipcErr).Warnf("failed to decode ipc label for container %q", id)
712+
}
713+
if ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil {
714+
log.G(ctx).WithError(ipcErr).Warnf("failed to clean up ipc for container %q", id)
715+
}
720716
if rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {
721717
log.G(ctx).WithError(rmErr).Warnf("failed to remove container %q state dir %q", id, internalLabels.stateDir)
722718
}

pkg/cmd/container/remove.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3737
"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore"
3838
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
39+
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
3940
"github.com/containerd/nerdctl/v2/pkg/labels"
4041
"github.com/containerd/nerdctl/v2/pkg/namestore"
4142
)
@@ -105,6 +106,14 @@ func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions
105106
if err != nil {
106107
return err
107108
}
109+
ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC])
110+
if err != nil {
111+
return err
112+
}
113+
err = ipcutil.CleanUp(ipc)
114+
if err != nil {
115+
return err
116+
}
108117
stateDir := l[labels.StateDir]
109118
name := l[labels.Name]
110119
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)

pkg/cmd/container/run_linux.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import (
3030
"github.com/containerd/nerdctl/v2/pkg/bypass4netnsutil"
3131
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3232
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
33+
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
3334
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3435
"github.com/containerd/nerdctl/v2/pkg/strutil"
35-
"github.com/docker/go-units"
3636
"github.com/opencontainers/runtime-spec/specs-go"
3737
)
3838

@@ -83,13 +83,6 @@ func setPlatformOptions(ctx context.Context, client *containerd.Client, id, uts
8383
return nil, err
8484
}
8585
opts = append(opts, b4nnOpts...)
86-
if len(options.ShmSize) > 0 {
87-
shmBytes, err := units.RAMInBytes(options.ShmSize)
88-
if err != nil {
89-
return nil, err
90-
}
91-
opts = append(opts, oci.WithDevShmSize(shmBytes/1024))
92-
}
9386

9487
ulimitOpts, err := generateUlimitsOpts(options.Ulimit)
9588
if err != nil {
@@ -149,15 +142,13 @@ func generateNamespaceOpts(
149142
return nil, fmt.Errorf("unknown uts value. valid value(s) are 'host', got: %q", uts)
150143
}
151144

152-
switch options.IPC {
153-
case "host":
154-
opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))
155-
opts = append(opts, withBindMountHostIPC)
156-
case "private", "":
157-
// If nothing is specified, or if private, default to normal behavior
158-
default:
159-
return nil, fmt.Errorf("unknown ipc value. valid values are 'private' or 'host', got: %q", options.IPC)
145+
stateDir := internalLabels.stateDir
146+
ipcOpts, ipcLabel, err := generateIPCOpts(ctx, client, options.IPC, options.ShmSize, stateDir)
147+
if err != nil {
148+
return nil, err
160149
}
150+
internalLabels.ipc = ipcLabel
151+
opts = append(opts, ipcOpts...)
161152

162153
pidOpts, pidLabel, err := generatePIDOpts(ctx, client, options.Pid)
163154
if err != nil {
@@ -169,6 +160,25 @@ func generateNamespaceOpts(
169160
return opts, nil
170161
}
171162

163+
func generateIPCOpts(ctx context.Context, client *containerd.Client, ipcFlag string, shmSize string, stateDir string) ([]oci.SpecOpts, string, error) {
164+
ipcFlag = strings.ToLower(ipcFlag)
165+
166+
ipc, err := ipcutil.DetectFlags(ctx, client, stateDir, ipcFlag, shmSize)
167+
if err != nil {
168+
return nil, "", err
169+
}
170+
ipcLabel, err := ipcutil.EncodeIPCLabel(ipc)
171+
if err != nil {
172+
return nil, "", err
173+
}
174+
opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)
175+
if err != nil {
176+
return nil, "", err
177+
}
178+
179+
return opts, ipcLabel, nil
180+
}
181+
172182
func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, string, error) {
173183
opts := make([]oci.SpecOpts, 0)
174184
pid = strings.ToLower(pid)

pkg/containerutil/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
"github.com/containerd/containerd"
2828
"github.com/containerd/containerd/oci"
29+
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
2930
"github.com/containerd/nerdctl/v2/pkg/labels"
3031
"github.com/containerd/nerdctl/v2/pkg/netutil/nettype"
3132
"github.com/opencontainers/runtime-spec/specs-go"
@@ -104,3 +105,26 @@ func ReconfigPIDContainer(ctx context.Context, c containerd.Container, client *c
104105
}
105106
return nil
106107
}
108+
109+
// ReconfigIPCContainer reconfigures the container's spec options for sharing IPC namespace and volumns.
110+
func ReconfigIPCContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {
111+
ipc, err := ipcutil.DecodeIPCLabel(lab[labels.IPC])
112+
if err != nil {
113+
return err
114+
}
115+
opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)
116+
if err != nil {
117+
return err
118+
}
119+
spec, err := c.Spec(ctx)
120+
if err != nil {
121+
return err
122+
}
123+
err = c.Update(ctx, containerd.UpdateContainerOpts(
124+
containerd.WithSpec(spec, oci.Compose(opts...)),
125+
))
126+
if err != nil {
127+
return err
128+
}
129+
return nil
130+
}

pkg/containerutil/containerutil.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/containerd/nerdctl/v2/pkg/consoleutil"
3939
"github.com/containerd/nerdctl/v2/pkg/errutil"
4040
"github.com/containerd/nerdctl/v2/pkg/formatter"
41+
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
4142
"github.com/containerd/nerdctl/v2/pkg/labels"
4243
"github.com/containerd/nerdctl/v2/pkg/nsutil"
4344
"github.com/containerd/nerdctl/v2/pkg/portutil"
@@ -228,6 +229,10 @@ func Start(ctx context.Context, container containerd.Container, flagA bool, clie
228229
return err
229230
}
230231

232+
if err := ReconfigIPCContainer(ctx, container, client, lab); err != nil {
233+
return err
234+
}
235+
231236
process, err := container.Spec(ctx)
232237
if err != nil {
233238
return err
@@ -331,6 +336,16 @@ func Stop(ctx context.Context, container containerd.Container, timeout *time.Dur
331336
if err != nil {
332337
return err
333338
}
339+
ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC])
340+
if err != nil {
341+
return err
342+
}
343+
// defer umount
344+
defer func() {
345+
if err := ipcutil.CleanUp(ipc); err != nil {
346+
log.G(ctx).Warnf("failed to clean up IPC container %s: %s", container.ID(), err)
347+
}
348+
}()
334349

335350
if timeout == nil {
336351
t, ok := l[labels.StopTimeout]

0 commit comments

Comments
 (0)