Skip to content

Commit 8680bdb

Browse files
committed
Support --pid=container:xxx for nerdctl run cmd
- 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 <[email protected]>
1 parent b4c0109 commit 8680bdb

File tree

9 files changed

+250
-16
lines changed

9 files changed

+250
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ Basic flags:
372372
- :whale: `--rm`: Automatically remove the container when it exits
373373
- :whale: `--pull=(always|missing|never)`: Pull image before running
374374
- Default: "missing"
375-
- :whale: `--pid=(host)`: PID namespace to use
375+
- :whale: `--pid=(host|container:<container>)`: PID namespace to use
376376
- :whale: `--stop-signal`: Signal to stop a container (default "SIGTERM")
377377
- :whale: `--stop-timeout`: Timeout (in seconds) to stop a container
378378

cmd/nerdctl/restart_linux_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package main
1818

1919
import (
20+
"fmt"
21+
"strings"
2022
"testing"
2123

2224
"github.com/containerd/nerdctl/pkg/testutil"
@@ -42,3 +44,28 @@ func TestRestart(t *testing.T) {
4244
newPid := newInspect.State.Pid
4345
assert.Assert(t, pid != newPid)
4446
}
47+
48+
func TestRestartPIDContainer(t *testing.T) {
49+
t.Parallel()
50+
base := testutil.NewBase(t)
51+
52+
baseContainerName := testutil.Identifier(t)
53+
base.Cmd("run", "-d", "--name", baseContainerName, testutil.AlpineImage, "sleep", "infinity").Run()
54+
defer base.Cmd("rm", "-f", baseContainerName).Run()
55+
56+
sharedContainerName := testutil.Identifier(t)
57+
base.Cmd("run", "-d", "--name", sharedContainerName, fmt.Sprintf("--pid=container:%s", baseContainerName), testutil.AlpineImage, "sleep", "infinity").Run()
58+
defer base.Cmd("rm", "-f", sharedContainerName).Run()
59+
60+
base.Cmd("restart", baseContainerName).AssertOK()
61+
base.Cmd("restart", sharedContainerName).AssertOK()
62+
63+
// output format : <inode number> /proc/1/ns/pid
64+
// example output: 4026532581 /proc/1/ns/pid
65+
basePSResult := base.Cmd("exec", baseContainerName, "ls", "-Li", "/proc/1/ns/pid").Run()
66+
baseOutput := strings.TrimSpace(basePSResult.Stdout())
67+
sharedPSResult := base.Cmd("exec", sharedContainerName, "ls", "-Li", "/proc/1/ns/pid").Run()
68+
sharedOutput := strings.TrimSpace(sharedPSResult.Stdout())
69+
70+
assert.Equal(t, baseOutput, sharedOutput)
71+
}

cmd/nerdctl/run.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import (
5050
"github.com/containerd/nerdctl/pkg/netutil"
5151
"github.com/containerd/nerdctl/pkg/platformutil"
5252
"github.com/containerd/nerdctl/pkg/referenceutil"
53+
"github.com/containerd/nerdctl/pkg/rootlessutil"
5354
"github.com/containerd/nerdctl/pkg/strutil"
5455
"github.com/containerd/nerdctl/pkg/taskutil"
5556
dopts "github.com/docker/cli/opts"
@@ -431,7 +432,7 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
431432
oci.WithDefaultSpec(),
432433
)
433434

434-
opts, err = setPlatformOptions(opts, cmd, id)
435+
opts, err = setPlatformOptions(ctx, opts, cmd, client, id)
435436
if err != nil {
436437
return nil, nil, err
437438
}
@@ -669,6 +670,11 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
669670
spec := containerd.WithSpec(&s, opts...)
670671
cOpts = append(cOpts, spec)
671672

673+
cOpts, err = setPlatformContainerOptions(ctx, cOpts, cmd, client, id)
674+
if err != nil {
675+
return nil, nil, err
676+
}
677+
672678
container, err := client.NewContainer(ctx, id, cOpts...)
673679
if err != nil {
674680
gcContainer := func() {
@@ -1141,3 +1147,46 @@ func parseEnvVars(paths []string) ([]string, error) {
11411147
}
11421148
return vars, nil
11431149
}
1150+
1151+
func generateSharingPIDOpts(ctx context.Context, targetCon containerd.Container) ([]oci.SpecOpts, error) {
1152+
opts := make([]oci.SpecOpts, 0)
1153+
1154+
task, err := targetCon.Task(ctx, nil)
1155+
if err != nil {
1156+
return nil, err
1157+
}
1158+
status, err := task.Status(ctx)
1159+
if err != nil {
1160+
return nil, err
1161+
}
1162+
1163+
if status.Status != containerd.Running {
1164+
return nil, fmt.Errorf("shared container is not running")
1165+
}
1166+
1167+
spec, err := targetCon.Spec(ctx)
1168+
if err != nil {
1169+
return nil, err
1170+
}
1171+
1172+
isHost := true
1173+
for _, n := range spec.Linux.Namespaces {
1174+
if n.Type == specs.PIDNamespace {
1175+
isHost = false
1176+
}
1177+
}
1178+
if isHost {
1179+
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
1180+
if rootlessutil.IsRootless() {
1181+
opts = append(opts, withBindMountHostProcfs)
1182+
}
1183+
} else {
1184+
ns := specs.LinuxNamespace{
1185+
Type: specs.PIDNamespace,
1186+
Path: fmt.Sprintf("/proc/%d/ns/pid", task.Pid()),
1187+
}
1188+
opts = append(opts, oci.WithLinuxNamespace(ns))
1189+
}
1190+
1191+
return opts, nil
1192+
}

cmd/nerdctl/run_freebsd.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"context"
2121

22+
"github.com/containerd/containerd"
2223
"github.com/containerd/containerd/containers"
2324
"github.com/containerd/containerd/oci"
2425
"github.com/spf13/cobra"
@@ -38,6 +39,10 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
3839
return nil, cobra.ShellCompDirectiveNoFileComp
3940
}
4041

41-
func setPlatformOptions(opts []oci.SpecOpts, cmd *cobra.Command, id string) ([]oci.SpecOpts, error) {
42+
func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]oci.SpecOpts, error) {
4243
return opts, nil
4344
}
45+
46+
func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
47+
return cOpts, nil
48+
}

