diff --git a/client.go b/client.go index 1566ef110..bc86590c2 100644 --- a/client.go +++ b/client.go @@ -538,11 +538,7 @@ func (c *Client) Restore(ctx context.Context, id string, checkpoint Image, opts copts := []NewContainerOpts{} for _, o := range opts { - co, err := o(ctx, id, c, checkpoint, index) - if err != nil { - return nil, err - } - copts = append(copts, co...) + copts = append(copts, o(ctx, id, c, checkpoint, index)) } ctr, err := c.NewContainer(ctx, id, copts...) @@ -550,26 +546,6 @@ func (c *Client) Restore(ctx context.Context, id string, checkpoint Image, opts return nil, err } - // apply rw layer - info, err := ctr.Info(ctx) - if err != nil { - return nil, err - } - - rw, err := GetIndexByMediaType(index, ocispec.MediaTypeImageLayerGzip) - if err != nil { - return nil, err - } - - mounts, err := c.SnapshotService(info.Snapshotter).Mounts(ctx, info.SnapshotKey) - if err != nil { - return nil, err - } - - if _, err := c.DiffService().Apply(ctx, *rw, mounts); err != nil { - return nil, err - } - return ctr, nil } diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index c21381947..af5cbd539 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -318,7 +318,10 @@ var checkpointCommand = cli.Command{ return err } defer cancel() - opts := []containerd.CheckpointOpts{} + opts := []containerd.CheckpointOpts{ + containerd.WithCheckpointRuntime, + } + if context.Bool("image") { opts = append(opts, containerd.WithCheckpointImage) } @@ -338,10 +341,13 @@ var checkpointCommand = cli.Command{ return err } } - if err := task.Pause(ctx); err != nil { - return err + // pause if running + if task != nil { + if err := task.Pause(ctx); err != nil { + return err + } + defer task.Resume(ctx) } - defer task.Resume(ctx) if _, err := container.Checkpoint(ctx, ref, opts...); err != nil { return err @@ -360,6 +366,10 @@ var restoreCommand = cli.Command{ Name: "live", Usage: "restore the runtime and memory data from the checkpoint", }, + cli.BoolFlag{ + Name: "rw", + Usage: "restore the rw layer from the checkpoint", + }, }, Action: func(context *cli.Context) error { id := context.Args().First() @@ -394,6 +404,9 @@ var restoreCommand = cli.Command{ containerd.WithRestoreSnapshot, containerd.WithRestoreRuntime, } + if context.Bool("rw") { + opts = append(opts, containerd.WithRestoreRW) + } ctr, err := client.Restore(ctx, id, checkpoint, opts...) if err != nil { @@ -404,6 +417,7 @@ var restoreCommand = cli.Command{ if context.Bool("live") { topts = append(topts, containerd.WithTaskCheckpoint(checkpoint)) } + task, err := ctr.NewTask(ctx, cio.NewCreator(cio.WithStdio), topts...) if err != nil { return err diff --git a/container.go b/container.go index c20f526be..39993faae 100644 --- a/container.go +++ b/container.go @@ -17,12 +17,10 @@ package containerd import ( - "bytes" "context" "encoding/json" "os" "path/filepath" - "runtime" "strings" "github.com/containerd/containerd/api/services/tasks/v1" @@ -40,6 +38,11 @@ import ( "github.com/pkg/errors" ) +const ( + checkpointImageNameLabel = "image.name" + checkpointRuntimeNameLabel = "runtime.name" +) + // Container is a metadata object for container resources and task creation type Container interface { // ID identifies the container @@ -312,45 +315,9 @@ func (c *container) Checkpoint(ctx context.Context, ref string, opts ...Checkpoi defer done(ctx) // add image name to manifest - ir := bytes.NewReader([]byte(img.Name())) - idesc, err := writeContent(ctx, c.client.ContentStore(), images.MediaTypeContainerd1CheckpointImageName, info.ID+"-image-name", ir) - if err != nil { - return nil, err - } - idesc.Platform = &ocispec.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - } - index.Manifests = append(index.Manifests, idesc) - + index.Annotations[checkpointImageNameLabel] = img.Name() // add runtime info to index - rr := bytes.NewReader([]byte(info.Runtime.Name)) - rdesc, err := writeContent(ctx, c.client.ContentStore(), images.MediaTypeContainerd1CheckpointRuntimeName, info.ID+"-runtime-name", rr) - if err != nil { - return nil, err - } - rdesc.Platform = &ocispec.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - } - index.Manifests = append(index.Manifests, rdesc) - - if info.Runtime.Options != nil { - data, err := info.Runtime.Options.Marshal() - if err != nil { - return nil, err - } - r := bytes.NewReader(data) - desc, err := writeContent(ctx, c.client.ContentStore(), images.MediaTypeContainerd1CheckpointRuntimeOptions, info.ID+"-runtime-options", r) - if err != nil { - return nil, err - } - desc.Platform = &ocispec.Platform{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - } - index.Manifests = append(index.Manifests, desc) - } + index.Annotations[checkpointRuntimeNameLabel] = info.Runtime.Name // process remaining opts for _, o := range opts { diff --git a/container_checkpoint_opts.go b/container_checkpoint_opts.go index b0d618070..3f78a4f12 100644 --- a/container_checkpoint_opts.go +++ b/container_checkpoint_opts.go @@ -17,6 +17,7 @@ package containerd import ( + "bytes" "context" "fmt" "runtime" @@ -24,6 +25,7 @@ import ( tasks "github.com/containerd/containerd/api/services/tasks/v1" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/diff" + "github.com/containerd/containerd/images" "github.com/containerd/containerd/rootfs" "github.com/containerd/containerd/runtime/v2/runc/options" "github.com/containerd/typeurl" @@ -78,23 +80,35 @@ func WithCheckpointTask(ctx context.Context, client *Client, c *containers.Conta return nil } +// WithCheckpointRuntime includes the container runtime info +func WithCheckpointRuntime(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error { + if c.Runtime.Options != nil { + data, err := c.Runtime.Options.Marshal() + if err != nil { + return err + } + r := bytes.NewReader(data) + desc, err := writeContent(ctx, client.ContentStore(), images.MediaTypeContainerd1CheckpointRuntimeOptions, c.ID+"-runtime-options", r) + if err != nil { + return err + } + desc.Platform = &imagespec.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + } + index.Manifests = append(index.Manifests, desc) + } + return nil +} + // WithCheckpointRW includes the rw in the checkpoint func WithCheckpointRW(ctx context.Context, client *Client, c *containers.Container, index *imagespec.Index, copts *options.CheckpointOptions) error { - cnt, err := client.LoadContainer(ctx, c.ID) - if err != nil { - return err - } - info, err := cnt.Info(ctx) - if err != nil { - return err - } - diffOpts := []diff.Opt{ - diff.WithReference(fmt.Sprintf("checkpoint-rw-%s", info.SnapshotKey)), + diff.WithReference(fmt.Sprintf("checkpoint-rw-%s", c.SnapshotKey)), } rw, err := rootfs.CreateDiff(ctx, - info.SnapshotKey, - client.SnapshotService(info.Snapshotter), + c.SnapshotKey, + client.SnapshotService(c.Snapshotter), client.DiffService(), diffOpts..., ) diff --git a/container_restore_opts.go b/container_restore_opts.go index eeb353c34..a6b41ff9d 100644 --- a/container_restore_opts.go +++ b/container_restore_opts.go @@ -19,11 +19,10 @@ package containerd import ( "context" + "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" - "github.com/containerd/containerd/oci" "github.com/containerd/containerd/platforms" - "github.com/containerd/typeurl" "github.com/gogo/protobuf/proto" ptypes "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" @@ -31,135 +30,130 @@ import ( "github.com/pkg/errors" ) +var ( + // ErrImageNameNotFoundInIndex is returned when the image name is not found in the index + ErrImageNameNotFoundInIndex = errors.New("image name not found in index") + // ErrRuntimeNameNotFoundInIndex is returned when the runtime name is not found in the index + ErrRuntimeNameNotFoundInIndex = errors.New("runtime name not found in index") +) + // RestoreOpts are options to manage the restore operation -type RestoreOpts func(context.Context, string, *Client, Image, *imagespec.Index) ([]NewContainerOpts, error) +type RestoreOpts func(context.Context, string, *Client, Image, *imagespec.Index) NewContainerOpts // WithRestoreImage restores the image for the container -func WithRestoreImage(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) ([]NewContainerOpts, error) { - store := client.ContentStore() - m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointImageName) - if err != nil { - if err != ErrMediaTypeNotFound { - return nil, err +func WithRestoreImage(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + name, ok := index.Annotations[checkpointImageNameLabel] + if !ok || name == "" { + return ErrRuntimeNameNotFoundInIndex } - } - imageName := "" - if m != nil { - data, err := content.ReadBlob(ctx, store, *m) + i, err := client.GetImage(ctx, name) if err != nil { - return nil, err + return err } - imageName = string(data) - } - i, err := client.GetImage(ctx, imageName) - if err != nil { - return nil, err - } - return []NewContainerOpts{ - WithImage(i), - }, nil + c.Image = i.Name() + return nil + } } // WithRestoreRuntime restores the runtime for the container -func WithRestoreRuntime(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) ([]NewContainerOpts, error) { - store := client.ContentStore() - n, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointRuntimeName) - if err != nil { - if err != ErrMediaTypeNotFound { - return nil, err +func WithRestoreRuntime(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + name, ok := index.Annotations[checkpointRuntimeNameLabel] + if !ok { + return ErrRuntimeNameNotFoundInIndex } - } - runtimeName := "" - if n != nil { - data, err := content.ReadBlob(ctx, store, *n) - if err != nil { - return nil, err - } - runtimeName = string(data) - } - // restore options if present - m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointRuntimeOptions) - if err != nil { - if err != ErrMediaTypeNotFound { - return nil, err - } - } - - var options *ptypes.Any - if m != nil { - data, err := content.ReadBlob(ctx, store, *m) + // restore options if present + m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointRuntimeOptions) if err != nil { - return nil, errors.Wrap(err, "unable to read checkpoint runtime") + if err != ErrMediaTypeNotFound { + return err + } } - if err := proto.Unmarshal(data, options); err != nil { - return nil, err + var options *ptypes.Any + if m != nil { + store := client.ContentStore() + data, err := content.ReadBlob(ctx, store, *m) + if err != nil { + return errors.Wrap(err, "unable to read checkpoint runtime") + } + if err := proto.Unmarshal(data, options); err != nil { + return err + } } + + c.Runtime = containers.RuntimeInfo{ + Name: name, + Options: options, + } + return nil } - return []NewContainerOpts{ - WithRuntime(runtimeName, options), - }, nil } // WithRestoreSpec restores the spec from the checkpoint for the container -func WithRestoreSpec(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) ([]NewContainerOpts, error) { - m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointConfig) - if err != nil { - return nil, err +func WithRestoreSpec(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointConfig) + if err != nil { + return err + } + store := client.ContentStore() + data, err := content.ReadBlob(ctx, store, *m) + if err != nil { + return errors.Wrap(err, "unable to read checkpoint config") + } + var any ptypes.Any + if err := proto.Unmarshal(data, &any); err != nil { + return err + } + c.Spec = &any + return nil } - store := client.ContentStore() - data, err := content.ReadBlob(ctx, store, *m) - if err != nil { - return nil, errors.Wrap(err, "unable to read checkpoint config") - } - var any ptypes.Any - if err := proto.Unmarshal(data, &any); err != nil { - return nil, err - } - - v, err := typeurl.UnmarshalAny(&any) - if err != nil { - return nil, err - } - spec := v.(*oci.Spec) - return []NewContainerOpts{ - WithSpec(spec), - }, nil } // WithRestoreSnapshot restores the snapshot from the checkpoint for the container -func WithRestoreSnapshot(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) ([]NewContainerOpts, error) { - imageName := "" - store := client.ContentStore() - m, err := GetIndexByMediaType(index, images.MediaTypeContainerd1CheckpointImageName) - if err != nil { - if err != ErrMediaTypeNotFound { - return nil, err +func WithRestoreSnapshot(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + imageName, ok := index.Annotations[checkpointImageNameLabel] + if !ok || imageName == "" { + return ErrRuntimeNameNotFoundInIndex } - } - if m != nil { - data, err := content.ReadBlob(ctx, store, *m) + i, err := client.GetImage(ctx, imageName) if err != nil { - return nil, err + return err } - imageName = string(data) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) + if err != nil { + return err + } + parent := identity.ChainID(diffIDs).String() + if _, err := client.SnapshotService(DefaultSnapshotter).Prepare(ctx, id, parent); err != nil { + return err + } + c.SnapshotKey = id + c.Snapshotter = DefaultSnapshotter + return nil + } +} + +// WithRestoreSnapshot restores the snapshot from the checkpoint for the container +func WithRestoreRW(ctx context.Context, id string, client *Client, checkpoint Image, index *imagespec.Index) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + // apply rw layer + rw, err := GetIndexByMediaType(index, imagespec.MediaTypeImageLayerGzip) + if err != nil { + return err + } + mounts, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, c.SnapshotKey) + if err != nil { + return err + } + + if _, err := client.DiffService().Apply(ctx, *rw, mounts); err != nil { + return err + } + return nil } - i, err := client.GetImage(ctx, imageName) - if err != nil { - return nil, err - } - - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) - if err != nil { - return nil, err - } - parent := identity.ChainID(diffIDs).String() - if _, err := client.SnapshotService(DefaultSnapshotter).Prepare(ctx, id, parent); err != nil { - return nil, err - } - return []NewContainerOpts{ - WithImage(i), - WithSnapshot(id), - }, nil }