Skip to content

Commit

Permalink
Support --pid=container:xxx for nerdctl run cmd
Browse files Browse the repository at this point in the history
- Add context and client to `setPlatformOptions()`
- Add branch for platform-specific container opts
- Add PIDContainer label into container's labels. It only works on linux
platform.
- Add reconfigPIDContainer() into container's start process. It recovers
the pid namespace from its PIDContainer label.

Signed-off-by: Min Uk Lee <minuk.dev@gmail.com>
  • Loading branch information
minuk-dev committed Oct 18, 2022
1 parent b4c0109 commit 8680bdb
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ Basic flags:
- :whale: `--rm`: Automatically remove the container when it exits
- :whale: `--pull=(always|missing|never)`: Pull image before running
- Default: "missing"
- :whale: `--pid=(host)`: PID namespace to use
- :whale: `--pid=(host|container:<container>)`: PID namespace to use
- :whale: `--stop-signal`: Signal to stop a container (default "SIGTERM")
- :whale: `--stop-timeout`: Timeout (in seconds) to stop a container

Expand Down
27 changes: 27 additions & 0 deletions cmd/nerdctl/restart_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package main

import (
"fmt"
"strings"
"testing"

"github.com/containerd/nerdctl/pkg/testutil"
Expand All @@ -42,3 +44,28 @@ func TestRestart(t *testing.T) {
newPid := newInspect.State.Pid
assert.Assert(t, pid != newPid)
}

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

baseContainerName := testutil.Identifier(t)
base.Cmd("run", "-d", "--name", baseContainerName, testutil.AlpineImage, "sleep", "infinity").Run()
defer base.Cmd("rm", "-f", baseContainerName).Run()

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

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

// output format : <inode number> /proc/1/ns/pid
// example output: 4026532581 /proc/1/ns/pid
basePSResult := base.Cmd("exec", baseContainerName, "ls", "-Li", "/proc/1/ns/pid").Run()
baseOutput := strings.TrimSpace(basePSResult.Stdout())
sharedPSResult := base.Cmd("exec", sharedContainerName, "ls", "-Li", "/proc/1/ns/pid").Run()
sharedOutput := strings.TrimSpace(sharedPSResult.Stdout())

assert.Equal(t, baseOutput, sharedOutput)
}
51 changes: 50 additions & 1 deletion cmd/nerdctl/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"github.com/containerd/nerdctl/pkg/netutil"
"github.com/containerd/nerdctl/pkg/platformutil"
"github.com/containerd/nerdctl/pkg/referenceutil"
"github.com/containerd/nerdctl/pkg/rootlessutil"
"github.com/containerd/nerdctl/pkg/strutil"
"github.com/containerd/nerdctl/pkg/taskutil"
dopts "github.com/docker/cli/opts"
Expand Down Expand Up @@ -431,7 +432,7 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
oci.WithDefaultSpec(),
)

opts, err = setPlatformOptions(opts, cmd, id)
opts, err = setPlatformOptions(ctx, opts, cmd, client, id)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -669,6 +670,11 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
spec := containerd.WithSpec(&s, opts...)
cOpts = append(cOpts, spec)

cOpts, err = setPlatformContainerOptions(ctx, cOpts, cmd, client, id)
if err != nil {
return nil, nil, err
}

container, err := client.NewContainer(ctx, id, cOpts...)
if err != nil {
gcContainer := func() {
Expand Down Expand Up @@ -1141,3 +1147,46 @@ func parseEnvVars(paths []string) ([]string, error) {
}
return vars, nil
}

func generateSharingPIDOpts(ctx context.Context, targetCon containerd.Container) ([]oci.SpecOpts, error) {
opts := make([]oci.SpecOpts, 0)

task, err := targetCon.Task(ctx, nil)
if err != nil {
return nil, err
}
status, err := task.Status(ctx)
if err != nil {
return nil, err
}

if status.Status != containerd.Running {
return nil, fmt.Errorf("shared container is not running")
}

spec, err := targetCon.Spec(ctx)
if err != nil {
return nil, err
}

isHost := true
for _, n := range spec.Linux.Namespaces {
if n.Type == specs.PIDNamespace {
isHost = false
}
}
if isHost {
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
if rootlessutil.IsRootless() {
opts = append(opts, withBindMountHostProcfs)
}
} else {
ns := specs.LinuxNamespace{
Type: specs.PIDNamespace,
Path: fmt.Sprintf("/proc/%d/ns/pid", task.Pid()),
}
opts = append(opts, oci.WithLinuxNamespace(ns))
}

return opts, nil
}
7 changes: 6 additions & 1 deletion cmd/nerdctl/run_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"context"

"github.com/containerd/containerd"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
"github.com/spf13/cobra"
Expand All @@ -38,6 +39,10 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
return nil, cobra.ShellCompDirectiveNoFileComp
}

func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]oci.SpecOpts, error) {
func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]oci.SpecOpts, error) {
return opts, nil
}

func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
return cOpts, nil
}
116 changes: 104 additions & 12 deletions cmd/nerdctl/run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import (
"fmt"
"strings"

"github.com/containerd/containerd"
"github.com/containerd/nerdctl/pkg/bypass4netnsutil"
"github.com/containerd/nerdctl/pkg/idutil/containerwalker"
"github.com/containerd/nerdctl/pkg/labels"
"github.com/containerd/nerdctl/pkg/rootlessutil"
"github.com/containerd/nerdctl/pkg/strutil"
"github.com/docker/go-units"
Expand Down Expand Up @@ -55,7 +58,7 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
}
}

