summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--runsc/boot/controller.go45
-rw-r--r--runsc/boot/loader.go193
-rw-r--r--runsc/cmd/events.go2
-rw-r--r--runsc/cmd/exec.go2
-rw-r--r--runsc/cmd/ps.go2
-rw-r--r--runsc/container/BUILD1
-rw-r--r--runsc/container/container.go66
-rw-r--r--runsc/container/container_test.go73
-rw-r--r--runsc/sandbox/sandbox.go42
-rw-r--r--runsc/sandbox/sandbox_test.go2
-rw-r--r--runsc/specutils/specutils.go55
11 files changed, 386 insertions, 97 deletions
diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go
index ae727f144..1a598199d 100644
--- a/runsc/boot/controller.go
+++ b/runsc/boot/controller.go
@@ -15,9 +15,12 @@
package boot
import (
+ "errors"
"fmt"
+ 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/kernel"
@@ -50,6 +53,10 @@ const (
// ContainerSignal is used to send a signal to a container.
ContainerSignal = "containerManager.Signal"
+ // ContainerStart is the URPC endpoint for running a non-root container
+ // within a sandbox.
+ ContainerStart = "containerManager.Start"
+
// ContainerWait is used to wait on the init process of the container
// and return its ExitStatus.
ContainerWait = "containerManager.Wait"
@@ -127,10 +134,14 @@ type containerManager struct {
// watchdog is the kernel watchdog.
watchdog *watchdog.Watchdog
+
+ // l is the loader that creates containers and sandboxes.
+ l *Loader
}
// StartRoot will start the root container process.
func (cm *containerManager) StartRoot(_, _ *struct{}) error {
+ log.Debugf("containerManager.StartRoot")
// Tell the root container to start and wait for the result.
cm.startChan <- struct{}{}
return <-cm.startResultChan
@@ -138,11 +149,42 @@ func (cm *containerManager) StartRoot(_, _ *struct{}) error {
// Processes retrieves information about processes running in the sandbox.
func (cm *containerManager) Processes(_, out *[]*control.Process) error {
+ log.Debugf("containerManager.Processes")
return control.Processes(cm.k, out)
}
+// StartArgs contains arguments to the Start method.
+type StartArgs struct {
+ // Spec is the spec of the container to start.
+ Spec *specs.Spec
+
+ // TODO: Separate sandbox and container configs.
+ // Config is the runsc-specific configuration for the sandbox.
+ Conf *Config
+}
+
+// Start runs a created container within a sandbox.
+func (cm *containerManager) Start(args *StartArgs, _ *struct{}) error {
+ log.Debugf("containerManager.Start")
+
+ // Validate arguments.
+ if args == nil {
+ return errors.New("start missing arguments")
+ }
+ if args.Spec == nil {
+ return errors.New("start arguments missing spec")
+ }
+ if args.Conf == nil {
+ return errors.New("start arguments missing config")
+ }
+
+ cm.l.startContainer(args, cm.k)
+ return nil
+}
+
// Execute runs a command on a created or running sandbox.
func (cm *containerManager) Execute(e *control.ExecArgs, waitStatus *uint32) error {
+ log.Debugf("containerManager.Execute")
proc := control.Proc{Kernel: cm.k}
if err := proc.Exec(e, waitStatus); err != nil {
return fmt.Errorf("error executing: %+v: %v", e, err)
@@ -152,6 +194,7 @@ func (cm *containerManager) Execute(e *control.ExecArgs, waitStatus *uint32) err
// Checkpoint pauses a sandbox and saves its state.
func (cm *containerManager) Checkpoint(o *control.SaveOpts, _ *struct{}) error {
+ log.Debugf("containerManager.Checkpoint")
state := control.State{
Kernel: cm.k,
Watchdog: cm.watchdog,
@@ -173,6 +216,7 @@ func (cm *containerManager) Resume(_, _ *struct{}) error {
// Wait waits for the init process in the given container.
func (cm *containerManager) Wait(cid *string, waitStatus *uint32) error {
+ log.Debugf("containerManager.Wait")
// TODO: Use the cid and wait on the init process in that
// container. Currently we just wait on PID 1 in the sandbox.
tg := cm.k.TaskSet().Root.ThreadGroupWithID(1)
@@ -195,6 +239,7 @@ 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.
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index 526e8f8bb..d1a413cc7 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package boot loads the kernel and runs a container..
+// Package boot loads the kernel and runs a container.
package boot
import (
@@ -79,8 +79,8 @@ type Loader struct {
// container. It should be called when a sandbox is destroyed.
stopSignalForwarding func()
- // procArgs refers to the root container task.
- procArgs kernel.CreateProcessArgs
+ // rootProcArgs refers to the root sandbox init task.
+ rootProcArgs kernel.CreateProcessArgs
}
func init() {
@@ -117,12 +117,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD, restoreFD int, ioFDs []in
}
tk.SetClocks(time.NewCalibratedClocks())
- // Create initial limits.
- ls, err := createLimitSet(spec)
- if err != nil {
- return nil, fmt.Errorf("error creating limits: %v", err)
- }
-
// Create capabilities.
caps, err := specutils.Capabilities(spec.Process.Capabilities)
if err != nil {
@@ -154,13 +148,6 @@ func New(spec *specs.Spec, conf *Config, controllerFD, restoreFD int, ioFDs []in
return nil, fmt.Errorf("failed to enable strace: %v", err)
}
- // Get the executable path, which is a bit tricky because we have to
- // inspect the environment PATH which is relative to the root path.
- exec, err := specutils.GetExecutablePath(spec.Process.Args[0], spec.Root.Path, spec.Process.Env)
- if err != nil {
- return nil, fmt.Errorf("error getting executable path: %v", err)
- }
-
// Create an empty network stack because the network namespace may be empty at
// this point. Netns is configured before Run() is called. Netstack is
// configured using a control uRPC message. Host network is configured inside
@@ -223,16 +210,56 @@ func New(spec *specs.Spec, conf *Config, controllerFD, restoreFD int, ioFDs []in
return nil, fmt.Errorf("error creating control server: %v", err)
}
+ // We don't care about child signals; some platforms can generate a
+ // tremendous number of useless ones (I'm looking at you, ptrace).
+ if err := sighandling.IgnoreChildStop(); err != nil {
+ return nil, fmt.Errorf("failed to ignore child stop signals: %v", err)
+ }
+ // Ensure that most signals received in sentry context are forwarded to
+ // the emulated kernel.
+ stopSignalForwarding := sighandling.StartForwarding(k)
+
+ procArgs, err := newProcess(spec, conf, ioFDs, console, creds, utsns, ipcns, k)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create root process: %v", err)
+ }
+
+ l := &Loader{
+ k: k,
+ ctrl: ctrl,
+ conf: conf,
+ console: console,
+ watchdog: watchdog,
+ stopSignalForwarding: stopSignalForwarding,
+ rootProcArgs: procArgs,
+ }
+ ctrl.manager.l = l
+ return l, nil
+}
+
+// newProcess creates a process that can be run with kernel.CreateProcess.
+func newProcess(spec *specs.Spec, conf *Config, ioFDs []int, console bool, creds *auth.Credentials, utsns *kernel.UTSNamespace, ipcns *kernel.IPCNamespace, k *kernel.Kernel) (kernel.CreateProcessArgs, error) {
+ // Create initial limits.
+ ls, err := createLimitSet(spec)
+ if err != nil {
+ return kernel.CreateProcessArgs{}, fmt.Errorf("error creating limits: %v", err)
+ }
+
+ // Get the executable path, which is a bit tricky because we have to
+ // inspect the environment PATH which is relative to the root path.
+ exec, err := specutils.GetExecutablePath(spec.Process.Args[0], spec.Root.Path, spec.Process.Env)
+ if err != nil {
+ return kernel.CreateProcessArgs{}, fmt.Errorf("error getting executable path: %v", err)
+ }
+
// Create the process arguments.
procArgs := kernel.CreateProcessArgs{
- Filename: exec,
- Argv: spec.Process.Args,
- Envv: spec.Process.Env,
- WorkingDirectory: spec.Process.Cwd,
- Credentials: creds,
- // Creating the FDMap requires that we have kernel.Kernel.fdMapUids, so
- // it must wait until we have a Kernel.
- Umask: uint(syscall.Umask(0)),
+ Filename: exec,
+ Argv: spec.Process.Args,
+ Envv: spec.Process.Env,
+ WorkingDirectory: spec.Process.Cwd,
+ Credentials: creds,
+ Umask: uint(0022),
Limits: ls,
MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
UTSNamespace: utsns,
@@ -240,52 +267,42 @@ func New(spec *specs.Spec, conf *Config, controllerFD, restoreFD int, ioFDs []in
}
ctx := procArgs.NewContext(k)
- // Use root user to configure mounts. The current user might not have
- // permission to do so.
- rootProcArgs := kernel.CreateProcessArgs{
- WorkingDirectory: "/",
- Credentials: auth.NewRootCredentials(creds.UserNamespace),
- Umask: uint(syscall.Umask(0022)),
- MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
- }
- rootCtx := rootProcArgs.NewContext(k)
-
- // Create the virtual filesystem.
- mns, err := createMountNamespace(ctx, rootCtx, spec, conf, ioFDs)
- if err != nil {
- return nil, fmt.Errorf("error creating mounts: %v", err)
- }
- k.SetRootMountNamespace(mns)
-
- // Create the FD map, which will set stdin, stdout, and stderr. If console
- // is true, then ioctl calls will be passed through to the host fd.
+ // Create the FD map, which will set stdin, stdout, and stderr. If
+ // console is true, then ioctl calls will be passed through to the host
+ // fd.
fdm, err := createFDMap(ctx, k, ls, console)
if err != nil {
- return nil, fmt.Errorf("error importing fds: %v", err)
+ return kernel.CreateProcessArgs{}, fmt.Errorf("error importing fds: %v", err)
}
// CreateProcess takes a reference on FDMap if successful. We
// won't need ours either way.
procArgs.FDMap = fdm
- // We don't care about child signals; some platforms can generate a
- // tremendous number of useless ones (I'm looking at you, ptrace).
- if err := sighandling.IgnoreChildStop(); err != nil {
- return nil, fmt.Errorf("failed to ignore child stop signals: %v", err)
+ // If this is the root container, we also need to setup the root mount
+ // namespace.
+ if k.RootMountNamespace() == nil {
+ // Use root user to configure mounts. The current user might not have
+ // permission to do so.
+ rootProcArgs := kernel.CreateProcessArgs{
+ WorkingDirectory: "/",
+ Credentials: auth.NewRootCredentials(creds.UserNamespace),
+ // The sentry should run with a umask of 0.
+ Umask: uint(syscall.Umask(0)),
+ MaxSymlinkTraversals: linux.MaxSymlinkTraversals,
+ }
+ rootCtx := rootProcArgs.NewContext(k)
+
+ // Create the virtual filesystem.
+ mns, err := createMountNamespace(ctx, rootCtx, spec, conf, ioFDs)
+ if err != nil {
+ return kernel.CreateProcessArgs{}, fmt.Errorf("error creating mounts: %v", err)
+ }
+
+ k.SetRootMountNamespace(mns)
}
- // Ensure that most signals received in sentry context are forwarded to
- // the emulated kernel.
- stopSignalForwarding := sighandling.StartForwarding(k)
- return &Loader{
- k: k,
- ctrl: ctrl,
- conf: conf,
- console: console,
- watchdog: watchdog,
- stopSignalForwarding: stopSignalForwarding,
- procArgs: procArgs,
- }, nil
+ return procArgs, nil
}
// Destroy cleans up all resources used by the loader.
@@ -350,17 +367,69 @@ func (l *Loader) run() error {
}
// Create the root container init task.
- if _, err := l.k.CreateProcess(l.procArgs); err != nil {
+ if _, err := l.k.CreateProcess(l.rootProcArgs); err != nil {
return fmt.Errorf("failed to create init process: %v", err)
}
// CreateProcess takes a reference on FDMap if successful.
- l.procArgs.FDMap.DecRef()
+ l.rootProcArgs.FDMap.DecRef()
l.watchdog.Start()
return l.k.Start()
}
+func (l *Loader) startContainer(args *StartArgs, k *kernel.Kernel) error {
+ spec := args.Spec
+ // Create capabilities.
+ caps, err := specutils.Capabilities(spec.Process.Capabilities)
+ if err != nil {
+ return fmt.Errorf("error creating capabilities: %v", err)
+ }
+
+ // Convert the spec's additional GIDs to KGIDs.
+ extraKGIDs := make([]auth.KGID, 0, len(spec.Process.User.AdditionalGids))
+ for _, GID := range spec.Process.User.AdditionalGids {
+ extraKGIDs = append(extraKGIDs, auth.KGID(GID))
+ }
+
+ // Create credentials. We reuse the root user namespace because the
+ // sentry currently supports only 1 mount namespace, which is tied to a
+ // single user namespace. Thus we must run in the same user namespace
+ // to access mounts.
+ // TODO: Create a new mount namespace for the container.
+ creds := auth.NewUserCredentials(
+ auth.KUID(spec.Process.User.UID),
+ auth.KGID(spec.Process.User.GID),
+ extraKGIDs,
+ caps,
+ l.k.RootUserNamespace())
+
+ // TODO New containers should be started in new PID namespaces
+ // when indicated by the spec.
+
+ procArgs, err := newProcess(
+ args.Spec,
+ args.Conf,
+ nil, // ioFDs
+ false, // console
+ creds,
+ k.RootUTSNamespace(),
+ k.RootIPCNamespace(),
+ k)
+ if err != nil {
+ return fmt.Errorf("failed to create new process: %v", err)
+ }
+
+ if _, err := l.k.CreateProcess(procArgs); err != nil {
+ return fmt.Errorf("failed to create process in sentry: %v", err)
+ }
+
+ // CreateProcess takes a reference on FDMap if successful.
+ procArgs.FDMap.DecRef()
+
+ return nil
+}
+
// WaitForStartSignal waits for a start signal from the control server.
func (l *Loader) WaitForStartSignal() {
<-l.ctrl.manager.startChan
diff --git a/runsc/cmd/events.go b/runsc/cmd/events.go
index f221ad3ae..df65ea31d 100644
--- a/runsc/cmd/events.go
+++ b/runsc/cmd/events.go
@@ -76,7 +76,7 @@ func (evs *Events) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
c, err := container.Load(conf.RootDir, id)
if err != nil {
- Fatalf("error loading sandox: %v", err)
+ Fatalf("error loading sandbox: %v", err)
}
// Repeatedly get stats from the container.
diff --git a/runsc/cmd/exec.go b/runsc/cmd/exec.go
index 235ed9bc6..cbce07c8e 100644
--- a/runsc/cmd/exec.go
+++ b/runsc/cmd/exec.go
@@ -104,7 +104,7 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
c, err := container.Load(conf.RootDir, id)
if err != nil {
- Fatalf("error loading sandox: %v", err)
+ Fatalf("error loading sandbox: %v", err)
}
if e.WorkingDirectory == "" {
diff --git a/runsc/cmd/ps.go b/runsc/cmd/ps.go
index 9f9f4d15e..5d219bfdc 100644
--- a/runsc/cmd/ps.go
+++ b/runsc/cmd/ps.go
@@ -62,7 +62,7 @@ func (ps *PS) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{})
c, err := container.Load(conf.RootDir, id)
if err != nil {
- Fatalf("error loading sandox: %v", err)
+ Fatalf("error loading sandbox: %v", err)
}
pList, err := c.Processes()
if err != nil {
diff --git a/runsc/container/BUILD b/runsc/container/BUILD
index fe477abf2..61e05e1c3 100644
--- a/runsc/container/BUILD
+++ b/runsc/container/BUILD
@@ -37,6 +37,7 @@ go_test(
"//pkg/sentry/kernel/auth",
"//pkg/unet",
"//runsc/container",
+ "//runsc/specutils",
"//runsc/test/testutil",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
diff --git a/runsc/container/container.go b/runsc/container/container.go
index 571784e07..3b7f95af9 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -214,22 +214,43 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
Owner: os.Getenv("USER"),
}
- // TODO: If the metadata annotations indicate that this
- // container should be started in another sandbox, we must do so. The
- // metadata will indicate the ID of the sandbox, which is the same as
- // the ID of the init container in the sandbox. We can look up that
- // init container by ID to get the sandbox, then we need to expose a
- // way to run a new container in the sandbox.
-
- // Start a new sandbox for this container. Any errors after this point
- // must destroy the container.
- s, err := sandbox.Create(id, spec, conf, bundleDir, consoleSocket)
- if err != nil {
- c.Destroy()
- return nil, err
- }
+ // If the metadata annotations indicate that this container should be
+ // started in an existing sandbox, we must do so. The metadata will
+ // indicate the ID of the sandbox, which is the same as the ID of the
+ // init container in the sandbox.
+ if specutils.ShouldCreateSandbox(spec) {
+ log.Debugf("Creating new sandbox for container %q", id)
+ // Start a new sandbox for this container. Any errors after this point
+ // must destroy the container.
+ s, err := sandbox.Create(id, spec, conf, bundleDir, consoleSocket)
+ if err != nil {
+ c.Destroy()
+ return nil, err
+ }
+ c.Sandbox = s
+ } else {
+ // This is sort of confusing. For a sandbox with a root
+ // container and a child container in it, runsc sees:
+ // * A container struct whose sandbox ID is equal to the
+ // container ID. This is the root container that is tied to
+ // the creation of the sandbox.
+ // * A container struct whose sandbox ID is equal to the above
+ // container/sandbox ID, but that has a different container
+ // ID. This is the child container.
+ sbid, ok := specutils.SandboxID(spec)
+ if !ok {
+ return nil, fmt.Errorf("no sandbox ID found when creating container")
+ }
+ log.Debugf("Creating new container %q in sandbox %q", c.ID, sbid)
- c.Sandbox = s
+ // Find the sandbox associated with this ID.
+ sb, err := Load(conf.RootDir, sbid)
+ if err != nil {
+ c.Destroy()
+ return nil, err
+ }
+ c.Sandbox = sb.Sandbox
+ }
c.Status = Created
// Save the metadata file.
@@ -242,7 +263,7 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
// this file is created, so it must be the last thing we do.
if pidFile != "" {
if err := ioutil.WriteFile(pidFile, []byte(strconv.Itoa(c.Pid())), 0644); err != nil {
- s.Destroy()
+ c.Destroy()
return nil, fmt.Errorf("error writing pid file: %v", err)
}
}
@@ -266,9 +287,16 @@ func (c *Container) Start(conf *boot.Config) error {
}
}
- if err := c.Sandbox.Start(c.ID, c.Spec, conf); err != nil {
- c.Destroy()
- return err
+ if specutils.ShouldCreateSandbox(c.Spec) {
+ if err := c.Sandbox.StartRoot(c.Spec, conf); err != nil {
+ c.Destroy()
+ return err
+ }
+ } else {
+ if err := c.Sandbox.Start(c.Spec, conf); err != nil {
+ c.Destroy()
+ return err
+ }
}
// "If any poststart hook fails, the runtime MUST log a warning, but
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 7f87ea5ab..1116ca170 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -36,6 +36,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth"
"gvisor.googlesource.com/gvisor/pkg/unet"
"gvisor.googlesource.com/gvisor/runsc/container"
+ "gvisor.googlesource.com/gvisor/runsc/specutils"
"gvisor.googlesource.com/gvisor/runsc/test/testutil"
)
@@ -51,7 +52,7 @@ func waitForProcessList(s *container.Container, expected []*control.Process) err
var got []*control.Process
for start := time.Now(); time.Now().Sub(start) < 10*time.Second; {
var err error
- got, err := s.Processes()
+ got, err = s.Processes()
if err != nil {
return fmt.Errorf("error getting process data from container: %v", err)
}
@@ -946,3 +947,73 @@ func TestAbbreviatedIDs(t *testing.T) {
}
}
}
+
+// TestMultiContainerSanity checks that it is possible to run 2 dead-simple
+// containers in the same sandbox.
+func TestMultiContainerSanity(t *testing.T) {
+ 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.Container, 0, len(containerIDs))
+ for i, annotations := range containerAnnotations {
+ spec := testutil.NewSpecWithArgs("sleep", "100")
+ spec.Annotations = annotations
+ bundleDir, conf, err := testutil.SetupContainerInRoot(rootDir, spec)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(bundleDir)
+ cont, err := container.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)
+ }
+}
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index 0181dc9d4..90b46e247 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -81,9 +81,9 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
return s, nil
}
-// Start starts running the containerized process inside the sandbox.
-func (s *Sandbox) Start(cid string, spec *specs.Spec, conf *boot.Config) error {
- log.Debugf("Start sandbox %q, pid: %d", s.ID, s.Pid)
+// StartRoot starts running the root container process inside the sandbox.
+func (s *Sandbox) StartRoot(spec *specs.Spec, conf *boot.Config) error {
+ log.Debugf("Start root sandbox %q, pid: %d", s.ID, s.Pid)
conn, err := s.connect()
if err != nil {
return err
@@ -96,9 +96,7 @@ func (s *Sandbox) Start(cid string, spec *specs.Spec, conf *boot.Config) error {
}
// Send a message to the sandbox control server to start the root
- // container..
- //
- // TODO: We need a way to start non-root containers.
+ // container.
if err := conn.Call(boot.RootContainerStart, nil, nil); err != nil {
return fmt.Errorf("error starting root container %v: %v", spec.Process.Args, err)
}
@@ -106,6 +104,26 @@ func (s *Sandbox) Start(cid string, spec *specs.Spec, conf *boot.Config) error {
return nil
}
+// Start starts running a non-root container inside the sandbox.
+func (s *Sandbox) Start(spec *specs.Spec, conf *boot.Config) error {
+ log.Debugf("Start non-root container sandbox %q, pid: %d", s.ID, s.Pid)
+ conn, err := s.connect()
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ args := boot.StartArgs{
+ Spec: spec,
+ Conf: conf,
+ }
+ if err := conn.Call(boot.ContainerStart, args, nil); err != nil {
+ return fmt.Errorf("error starting non-root container %v: %v", spec.Process.Args, err)
+ }
+
+ return nil
+}
+
// Processes retrieves the list of processes and associated metadata for a
// given container in this sandbox.
func (s *Sandbox) Processes(cid string) ([]*control.Process, error) {
@@ -130,11 +148,11 @@ func (s *Sandbox) Execute(cid string, e *control.ExecArgs) (syscall.WaitStatus,
log.Debugf("Executing new process in container %q in sandbox %q", cid, s.ID)
conn, err := s.connect()
if err != nil {
- return 0, fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err)
+ return 0, s.connError(err)
}
defer conn.Close()
- // Send a message to the sandbox control server to start the container..
+ // Send a message to the sandbox control server to start the container.
var waitStatus uint32
// TODO: Pass in the container id (cid) here. The sandbox
// should execute in the context of that container.
@@ -168,11 +186,15 @@ func (s *Sandbox) connect() (*urpc.Client, error) {
log.Debugf("Connecting to sandbox %q", s.ID)
conn, err := client.ConnectTo(boot.ControlSocketAddr(s.ID))
if err != nil {
- return nil, fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err)
+ return nil, s.connError(err)
}
return conn, nil
}
+func (s *Sandbox) connError(err error) error {
+ return fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err)
+}
+
func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir, binPath string) ([]*os.File, error) {
if conf.FileAccess != boot.FileAccessProxy {
// Don't start a gofer. The sandbox will access host FS directly.
@@ -266,7 +288,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund
}
// If the console control socket file is provided, then create a new
- // pty master/slave pair and set the tty on the sandox process.
+ // pty master/slave pair and set the tty on the sandbox process.
if consoleEnabled {
// setupConsole will send the master on the socket, and return
// the slave.
diff --git a/runsc/sandbox/sandbox_test.go b/runsc/sandbox/sandbox_test.go
index e25290d5e..fee2de283 100644
--- a/runsc/sandbox/sandbox_test.go
+++ b/runsc/sandbox/sandbox_test.go
@@ -44,7 +44,7 @@ func TestGoferExits(t *testing.T) {
t.Fatalf("error creating container: %v", err)
}
defer s.Destroy()
- if err := s.Start("123", spec, conf); err != nil {
+ if err := s.StartRoot(spec, conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
diff --git a/runsc/specutils/specutils.go b/runsc/specutils/specutils.go
index 8dae3efb1..c552111f2 100644
--- a/runsc/specutils/specutils.go
+++ b/runsc/specutils/specutils.go
@@ -63,6 +63,26 @@ func ValidateSpec(spec *specs.Spec) error {
if spec.Linux != nil && spec.Linux.Seccomp != nil {
log.Warningf("Seccomp spec is being ignored")
}
+
+ // 2 annotations are use by containerd to support multi-container pods.
+ // "io.kubernetes.cri.container-type"
+ // "io.kubernetes.cri.sandbox-id"
+ containerType, hasContainerType := spec.Annotations[ContainerdContainerTypeAnnotation]
+ _, hasSandboxID := spec.Annotations[ContainerdSandboxIDAnnotation]
+ switch {
+ // Non-containerd use won't set a container type.
+ case !hasContainerType:
+ case containerType == ContainerdContainerTypeSandbox:
+ // When starting a container in an existing sandbox, the sandbox ID
+ // must be set.
+ case containerType == ContainerdContainerTypeContainer:
+ if !hasSandboxID {
+ return fmt.Errorf("spec has container-type of %s, but no sandbox ID set", containerType)
+ }
+ default:
+ return fmt.Errorf("unknown container-type: %s", containerType)
+ }
+
return nil
}
@@ -82,7 +102,7 @@ func ReadSpec(bundleDir string) (*specs.Spec, error) {
}
// GetExecutablePath returns the absolute path to the executable, relative to
-// the root. It searches the environment PATH for the first file that exists
+// the root. It searches the environment PATH for the first file that exists
// with the given name.
func GetExecutablePath(exec, root string, env []string) (string, error) {
exec = filepath.Clean(exec)
@@ -246,6 +266,39 @@ func BinPath() (string, error) {
return binPath, nil
}
+const (
+ // ContainerdContainerTypeAnnotation is the OCI annotation set by
+ // containerd to indicate whether the container to create should have
+ // its own sandbox or a container within an existing sandbox.
+ ContainerdContainerTypeAnnotation = "io.kubernetes.cri.container-type"
+ // ContainerdContainerTypeContainer is the container type value
+ // indicating the container should be created in an existing sandbox.
+ ContainerdContainerTypeContainer = "container"
+ // ContainerdContainerTypeSandbox is the container type value
+ // indicating the container should be created in a new sandbox.
+ ContainerdContainerTypeSandbox = "sandbox"
+
+ // ContainerdSandboxIDAnnotation is the OCI annotation set to indicate
+ // which sandbox the container should be created in when the container
+ // is not the first container in the sandbox.
+ ContainerdSandboxIDAnnotation = "io.kubernetes.cri.sandbox-id"
+)
+
+// ShouldCreateSandbox returns true if the spec indicates that a new sandbox
+// should be created for the container. If false, the container should be
+// started in an existing sandbox.
+func ShouldCreateSandbox(spec *specs.Spec) bool {
+ t, ok := spec.Annotations[ContainerdContainerTypeAnnotation]
+ return !ok || t == ContainerdContainerTypeSandbox
+}
+
+// SandboxID returns the ID of the sandbox to join and whether an ID was found
+// in the spec.
+func SandboxID(spec *specs.Spec) (string, bool) {
+ id, ok := spec.Annotations[ContainerdSandboxIDAnnotation]
+ return id, ok
+}
+
// WaitForReady waits for a process to become ready. The process is ready when
// the 'ready' function returns true. It continues to wait if 'ready' returns
// false. It returns error on timeout, if the process stops or if 'ready' fails.