cmd/nerdctl/run_linux.go

Lines changed: 104 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import (
2121
"fmt"
2222
"strings"
2323

24+
"github.com/containerd/containerd"
2425
"github.com/containerd/nerdctl/pkg/bypass4netnsutil"
26+
"github.com/containerd/nerdctl/pkg/idutil/containerwalker"
27+
"github.com/containerd/nerdctl/pkg/labels"
2528
"github.com/containerd/nerdctl/pkg/rootlessutil"
2629
"github.com/containerd/nerdctl/pkg/strutil"
2730
"github.com/docker/go-units"
@@ -55,7 +58,7 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
5558
}
5659
}
5760

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

131-
pidNs, err := cmd.Flags().GetString("pid")
134+
pid, err := cmd.Flags().GetString("pid")
132135
if err != nil {
133136
return nil, err
134137
}
135-
pidNs = strings.ToLower(pidNs)
136-
if pidNs != "" {
137-
if pidNs != "host" {
138-
return nil, fmt.Errorf("invalid pid namespace. Set --pid=host to enable host pid namespace")
139-
} else {
140-
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
141-
if rootlessutil.IsRootless() {
142-
opts = append(opts, withBindMountHostProcfs)
143-
}
144-
}
138+
pidOpts, err := generatePIDOpts(ctx, client, pid)
139+
if err != nil {
140+
return nil, err
145141
}
142+
opts = append(opts, pidOpts...)
146143

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

192+
func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
193+
pid, err := cmd.Flags().GetString("pid")
194+
if err != nil {
195+
return nil, err
196+
}
197+
198+
pidCOpts, err := generatePIDCOpts(ctx, client, pid)
199+
if err != nil {
200+
return nil, err
201+
}
202+
cOpts = append(cOpts, pidCOpts...)
203+
204+
return cOpts, nil
205+
}
206+
195207
func setOOMScoreAdj(opts []oci.SpecOpts, cmd *cobra.Command) ([]oci.SpecOpts, error) {
196208
if !cmd.Flags().Changed("oom-score-adj") {
197209
return opts, nil
@@ -217,3 +229,83 @@ func withOOMScoreAdj(score int) oci.SpecOpts {
217229
return nil
218230
}
219231
}
232+
233+
func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, error) {
234+
opts := make([]oci.SpecOpts, 0)
235+
pid = strings.ToLower(pid)
236+
237+
switch pid {
238+
case "":
239+
// do nothing
240+
case "host":
241+
opts = append(opts, oci.WithHostNamespace(specs.PIDNamespace))
242+
if rootlessutil.IsRootless() {
243+
opts = append(opts, withBindMountHostProcfs)
244+
}
245+
default: // container:<id|name>
246+
parsed := strings.Split(pid, ":")
247+
if len(parsed) < 2 || parsed[0] != "container" {
248+
return nil, fmt.Errorf("invalid pid namespace. Set --pid=[host|container:<name|id>")
249+
}
250+
251+
containerName := parsed[1]
252+
walker := &containerwalker.ContainerWalker{
253+
Client: client,
254+
OnFound: func(ctx context.Context, found containerwalker.Found) error {
255+
if found.MatchCount > 1 {
256+
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
257+
}
258+
259+
o, err := generateSharingPIDOpts(ctx, found.Container)
260+
if err != nil {
261+
return err
262+
}
263+
opts = append(opts, o...)
264+
265+
return nil
266+
},
267+
}
268+
matchedCount, err := walker.Walk(ctx, containerName)
269+
if err != nil {
270+
return nil, err
271+
}
272+
if matchedCount < 1 {
273+
return nil, fmt.Errorf("no such container: %s", containerName)
274+
}
275+
}
276+
277+
return opts, nil
278+
}
279+
280+
func generatePIDCOpts(ctx context.Context, client *containerd.Client, pid string) ([]containerd.NewContainerOpts, error) {
281+
pid = strings.ToLower(pid)
282+
283+
cOpts := make([]containerd.NewContainerOpts, 0)
284+
parsed := strings.Split(pid, ":")
285+
if len(parsed) < 2 || parsed[0] != "container" {
286+
// no need to save pid options
287+
return cOpts, nil
288+
}
289+
290+
containerName := parsed[1]
291+
walker := &containerwalker.ContainerWalker{
292+
Client: client,
293+
OnFound: func(ctx context.Context, found containerwalker.Found) error {
294+
if found.MatchCount > 1 {
295+
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
296+
}
297+
cOpts = append(cOpts, containerd.WithAdditionalContainerLabels(map[string]string{
298+
labels.PIDContainer: containerName,
299+
}))
300+
return nil
301+
},
302+
}
303+
matchedCount, err := walker.Walk(ctx, containerName)
304+
if err != nil {
305+
return nil, err
306+
}
307+
if matchedCount < 1 {
308+
return nil, fmt.Errorf("no such container: %s", containerName)
309+
}
310+
return cOpts, nil
311+
}