func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]oci.SpecOpts, error) {
func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]oci.SpecOpts, error) {
opts = append(opts,
oci.WithDefaultUnixDevices,
WithoutRunMount(), // unmount default tmpfs on "/run": https://github.com/containerd/nerdctl/issues/157)
Expand Down Expand Up @@ -128,21 +131,15 @@ func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]o
opts = append(opts, oci.WithDevShmSize(shmBytes/1024))
}

pidNs, err := cmd.Flags().GetString("pid")
pid, err := cmd.Flags().GetString("pid")
if err != nil {
return nil, err
}
pidNs = strings.ToLower(pidNs)
if pidNs != "" {
if pidNs != "host" {
return nil, fmt.Errorf("invalid pid namespace. Set --pid=host to enable host pid namespace")
} else {
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
if rootlessutil.IsRootless() {
opts = append(opts, withBindMountHostProcfs)
}
}
pidOpts, err := generatePIDOpts(ctx, client, pid)
if err != nil {
return nil, err
}
opts = append(opts, pidOpts...)

ulimitOpts, err := generateUlimitsOpts(cmd)
if err != nil {
Expand Down Expand Up @@ -192,6 +189,21 @@ func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]o
return opts, nil
}

func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
pid, err := cmd.Flags().GetString("pid")
if err != nil {
return nil, err
}

pidCOpts, err := generatePIDCOpts(ctx, client, pid)
if err != nil {
return nil, err
}
cOpts = append(cOpts, pidCOpts...)

return cOpts, nil
}

func setOOMScoreAdj(opts []oci.SpecOpts, cmd *cobra.Command) ([]oci.SpecOpts, error) {
if !cmd.Flags().Changed("oom-score-adj") {
return opts, nil
Expand All @@ -217,3 +229,83 @@ func withOOMScoreAdj(score int) oci.SpecOpts {
return nil
}
}

func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, error) {
opts := make([]oci.SpecOpts, 0)
pid = strings.ToLower(pid)

switch pid {
case "":
// do nothing
case "host":
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
if rootlessutil.IsRootless() {
opts = append(opts, withBindMountHostProcfs)
}
default: // container:<id|name>
parsed := strings.Split(pid, ":")
if len(parsed) < 2 || parsed[0] != "container" {
return nil, fmt.Errorf("invalid pid namespace. Set --pid=[host|container:<name|id>")
}

containerName := parsed[1]
walker := &containerwalker.ContainerWalker{
Client: client,
OnFound: func(ctx context.Context, found containerwalker.Found) error {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
}

o, err := generateSharingPIDOpts(ctx, found.Container)
if err != nil {
return err
}
opts = append(opts, o...)

return nil
},
}
matchedCount, err := walker.Walk(ctx, containerName)
if err != nil {
return nil, err
}
if matchedCount < 1 {
return nil, fmt.Errorf("no such container: %s", containerName)
}
}

return opts, nil
}

func generatePIDCOpts(ctx context.Context, client *containerd.Client, pid string) ([]containerd.NewContainerOpts, error) {
pid = strings.ToLower(pid)

cOpts := make([]containerd.NewContainerOpts, 0)
parsed := strings.Split(pid, ":")
if len(parsed) < 2 || parsed[0] != "container" {
// no need to save pid options
return cOpts, nil
}

containerName := parsed[1]
walker := &containerwalker.ContainerWalker{
Client: client,
OnFound: func(ctx context.Context, found containerwalker.Found) error {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
}
cOpts = append(cOpts, containerd.WithAdditionalContainerLabels(map[string]string{
labels.PIDContainer: containerName,
}))
return nil
},
}
matchedCount, err := walker.Walk(ctx, containerName)
if err != nil {
return nil, err
}
if matchedCount < 1 {
return nil, fmt.Errorf("no such container: %s", containerName)
}
return cOpts, nil
}
12 changes: 12 additions & 0 deletions cmd/nerdctl/run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ func TestRunPidHost(t *testing.T) {
base.Cmd("run", "--rm", "--pid=host", testutil.AlpineImage, "ps", "auxw").AssertOutContains(strconv.Itoa(pid))
}

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

sharedContainerResult := base.Cmd("run", "-d", testutil.AlpineImage, "sleep", "infinity").Run()
baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())
defer base.Cmd("rm", "-f", baseContainerID).Run()

base.Cmd("run", "--rm", fmt.Sprintf("--pid=container:%s", baseContainerID),
testutil.AlpineImage, "ps", "ax").AssertOutContains("sleep infinity")
}

func TestRunIpcHost(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
Expand Down
7 changes: 6 additions & 1 deletion cmd/nerdctl/run_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"

"github.com/containerd/containerd"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
"github.com/docker/go-units"
Expand All @@ -40,7 +41,7 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
return nil, cobra.ShellCompDirectiveNoFileComp
}

func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]oci.SpecOpts, error) {
func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]oci.SpecOpts, error) {
cpus, err := cmd.Flags().GetFloat64("cpus")
if err != nil {
return nil, err
Expand All @@ -67,3 +68,7 @@ func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]o

return opts, nil
}

func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
return cOpts, nil
}
Loading

0 comments on commit 8680bdb

Please sign in to comment.