Allow sandbox PID namespace to be used

Identify the sandbox PID namespace path and translate it to the root
PID namespace. This is one step towards supporting K8s ephemeral
container target. Currently, the target always comes as the sandbox
because containerd uses the process PID (in gVisor's case the sandbox
PID) to identify the container. Instead of landing in the desired
container, the root namespace is targeted to allow all processes to
be visible and debugged.

PiperOrigin-RevId: 531272178
This commit is contained in:
Fabricio Voznika
2023-05-11 12:17:50 -07:00
committed by gVisor bot
parent 6b52411e67
commit 570973f0be
4 changed files with 120 additions and 4 deletions
+2
View File
@@ -801,12 +801,14 @@ func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid st
if ns.Path != "" {
for _, p := range l.processes {
if ns.Path == p.pidnsPath {
log.Debugf("Joining PID namespace named %q", ns.Path)
pidns = p.tg.PIDNamespace()
break
}
}
}
if pidns == nil {
log.Warningf("PID namespace %q not found, running in new PID namespace", ns.Path)
pidns = l.k.RootPIDNamespace().NewChild(l.k.RootUserNamespace())
}
ep.pidnsPath = ns.Path
+1 -1
View File
@@ -45,7 +45,7 @@ type Create struct {
// userLog is the path to send user-visible logs to. This log is different
// from debug logs. The former is meant to be consumed by the users and should
// contain only information that is relevant to the person running the
// container, e.g. unsuported syscalls, while the later is more verbose and
// container, e.g. unsupported syscalls, while the later is more verbose and
// consumed by developers.
userLog string
}
+90 -3
View File
@@ -176,7 +176,7 @@ func TestMultiContainerSanity(t *testing.T) {
// TestMultiPIDNS checks that it is possible to run 2 dead-simple containers in
// the same sandbox with different pidns.
func TestMultiPIDNS(t *testing.T) {
for name, conf := range configs(t, false /* noOverlay */) {
for name, conf := range configs(t, true /* noOverlay */) {
t.Run(name, func(t *testing.T) {
rootDir, cleanup, err := testutil.SetupRootDir()
if err != nil {
@@ -248,7 +248,7 @@ func TestMultiPIDNS(t *testing.T) {
// TestMultiPIDNSPath checks the pidns path.
func TestMultiPIDNSPath(t *testing.T) {
for name, conf := range configs(t, false /* noOverlay */) {
for name, conf := range configs(t, true /* noOverlay */) {
t.Run(name, func(t *testing.T) {
rootDir, cleanup, err := testutil.SetupRootDir()
if err != nil {
@@ -366,7 +366,7 @@ func TestMultiPIDNSKill(t *testing.T) {
t.Fatal("error finding test_app:", err)
}
for name, conf := range configs(t, false /* noOverlay */) {
for name, conf := range configs(t, true /* noOverlay */) {
t.Run(name, func(t *testing.T) {
rootDir, cleanup, err := testutil.SetupRootDir()
if err != nil {
@@ -451,6 +451,93 @@ func TestMultiPIDNSKill(t *testing.T) {
}
}
// TestMultiPIDNSRoot checks that the sandbox PID namespace can be used to
// reference the root PID namespace of the sandbox.
func TestMultiPIDNSRoot(t *testing.T) {
for name, conf := range configs(t, true /* noOverlay */) {
t.Run(name, func(t *testing.T) {
rootDir, cleanup, err := testutil.SetupRootDir()
if err != nil {
t.Fatalf("error creating root dir: %v", err)
}
defer cleanup()
conf.RootDir = rootDir
// Setup the containers. One in the root PID namespace and another in a
// sub-namespace.
sleep := []string{"sleep", "100"}
testSpecs, ids := createSpecs(sleep, sleep, sleep)
testSpecs[1].Linux = &specs.Linux{
Namespaces: []specs.LinuxNamespace{
{
Type: "pid",
Path: "/proc/1/ns/pid",
},
},
}
// Start 2 containers first, and use the 3rd to join the sandbox pidns.
delayedSpec, delayedID := testSpecs[2], ids[2]
testSpecs = testSpecs[:2]
ids = ids[:2]
containers, cleanup, err := startContainers(conf, testSpecs, ids)
if err != nil {
t.Fatalf("error starting containers: %v", err)
}
defer cleanup()
delayedSpec.Linux = &specs.Linux{
Namespaces: []specs.LinuxNamespace{
{
Type: "pid",
Path: fmt.Sprintf("/proc/%d/ns/pid", containers[0].SandboxPid()),
},
},
}
delayed, cleanup, err := startContainers(conf, []*specs.Spec{delayedSpec}, []string{delayedID})
if err != nil {
t.Fatalf("error starting sub-container: %v", err)
}
defer cleanup()
// Wait for all container processes to be up and running.
expectedPL := []*control.Process{
newProcessBuilder().PID(1).PPID(0).Cmd("sleep").Process(),
}
if err := waitForProcessList(containers[0], expectedPL); err != nil {
t.Errorf("failed to wait for sleep to start: %v", err)
}
expectedPL = []*control.Process{
newProcessBuilder().PID(2).PPID(0).Cmd("sleep").Process(),
}
if err := waitForProcessList(containers[1], expectedPL); err != nil {
t.Fatalf("failed to wait for sleep to start: %v", err)
}
expectedPL = []*control.Process{
newProcessBuilder().PID(3).PPID(0).Cmd("sleep").Process(),
}
if err := waitForProcessList(delayed[0], expectedPL); err != nil {
t.Fatalf("failed to wait for sleep to start: %v", err)
}
// Check that delayer container is running in the root PID namespace and
// can see all other processes.
expectedPL = []*control.Process{
newProcessBuilder().PID(1).Cmd("sleep").Process(),
newProcessBuilder().PID(2).Cmd("sleep").Process(),
newProcessBuilder().PID(3).Cmd("sleep").Process(),
newProcessBuilder().Cmd("ps").Process(),
}
if got, err := execPS(conf, delayed[0]); err != nil {
t.Fatal(err)
} else if !procListsEqual(got, expectedPL) {
t.Fatalf("container got process list: %s, want: %s", procListToString(got), procListToString(expectedPL))
}
})
}
}
func TestMultiContainerWait(t *testing.T) {
rootDir, cleanup, err := testutil.SetupRootDir()
if err != nil {
+27
View File
@@ -403,6 +403,7 @@ func (s *Sandbox) StartSubcontainer(spec *specs.Spec, conf *config.Config, cid s
if err := s.configureStdios(conf, stdios); err != nil {
return err
}
s.fixPidns(spec)
// The payload contains (in this specific order):
// * stdin/stdout/stderr (optional: only present when not using TTY)
@@ -1518,3 +1519,29 @@ func (s *Sandbox) CgroupsWriteControlFile(file control.CgroupControlFile, value
}
return out.Results[0].AsError()
}
// fixPidns looks at the PID namespace path. If that path corresponds to the
// sandbox process PID namespace, then change the spec so that the container
// joins the sandbox root namespace.
func (s *Sandbox) fixPidns(spec *specs.Spec) {
pidns, ok := specutils.GetNS(specs.PIDNamespace, spec)
if !ok {
// pidns was not set, nothing to fix.
return
}
if pidns.Path != fmt.Sprintf("/proc/%d/ns/pid", s.Pid.load()) {
// Fix only if the PID namespace corresponds to the sandbox's.
return
}
for i := range spec.Linux.Namespaces {
if spec.Linux.Namespaces[i].Type == specs.PIDNamespace {
// Removing the namespace makes the container join the sandbox root
// namespace.
log.Infof("Fixing PID namespace in spec from %q to make the container join the sandbox root namespace", pidns.Path)
spec.Linux.Namespaces = append(spec.Linux.Namespaces[:i], spec.Linux.Namespaces[i+1:]...)
return
}
}
panic("unreachable")
}