runsc: Support runsc kill multi-container.

Now, we can kill individual containers rather than the entire sandbox.

PiperOrigin-RevId: 211748106
Change-Id: Ic97e91db33d53782f838338c4a6d0aab7a313ead
This commit is contained in:
Kevin Krakauer 2018-09-05 21:13:46 -07:00 committed by Shentubot
parent 156b49ca85
commit 8f0b6e7fc0
5 changed files with 156 additions and 17 deletions

View File

@ -22,7 +22,6 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/control/server"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
"gvisor.googlesource.com/gvisor/pkg/sentry/control"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
@ -387,13 +386,5 @@ type SignalArgs struct {
// Signal sends a signal to the init process of the container.
func (cm *containerManager) Signal(args *SignalArgs, _ *struct{}) error {
log.Debugf("containerManager.Signal")
// TODO: Use the cid and send the signal to the init
// process in theat container. Currently we just signal PID 1 in the
// sandbox.
si := arch.SignalInfo{Signo: args.Signo}
t := cm.l.k.TaskSet().Root.TaskWithID(1)
if t == nil {
return fmt.Errorf("cannot signal: no task with id 1")
}
return t.SendSignal(&si)
return cm.l.signal(args.CID, args.Signo)
}

View File

@ -31,6 +31,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/abi/linux"
"gvisor.googlesource.com/gvisor/pkg/cpuid"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/arch"
"gvisor.googlesource.com/gvisor/pkg/sentry/inet"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
@ -576,3 +577,19 @@ func newEmptyNetworkStack(conf *Config, clock tcpip.Clock) (inet.Stack, error) {
panic(fmt.Sprintf("invalid network configuration: %v", conf.Network))
}
}
func (l *Loader) signal(cid string, signo int32) error {
l.mu.Lock()
tgid, ok := l.containerRootTGIDs[cid]
l.mu.Unlock()
if !ok {
return fmt.Errorf("failed to signal container %q: no such container", cid)
}
// The thread group ID of a process is the leading task's thread ID.
t := l.k.TaskSet().Root.TaskWithID(tgid)
if t == nil {
return fmt.Errorf("cannot signal: no task with ID %d", tgid)
}
return t.SendSignal(&arch.SignalInfo{Signo: signo})
}

View File

@ -81,6 +81,8 @@ func (*Kill) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su
if err != nil {
Fatalf("%v", err)
}
// TODO: Distinguish between already-exited containers and
// genuine errors.
if err := c.Signal(sig); err != nil {
Fatalf("%v", err)
}

View File

@ -427,6 +427,7 @@ func (c *Container) Signal(sig syscall.Signal) error {
log.Warningf("container %q not running, not sending signal %v", c.ID, sig)
return nil
}
// TODO: Query the container for its state, then save it.
return c.Sandbox.Signal(c.ID, sig)
}

View File

