Skip to content

Support --ipc=(shareable|container:<container>) flag #2757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cmd/nerdctl/container_restart_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,33 @@ func TestRestartPIDContainer(t *testing.T) {
assert.Equal(t, baseOutput, sharedOutput)
}

func TestRestartIPCContainer(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

const shmSize = "32m"
baseContainerName := testutil.Identifier(t)
defer base.Cmd("rm", "-f", baseContainerName).Run()
base.Cmd("run", "-d", "--shm-size", shmSize, "--ipc", "shareable", "--name", baseContainerName, testutil.AlpineImage, "sleep", "infinity").AssertOK()

sharedContainerName := fmt.Sprintf("%s-shared", baseContainerName)
defer base.Cmd("rm", "-f", sharedContainerName).Run()
base.Cmd("run", "-d", "--name", sharedContainerName, fmt.Sprintf("--ipc=container:%s", baseContainerName), testutil.AlpineImage, "sleep", "infinity").AssertOK()

base.Cmd("stop", baseContainerName).Run()
base.Cmd("stop", sharedContainerName).Run()

base.Cmd("restart", baseContainerName).AssertOK()
base.Cmd("restart", sharedContainerName).AssertOK()

baseShmSizeResult := base.Cmd("exec", baseContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run()
baseOutput := strings.TrimSpace(baseShmSizeResult.Stdout())
sharedShmSizeResult := base.Cmd("exec", sharedContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run()
sharedOutput := strings.TrimSpace(sharedShmSizeResult.Stdout())

assert.Equal(t, baseOutput, sharedOutput)
}

func TestRestartWithTime(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
Expand Down
58 changes: 58 additions & 0 deletions cmd/nerdctl/container_run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,64 @@ func TestRunShmSize(t *testing.T) {
base.Cmd("run", "--rm", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
}

func TestRunShmSizeIPCShareable(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
const shmSize = "32m"

container := testutil.Identifier(t)
base.Cmd("run", "--rm", "--name", container, "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
defer base.Cmd("rm", "-f", container)
}

func TestRunIPCShareableRemoveMount(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
container := testutil.Identifier(t)

base.Cmd("run", "--name", container, "--ipc", "shareable", testutil.AlpineImage, "sleep", "0").AssertOK()
base.Cmd("rm", container).AssertOK()
}

func TestRunIPCContainerNotExists(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

container := testutil.Identifier(t)
result := base.Cmd("run", "--name", container, "--ipc", "container:abcd1234", testutil.AlpineImage, "sleep", "infinity").Run()
defer base.Cmd("rm", "-f", container)
combined := result.Combined()
if !strings.Contains(strings.ToLower(combined), "no such container: abcd1234") {
t.Fatalf("unexpected output: %s", combined)
}
}

func TestRunShmSizeIPCContainer(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

const shmSize = "32m"
sharedContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run()
baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())
defer base.Cmd("rm", "-f", baseContainerID).Run()

base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", baseContainerID),
testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
}

func TestRunIPCContainer(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

const shmSize = "32m"
victimContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run()
victimContainerID := strings.TrimSpace(victimContainerResult.Stdout())
defer base.Cmd("rm", "-f", victimContainerID).Run()

base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", victimContainerID),
testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k")
}

func TestRunPidHost(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
Expand Down
2 changes: 1 addition & 1 deletion docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ Logging flags:

Shared memory flags:

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

GPU flags:
Expand Down
32 changes: 14 additions & 18 deletions pkg/cmd/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
Expand All @@ -44,6 +43,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/idgen"
"github.com/containerd/nerdctl/v2/pkg/imgutil"
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
"github.com/containerd/nerdctl/v2/pkg/labels"
"github.com/containerd/nerdctl/v2/pkg/logging"
"github.com/containerd/nerdctl/v2/pkg/mountutil"
Expand Down Expand Up @@ -387,23 +387,6 @@ func generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage,
return opts, cOpts, nil
}

// withBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind.
// Required for --ipc=host on rootless.
func withBindMountHostIPC(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
for i, m := range s.Mounts {
switch p := path.Clean(m.Destination); p {
case "/dev/shm", "/dev/mqueue":
s.Mounts[i] = specs.Mount{
Destination: p,
Type: "bind",
Source: p,
Options: []string{"rbind", "nosuid", "noexec", "nodev"},
}
}
}
return nil
}

// GenerateLogURI generates a log URI for the current container store
func GenerateLogURI(dataStore string) (*url.URL, error) {
selfExe, err := os.Executable()
Expand Down Expand Up @@ -516,6 +499,8 @@ type internalLabels struct {
anonVolumes []string
// pid namespace
pidContainer string
// ipc namespace & dev/shm
ipc string
// log
logURI string
}
Expand Down Expand Up @@ -591,6 +576,10 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO
m[labels.PIDContainer] = internalLabels.pidContainer
}

if internalLabels.ipc != "" {
m[labels.IPC] = internalLabels.ipc
}

return containerd.WithAdditionalContainerLabels(m), nil
}

Expand Down Expand Up @@ -717,6 +706,13 @@ func generateGcFunc(ctx context.Context, container containerd.Container, ns, id,
}
}

ipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc)
if ipcErr != nil {
log.G(ctx).WithError(ipcErr).Warnf("failed to decode ipc label for container %q", id)
}
if ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil {
log.G(ctx).WithError(ipcErr).Warnf("failed to clean up ipc for container %q", id)
}
if rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {
log.G(ctx).WithError(rmErr).Warnf("failed to remove container %q state dir %q", id, internalLabels.stateDir)
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/cmd/container/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/containerutil"
"github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
"github.com/containerd/nerdctl/v2/pkg/labels"
"github.com/containerd/nerdctl/v2/pkg/namestore"
)
Expand Down Expand Up @@ -105,6 +106,14 @@ func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions
if err != nil {
return err
}
ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC])
if err != nil {
return err
}
err = ipcutil.CleanUp(ipc)
if err != nil {
return err
}
stateDir := l[labels.StateDir]
name := l[labels.Name]
dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address)
Expand Down
42 changes: 26 additions & 16 deletions pkg/cmd/container/run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ import (
"github.com/containerd/nerdctl/v2/pkg/bypass4netnsutil"
"github.com/containerd/nerdctl/v2/pkg/containerutil"
"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
"github.com/containerd/nerdctl/v2/pkg/strutil"
"github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -83,13 +83,6 @@ func setPlatformOptions(ctx context.Context, client *containerd.Client, id, uts
return nil, err
}
opts = append(opts, b4nnOpts...)
if len(options.ShmSize) > 0 {
shmBytes, err := units.RAMInBytes(options.ShmSize)
if err != nil {
return nil, err
}
opts = append(opts, oci.WithDevShmSize(shmBytes/1024))
}

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