cmd/nerdctl/run_linux_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ func TestRunPidHost(t *testing.T) {
7575
base.Cmd("run", "--rm", "--pid=host", testutil.AlpineImage, "ps", "auxw").AssertOutContains(strconv.Itoa(pid))
7676
}
7777

78+
func TestRunPidContainer(t *testing.T) {
79+
t.Parallel()
80+
base := testutil.NewBase(t)
81+
82+
sharedContainerResult := base.Cmd("run", "-d", testutil.AlpineImage, "sleep", "infinity").Run()
83+
baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout())
84+
defer base.Cmd("rm", "-f", baseContainerID).Run()
85+
86+
base.Cmd("run", "--rm", fmt.Sprintf("--pid=container:%s", baseContainerID),
87+
testutil.AlpineImage, "ps", "ax").AssertOutContains("sleep infinity")
88+
}
89+
7890
func TestRunIpcHost(t *testing.T) {
7991
t.Parallel()
8092
base := testutil.NewBase(t)

cmd/nerdctl/run_windows.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222

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

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

6869
return opts, nil
6970
}
71+
72+
func setPlatformContainerOptions(ctx context.Context, cOpts []containerd.NewContainerOpts, cmd *cobra.Command, client *containerd.Client, id string) ([]containerd.NewContainerOpts, error) {
73+
return cOpts, nil
74+
}

0 commit comments

Comments
 (0)