Skip to content

Commit

Permalink
Support --pid when container restart
Browse files Browse the repository at this point in the history
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 10, 2022
1 parent 82e3d04 commit 7a6d425
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 41 deletions.
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)

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

sharedContainerResult := base.Cmd("run", "-d", fmt.Sprintf("--pid=container:%s", baseContainerID), testutil.AlpineImage, "sleep", "infinity").Run()
sharedContainerID := strings.TrimSpace(sharedContainerResult.Stdout())
defer base.Cmd("rm", "-f", sharedContainerID).Run()

base.Cmd("restart", baseContainerID).AssertOK()
base.Cmd("restart", sharedContainerID).AssertOK()

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

assert.Equal(t, baseOutput, sharedOutput)
}
44 changes: 44 additions & 0 deletions 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 @@ -1146,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
}
96 changes: 55 additions & 41 deletions cmd/nerdctl/run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ package main

import (
"context"
"errors"
"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 @@ -190,6 +190,17 @@ func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Com
}

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
}

Expand Down Expand Up @@ -237,63 +248,66 @@ func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string)
return nil, fmt.Errorf("invalid pid namespace. Set --pid=[host|container:<name|id>")
}

containerName := parsed[1]
var targetCon containerd.Container
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)
}

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

if status.Status != containerd.Running {
return errors.New("shared container is not running")
}

spec, err := found.Container.Spec(ctx)
if err != nil {
return 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))
}

targetCon = found.Container
return nil
},
}

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

o, err := generateSharingPIDOpts(ctx, targetCon)
if err != nil {
return nil, err
}

opts = append(opts, o...)
}

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
}
35 changes: 35 additions & 0 deletions cmd/nerdctl/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ func startContainer(ctx context.Context, container containerd.Container, flagA b
return err
}

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

taskCIO := cio.NullIO

// Choosing the user selected option over the labels
Expand Down Expand Up @@ -221,6 +225,37 @@ func reconfigNetContainer(ctx context.Context, c containerd.Container, client *c
return nil
}

func reconfigPIDContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error {
targetContainerID, ok := lab[labels.PIDContainer]
if !ok {
return nil
}

targetCon, err := client.LoadContainer(ctx, targetContainerID)
if err != nil {
return err
}

opts, err := generateSharingPIDOpts(ctx, targetCon)
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
}

func startShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// show non-running container names
statusFilterFn := func(st containerd.ProcessStatus) bool {
Expand Down
3 changes: 3 additions & 0 deletions pkg/labels/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ const (
StopTimout = Prefix + "stop-timeout"

MACAddress = Prefix + "mac-address"

// PIDContainer is the `nerdctl run --pid` for restarting
PIDContainer = Prefix + "pid-container"
)

var ShellCompletions = []string{
Expand Down

0 comments on commit 7a6d425

Please sign in to comment.