switch options.IPC {
case "host":
opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))
opts = append(opts, withBindMountHostIPC)
case "private", "":
// If nothing is specified, or if private, default to normal behavior
default:
return nil, fmt.Errorf("unknown ipc value. valid values are 'private' or 'host', got: %q", options.IPC)
stateDir := internalLabels.stateDir
ipcOpts, ipcLabel, err := generateIPCOpts(ctx, client, options.IPC, options.ShmSize, stateDir)
if err != nil {
return nil, err
}
internalLabels.ipc = ipcLabel
opts = append(opts, ipcOpts...)

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

func generateIPCOpts(ctx context.Context, client *containerd.Client, ipcFlag string, shmSize string, stateDir string) ([]oci.SpecOpts, string, error) {
ipcFlag = strings.ToLower(ipcFlag)

ipc, err := ipcutil.DetectFlags(ctx, client, stateDir, ipcFlag, shmSize)
if err != nil {
return nil, "", err
}
ipcLabel, err := ipcutil.EncodeIPCLabel(ipc)
if err != nil {
return nil, "", err
}
opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)
if err != nil {
return nil, "", err
}

return opts, ipcLabel, nil
}

func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, string, error) {
opts := make([]oci.SpecOpts, 0)
pid = strings.ToLower(pid)
Expand Down
24 changes: 24 additions & 0 deletions pkg/containerutil/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/containerd/containerd"
"github.com/containerd/containerd/oci"
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
"github.com/containerd/nerdctl/v2/pkg/labels"
"github.com/containerd/nerdctl/v2/pkg/netutil/nettype"
"github.com/opencontainers/runtime-spec/specs-go"
Expand Down Expand Up @@ -104,3 +105,26 @@ func ReconfigPIDContainer(ctx context.Context, c containerd.Container, client *c
}
return nil
}

// ReconfigIPCContainer reconfigures the container's spec options for sharing IPC namespace and volumns.
func ReconfigIPCContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {
ipc, err := ipcutil.DecodeIPCLabel(lab[labels.IPC])
if err != nil {
return err
}
opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client)
if err != nil {
return err
}
spec, err := c.Spec(ctx)
if err != nil {
return err
}
err = c.Update(ctx, containerd.UpdateContainerOpts(
containerd.WithSpec(spec, oci.Compose(opts...)),
))
if err != nil {
return err
}
return nil
}
15 changes: 15 additions & 0 deletions pkg/containerutil/containerutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/containerd/nerdctl/v2/pkg/consoleutil"
"github.com/containerd/nerdctl/v2/pkg/errutil"
"github.com/containerd/nerdctl/v2/pkg/formatter"
"github.com/containerd/nerdctl/v2/pkg/ipcutil"
"github.com/containerd/nerdctl/v2/pkg/labels"
"github.com/containerd/nerdctl/v2/pkg/nsutil"
"github.com/containerd/nerdctl/v2/pkg/portutil"
Expand Down Expand Up @@ -228,6 +229,10 @@ func Start(ctx context.Context, container containerd.Container, flagA bool, clie
return err
}

if err := ReconfigIPCContainer(ctx, container, client, lab); err != nil {
return err
}

process, err := container.Spec(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -331,6 +336,16 @@ func Stop(ctx context.Context, container containerd.Container, timeout *time.Dur
if err != nil {
return err
}
ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC])
if err != nil {
return err
}
// defer umount
defer func() {
if err := ipcutil.CleanUp(ipc); err != nil {
log.G(ctx).Warnf("failed to clean up IPC container %s: %s", container.ID(), err)
}
}()

if timeout == nil {
t, ok := l[labels.StopTimeout]
Expand Down
Loading