api/services: define the container metadata service

Working from feedback on the existing implementation, we have now
introduced a central metadata object to represent the lifecycle and pin
the resources required to implement what people today know as
containers. This includes the runtime specification and the root
filesystem snapshots. We also allow arbitrary labeling of the container.
Such provisions will bring the containerd definition of container closer
to what is expected by users.

The objects that encompass today's ContainerService, centered around the
runtime, will be known as tasks. These tasks take on the existing
lifecycle behavior of containerd's containers, which means that they are
deleted when they exit. Largely, there are no other changes except for
naming.

The `Container` object will operate purely as a metadata object. No
runtime state will be held on `Container`. It only informs the execution
service on what is required for creating tasks and the resources in use
by that container. The resources referenced by that container will be
deleted when the container is deleted, if not in use. In this sense,
users can create, list, label and delete containers in a similar way as
they do with docker today, without the complexity of runtime locks that
plagues current implementations.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
Stephen J Day
2017-05-15 17:44:50 -07:00
parent 8f3b89c79d
commit 539742881d
47 changed files with 4067 additions and 1115 deletions

View File

@@ -46,7 +46,8 @@ var checkpointCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
@@ -59,13 +60,13 @@ var checkpointCommand = cli.Command{
return errors.Wrap(err, "failed resolving image store")
}
var spec specs.Spec
info, err := containers.Info(ctx, &execution.InfoRequest{
ID: id,
info, err := tasks.Info(ctx, &execution.InfoRequest{
ContainerID: id,
})
if err != nil {
return err
}
if err := json.Unmarshal(info.Spec.Value, &spec); err != nil {
if err := json.Unmarshal(info.Task.Spec.Value, &spec); err != nil {
return err
}
stopped := context.Bool("exit")
@@ -73,22 +74,22 @@ var checkpointCommand = cli.Command{
// we pause the container and give us time to checkpoint the filesystem before
// it resumes execution
if !stopped {
if _, err := containers.Pause(ctx, &execution.PauseRequest{
ID: id,
if _, err := tasks.Pause(ctx, &execution.PauseRequest{
ContainerID: id,
}); err != nil {
return err
}
defer func() {
if _, err := containers.Resume(ctx, &execution.ResumeRequest{
ID: id,
if _, err := tasks.Resume(ctx, &execution.ResumeRequest{
ContainerID: id,
}); err != nil {
logrus.WithError(err).Error("ctr: unable to resume container")
}
}()
}
checkpoint, err := containers.Checkpoint(ctx, &execution.CheckpointRequest{
ID: id,
Exit: context.Bool("exit"),
checkpoint, err := tasks.Checkpoint(ctx, &execution.CheckpointRequest{
ContainerID: id,
Exit: context.Bool("exit"),
})
if err != nil {
return err

View File

@@ -3,6 +3,7 @@ package main
import (
gocontext "context"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
@@ -13,10 +14,15 @@ var deleteCommand = cli.Command{
Usage: "delete an existing container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
containers, err := getExecutionService(context)
containers, err := getContainersService(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
if err != nil {
return err
}
snapshotter, err := getSnapshotter(context)
if err != nil {
return err
@@ -26,13 +32,20 @@ var deleteCommand = cli.Command{
return errors.New("container id must be provided")
}
ctx := gocontext.TODO()
_, err = containers.Delete(ctx, &execution.DeleteRequest{
_, err = containers.Delete(ctx, &containersapi.DeleteContainerRequest{
ID: id,
})
if err != nil {
return errors.Wrap(err, "failed to delete container")
}
_, err = tasks.Delete(ctx, &execution.DeleteRequest{
ContainerID: id,
})
if err != nil {
return errors.Wrap(err, "failed to delete container")
}
if err := snapshotter.Remove(ctx, id); err != nil {
return errors.Wrapf(err, "failed to remove snapshot %q", id)
}

View File

@@ -14,11 +14,11 @@ var eventsCommand = cli.Command{
Name: "events",
Usage: "display containerd events",
Action: func(context *cli.Context) error {
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
events, err := containers.Events(gocontext.Background(), &execution.EventsRequest{})
events, err := tasks.Events(gocontext.Background(), &execution.EventsRequest{})
if err != nil {
return err
}

View File

@@ -37,11 +37,11 @@ var execCommand = cli.Command{
return errors.New("container id must be provided")
}
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
events, err := containers.Events(ctx, &execution.EventsRequest{})
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}
@@ -66,12 +66,12 @@ var execCommand = cli.Command{
if err != nil {
return err
}
response, err := containers.Exec(ctx, request)
response, err := tasks.Exec(ctx, request)
if err != nil {
return err
}
if request.Terminal {
if err := handleConsoleResize(ctx, containers, id, response.Pid, con); err != nil {
if err := handleConsoleResize(ctx, tasks, id, response.Pid, con); err != nil {
logrus.WithError(err).Error("console resize")
}
}

View File

@@ -56,7 +56,7 @@ func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecReq
return nil, err
}
return &execution.ExecRequest{
ID: id,
ContainerID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: data,

View File

@@ -23,7 +23,7 @@ func newExecRequest(context *cli.Context, tmpDir, id string) (*execution.ExecReq
}
now := time.Now().UnixNano()
request := &execution.ExecRequest{
ID: id,
ContainerID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: data,

View File

@@ -6,6 +6,7 @@ import (
gocontext "context"
"fmt"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/pkg/errors"
"github.com/urfave/cli"
@@ -26,11 +27,30 @@ var infoCommand = cli.Command{
return errors.New("container id must be provided")
}
containers, err := getExecutionService(context)
containers, err := getContainersService(context)
if err != nil {
return err
}
response, err := containers.Info(gocontext.Background(), &execution.InfoRequest{ID: id})
tasks, err := getTasksService(context)
if err != nil {
return err
}
containerResponse, err := containers.Get(gocontext.TODO(), &containersapi.GetContainerRequest{ID: id})
if err != nil {
return err
}
// TODO(stevvooe): Just dumping the container and the task, for now. We
// should split this into two separate commands.
cjson, err := json.MarshalIndent(containerResponse, "", " ")
if err != nil {
return err
}
fmt.Println(string(cjson))
response, err := tasks.Info(gocontext.Background(), &execution.InfoRequest{ContainerID: id})
if err != nil {
return err
}

View File

@@ -49,8 +49,8 @@ var killCommand = cli.Command{
}
killRequest := &execution.KillRequest{
ID: id,
Signal: uint32(signal),
ContainerID: id,
Signal: uint32(signal),
PidOrAll: &execution.KillRequest_Pid{
Pid: uint32(pid),
},
@@ -62,11 +62,11 @@ var killCommand = cli.Command{
}
}
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
_, err = containers.Kill(gocontext.Background(), killRequest)
_, err = tasks.Kill(gocontext.Background(), killRequest)
if err != nil {
return err
}

View File

@@ -6,10 +6,16 @@ import (
"os"
"text/tabwriter"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
tasktypes "github.com/containerd/containerd/api/types/task"
"github.com/urfave/cli"
)
var containersCommand = cli.Command{
Name: "containers",
}
var listCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
@@ -22,11 +28,18 @@ var listCommand = cli.Command{
},
Action: func(context *cli.Context) error {
quiet := context.Bool("quiet")
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
response, err := containers.List(gocontext.Background(), &execution.ListRequest{})
containers, err := getContainersService(context)
if err != nil {
return err
}
response, err := containers.List(gocontext.Background(), &containersapi.ListContainersRequest{})
if err != nil {
return err
}
@@ -36,13 +49,35 @@ var listCommand = cli.Command{
fmt.Println(c.ID)
}
} else {
tasksResponse, err := tasks.List(gocontext.TODO(), &execution.ListRequest{})
if err != nil {
return err
}
// Join with tasks to get status.
tasksByContainerID := map[string]*tasktypes.Task{}
for _, task := range tasksResponse.Tasks {
task.Descriptor()
tasksByContainerID[task.ContainerID] = task
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprintln(w, "ID\tPID\tSTATUS")
fmt.Fprintln(w, "ID\tIMAGE\tPID\tSTATUS")
for _, c := range response.Containers {
if _, err := fmt.Fprintf(w, "%s\t%d\t%s\n",
var status string
task, ok := tasksByContainerID[c.ID]
if ok {
status = task.Status.String()
} else {
status = "STOPPED" // TODO(stevvooe): Is this assumption correct?
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n",
c.ID,
c.Pid,
c.Status.String(),
c.Image,
task.Pid,
status,
); err != nil {
return err
}

View File

@@ -13,7 +13,7 @@ var pauseCommand = cli.Command{
Usage: "pause an existing container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
@@ -21,8 +21,8 @@ var pauseCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
_, err = containers.Pause(gocontext.Background(), &execution.PauseRequest{
ID: id,
_, err = tasks.Pause(gocontext.Background(), &execution.PauseRequest{
ContainerID: id,
})
return err
},

View File

@@ -27,15 +27,15 @@ var psCommand = cli.Command{
}
pr := &execution.ProcessesRequest{
ID: id,
ContainerID: id,
}
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
resp, err := containers.Processes(gocontext.Background(), pr)
resp, err := tasks.Processes(gocontext.Background(), pr)
if err != nil {
return err
}

View File

@@ -13,7 +13,7 @@ var resumeCommand = cli.Command{
Usage: "resume a paused container",
ArgsUsage: "CONTAINER",
Action: func(context *cli.Context) error {
containers, err := getExecutionService(context)
tasks, err := getTasksService(context)
if err != nil {
return err
}
@@ -21,8 +21,8 @@ var resumeCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
_, err = containers.Resume(gocontext.Background(), &execution.ResumeRequest{
ID: id,
_, err = tasks.Resume(gocontext.Background(), &execution.ResumeRequest{
ContainerID: id,
})
return err
},

View File

@@ -80,7 +80,11 @@ var runCommand = cli.Command{
if id == "" {
return errors.New("container id must be provided")
}
containers, err := getExecutionService(context)
containers, err := getContainersService(context)
if err != nil {
return err
}
tasks, err := getTasksService(context)
if err != nil {
return err
}
@@ -89,7 +93,7 @@ var runCommand = cli.Command{
return err
}
defer os.RemoveAll(tmpDir)
events, err := containers.Events(ctx, &execution.EventsRequest{})
events, err := tasks.Events(ctx, &execution.EventsRequest{})
if err != nil {
return err
}
@@ -227,11 +231,22 @@ var runCommand = cli.Command{
return err
}
if len(spec) == 0 {
if spec, err = newSpec(context, &imageConfig.Config, ref); err != nil {
if spec, err = newContainerSpec(context, &imageConfig.Config, ref); err != nil {
return err
}
}
create, err := newCreateRequest(context, id, tmpDir, checkpoint, mounts, spec)
createContainer, err := newCreateContainerRequest(context, id, id, spec)
if err != nil {
return err
}
_, err = containers.Create(ctx, createContainer)
if err != nil {
return err
}
create, err := newCreateTaskRequest(context, id, tmpDir, checkpoint, mounts)
if err != nil {
return err
}
@@ -247,22 +262,22 @@ var runCommand = cli.Command{
if err != nil {
return err
}
response, err := containers.Create(ctx, create)
response, err := tasks.Create(ctx, create)
if err != nil {
return err
}
pid := response.Pid
if create.Terminal {
if err := handleConsoleResize(ctx, containers, id, pid, con); err != nil {
if err := handleConsoleResize(ctx, tasks, id, pid, con); err != nil {
logrus.WithError(err).Error("console resize")
}
} else {
sigc := forwardAllSignals(containers, id)
sigc := forwardAllSignals(tasks, id)
defer stopCatch(sigc)
}
if checkpoint == nil {
if _, err := containers.Start(ctx, &execution.StartRequest{
ID: id,
if _, err := tasks.Start(ctx, &execution.StartRequest{
ContainerID: id,
}); err != nil {
return err
}
@@ -274,8 +289,8 @@ var runCommand = cli.Command{
if err != nil {
return err
}
if _, err := containers.Delete(ctx, &execution.DeleteRequest{
ID: response.ID,
if _, err := tasks.Delete(ctx, &execution.DeleteRequest{
ContainerID: response.ContainerID,
}); err != nil {
return err
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/descriptor"
"github.com/containerd/containerd/api/types/mount"
@@ -265,7 +266,7 @@ func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig, rootfs st
return customSpec(config, rootfs)
}
func newSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) {
func newContainerSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) {
s, err := getConfig(context, config, context.String("rootfs"))
if err != nil {
return nil, err
@@ -277,26 +278,31 @@ func newSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string)
return json.Marshal(s)
}
func newCreateRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mountt.Mount, spec []byte) (*execution.CreateRequest, error) {
create := &execution.CreateRequest{
ID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: spec,
func newCreateContainerRequest(context *cli.Context, id, snapshot string, spec []byte) (*containersapi.CreateContainerRequest, error) {
create := &containersapi.CreateContainerRequest{
Container: containersapi.Container{
ID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: spec,
},
Runtime: context.String("runtime"),
RootFS: snapshot,
},
Runtime: context.String("runtime"),
Terminal: context.Bool("tty"),
Stdin: filepath.Join(tmpDir, "stdin"),
Stdout: filepath.Join(tmpDir, "stdout"),
Stderr: filepath.Join(tmpDir, "stderr"),
}
if checkpoint != nil {
create.Checkpoint = &descriptor.Descriptor{
MediaType: checkpoint.MediaType,
Size_: checkpoint.Size,
Digest: checkpoint.Digest,
}
return create, nil
}
func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mountt.Mount) (*execution.CreateRequest, error) {
create := &execution.CreateRequest{
ContainerID: id,
Terminal: context.Bool("tty"),
Stdin: filepath.Join(tmpDir, "stdin"),
Stdout: filepath.Join(tmpDir, "stdout"),
Stderr: filepath.Join(tmpDir, "stderr"),
}
for _, m := range mounts {
create.Rootfs = append(create.Rootfs, &mount.Mount{
Type: m.Type,
@@ -304,20 +310,29 @@ func newCreateRequest(context *cli.Context, id, tmpDir string, checkpoint *ocisp
Options: m.Options,
})
}
if checkpoint != nil {
create.Checkpoint = &descriptor.Descriptor{
MediaType: checkpoint.MediaType,
Size_: checkpoint.Size,
Digest: checkpoint.Digest,
}
}
return create, nil
}
func handleConsoleResize(ctx context.Context, service execution.ContainerServiceClient, id string, pid uint32, con console.Console) error {
func handleConsoleResize(ctx context.Context, service execution.TasksClient, id string, pid uint32, con console.Console) error {
// do an initial resize of the console
size, err := con.Size()
if err != nil {
return err
}
if _, err := service.Pty(ctx, &execution.PtyRequest{
ID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
return err
}
@@ -331,10 +346,10 @@ func handleConsoleResize(ctx context.Context, service execution.ContainerService
continue
}
if _, err := service.Pty(ctx, &execution.PtyRequest{
ID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
logrus.WithError(err).Error("resize pty")
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containerd/console"
containersapi "github.com/containerd/containerd/api/services/containers"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/mount"
@@ -117,7 +118,7 @@ func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig, rootfs st
return s, nil
}
func newSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) {
func newContainerSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) {
spec, err := getConfig(context, config, context.String("rootfs"))
if err != nil {
return nil, err
@@ -136,17 +137,28 @@ func newSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string)
return json.Marshal(rtSpec)
}
func newCreateRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mount.Mount, spec []byte) (*execution.CreateRequest, error) {
create := &execution.CreateRequest{
ID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: spec,
func newCreateContainerRequest(context *cli.Context, id, snapshot string, spec []byte) (*containersapi.CreateContainerRequest, error) {
create := &containersapi.CreateContainerRequest{
Container: containersapi.Container{
ID: id,
Spec: &protobuf.Any{
TypeUrl: specs.Version,
Value: spec,
},
Runtime: context.String("runtime"),
RootFS: snapshot,
},
Runtime: context.String("runtime"),
Terminal: context.Bool("tty"),
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
}
return create, nil
}
func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mount.Mount) (*execution.CreateRequest, error) {
create := &execution.CreateRequest{
ContainerID: id,
Terminal: context.Bool("tty"),
Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
}
if !create.Terminal {
create.Stderr = fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id)
@@ -154,7 +166,7 @@ func newCreateRequest(context *cli.Context, id, tmpDir string, checkpoint *ocisp
return create, nil
}
func handleConsoleResize(ctx context.Context, service execution.ContainerServiceClient, id string, pid uint32, con console.Console) error {
func handleConsoleResize(ctx context.Context, service execution.TasksClient, id string, pid uint32, con console.Console) error {
// do an initial resize of the console
size, err := con.Size()
if err != nil {
@@ -173,10 +185,10 @@ func handleConsoleResize(ctx context.Context, service execution.ContainerService
if size.Width != prevSize.Width || size.Height != prevSize.Height {
if _, err := service.Pty(ctx, &execution.PtyRequest{
ID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
ContainerID: id,
Pid: pid,
Width: uint32(size.Width),
Height: uint32(size.Height),
}); err != nil {
log.G(ctx).WithError(err).Error("resize pty")
}

View File

@@ -13,13 +13,14 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
containersapi "github.com/containerd/containerd/api/services/containers"
contentapi "github.com/containerd/containerd/api/services/content"
diffapi "github.com/containerd/containerd/api/services/diff"
"github.com/containerd/containerd/api/services/execution"
imagesapi "github.com/containerd/containerd/api/services/images"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
versionservice "github.com/containerd/containerd/api/services/version"
"github.com/containerd/containerd/api/types/container"
"github.com/containerd/containerd/api/types/task"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
contentservice "github.com/containerd/containerd/services/content"
@@ -34,12 +35,20 @@ import (
var grpcConn *grpc.ClientConn
func getExecutionService(context *cli.Context) (execution.ContainerServiceClient, error) {
func getContainersService(context *cli.Context) (containersapi.ContainersClient, error) {
conn, err := getGRPCConnection(context)
if err != nil {
return nil, err
}
return execution.NewContainerServiceClient(conn), nil
return containersapi.NewContainersClient(conn), nil
}
func getTasksService(context *cli.Context) (execution.TasksClient, error) {
conn, err := getGRPCConnection(context)
if err != nil {
return nil, err
}
return execution.NewTasksClient(conn), nil
}
func getContentStore(context *cli.Context) (content.Store, error) {
@@ -94,13 +103,13 @@ func getTempDir(id string) (string, error) {
return tmpDir, nil
}
func waitContainer(events execution.ContainerService_EventsClient, id string, pid uint32) (uint32, error) {
func waitContainer(events execution.Tasks_EventsClient, id string, pid uint32) (uint32, error) {
for {
e, err := events.Recv()
if err != nil {
return 255, err
}
if e.Type != container.Event_EXIT {
if e.Type != task.Event_EXIT {
continue
}
if e.ID == id && e.Pid == pid {
@@ -109,7 +118,7 @@ func waitContainer(events execution.ContainerService_EventsClient, id string, pi
}
}
func forwardAllSignals(containers execution.ContainerServiceClient, id string) chan os.Signal {
func forwardAllSignals(containers execution.TasksClient, id string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.Notify(sigc)
@@ -117,8 +126,8 @@ func forwardAllSignals(containers execution.ContainerServiceClient, id string) c
for s := range sigc {
logrus.Debug("Forwarding signal ", s)
killRequest := &execution.KillRequest{
ID: id,
Signal: uint32(s.(syscall.Signal)),
ContainerID: id,
Signal: uint32(s.(syscall.Signal)),
PidOrAll: &execution.KillRequest_All{
All: false,
},