@ -38,6 +38,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/unet"
"gvisor.googlesource.com/gvisor/runsc/boot"
"gvisor.googlesource.com/gvisor/runsc/specutils"
"gvisor.googlesource.com/gvisor/runsc/test/testutil"
)
@ -336,8 +337,8 @@ func TestLifecycle(t *testing.T) {
wg.Done()
}()
// Wait a bit to ensure that we've started waiting on the container
// before we signal.
// Wait a bit to ensure that we've started waiting on the
// container before we signal.
<-ch
time.Sleep(100 * time.Millisecond)
// Send the container a SIGTERM which will cause it to stop.
@ -347,11 +348,11 @@ func TestLifecycle(t *testing.T) {
// Wait for it to die.
wg.Wait()
// The sandbox process should have exited by now, but it is a zombie.
// In normal runsc usage, it will be parented to init, and init will
// reap the sandbox. However, in this case the test runner is the
// parent and will not reap the sandbox process, so we must do it
// ourselves.
// The sandbox process should have exited by now, but it is a
// zombie. In normal runsc usage, it will be parented to init,
// and init will reap the sandbox. However, in this case the
// test runner is the parent and will not reap the sandbox
// process, so we must do it ourselves.
p, _ := os.FindProcess(s.Sandbox.Pid)
p.Wait()
g, _ := os.FindProcess(s.GoferPid)
@ -1547,6 +1548,133 @@ func TestGoferExits(t *testing.T) {
}
}
// TestMultiContainerSignal checks that it is possible to signal individual
// containers without killing the entire sandbox.
func TestMultiContainerSignal(t *testing.T) {
for _, conf := range configs(all...) {
t.Logf("Running test with conf: %+v", conf)
containerIDs := []string{
testutil.UniqueContainerID(),
testutil.UniqueContainerID(),
}
containerAnnotations := []map[string]string{
// The first container creates a sandbox.
map[string]string{
specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox,
},
// The second container creates a container within the first
// container's sandbox.
map[string]string{
specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer,
specutils.ContainerdSandboxIDAnnotation: containerIDs[0],
},
}
rootDir, err := testutil.SetupRootDir()
if err != nil {
t.Fatalf("error creating root dir: %v", err)
}
defer os.RemoveAll(rootDir)
// Setup the containers.
containers := make([]*Container, 0, len(containerIDs))
for i, annotations := range containerAnnotations {
spec := testutil.NewSpecWithArgs("sleep", "100")
spec.Annotations = annotations
bundleDir, err := testutil.SetupContainerInRoot(rootDir, spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer os.RemoveAll(bundleDir)
cont, err := Create(containerIDs[i], spec, conf, bundleDir, "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer cont.Destroy()
if err := cont.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
containers = append(containers, cont)
}
expectedPL := []*control.Process{
{
UID: 0,
PID: 1,
PPID: 0,
C: 0,
Cmd: "sleep",
},
{
UID: 0,
PID: 2,
PPID: 0,
C: 0,
Cmd: "sleep",
},
}
// Check via ps that multiple processes are running.
if err := waitForProcessList(containers[0], expectedPL); err != nil {
t.Errorf("failed to wait for sleep to start: %v", err)
}
// Kill process 2.
if err := containers[1].Signal(syscall.SIGKILL); err != nil {
t.Errorf("failed to kill process 2: %v", err)
}
// Make sure process 1 is still running.
if err := waitForProcessList(containers[0], expectedPL[:1]); err != nil {
t.Errorf("failed to wait for sleep to start: %v", err)
}
// Now that process 2 is gone, ensure we get an error trying to
// signal it again.
if err := containers[1].Signal(syscall.SIGKILL); err == nil {
t.Errorf("container %q shouldn't exist, but we were able to signal it", containers[1].ID)
}
// Kill process 1.
if err := containers[0].Signal(syscall.SIGKILL); err != nil {
t.Errorf("failed to kill process 1: %v", err)
}
if err := waitForSandboxExit(containers[0]); err != nil {
t.Errorf("failed to exit sandbox: %v", err)
}
// The sentry should be gone, so signaling should yield an
// error.
if err := containers[0].Signal(syscall.SIGKILL); err == nil {
t.Errorf("sandbox %q shouldn't exist, but we were able to signal it", containers[0].Sandbox.ID)
}
}
}
// waitForSandboxExit waits until both the sandbox and gofer processes of the
// container have exited.
func waitForSandboxExit(container *Container) error {
goferProc, _ := os.FindProcess(container.GoferPid)
state, err := goferProc.Wait()
if err != nil {
return err
}
if !state.Exited() {
return fmt.Errorf("gofer with PID %d failed to exit", container.GoferPid)
}
sandboxProc, _ := os.FindProcess(container.Sandbox.Pid)
state, err = sandboxProc.Wait()
if err != nil {
return err
}
if !state.Exited() {
return fmt.Errorf("sandbox with PID %d failed to exit", container.Sandbox.Pid)
}
return nil
}
func TestMain(m *testing.M) {
testutil.RunAsRoot(m)
}