| package main |
| |
| import ( |
| "bytes" |
| gocontext "context" |
| "encoding/json" |
| "fmt" |
| "io" |
| "runtime" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/containerd/containerd/api/services/execution" |
| "github.com/containerd/containerd/api/types/descriptor" |
| "github.com/containerd/containerd/archive" |
| "github.com/containerd/containerd/content" |
| "github.com/containerd/containerd/images" |
| "github.com/containerd/containerd/rootfs" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| specs "github.com/opencontainers/runtime-spec/specs-go" |
| "github.com/pkg/errors" |
| "github.com/urfave/cli" |
| ) |
| |
| var checkpointCommand = cli.Command{ |
| Name: "checkpoint", |
| Usage: "checkpoint a container", |
| Flags: []cli.Flag{ |
| cli.StringFlag{ |
| Name: "id", |
| Usage: "id of the container", |
| }, |
| cli.BoolFlag{ |
| Name: "exit", |
| Usage: "stop the container after the checkpoint", |
| }, |
| cli.BoolFlag{ |
| Name: "binds", |
| Usage: "checkpoint bind mounts with the checkpoint", |
| }, |
| }, |
| Action: func(context *cli.Context) error { |
| var ( |
| id = context.String("id") |
| ctx = gocontext.Background() |
| ) |
| if id == "" { |
| return errors.New("container id must be provided") |
| } |
| |
| tasks, err := getTasksService(context) |
| if err != nil { |
| return err |
| } |
| content, err := getContentStore(context) |
| if err != nil { |
| return err |
| } |
| imageStore, err := getImageStore(context) |
| if err != nil { |
| return errors.Wrap(err, "failed resolving image store") |
| } |
| var spec specs.Spec |
| info, err := tasks.Info(ctx, &execution.InfoRequest{ |
| ContainerID: id, |
| }) |
| if err != nil { |
| return err |
| } |
| if err := json.Unmarshal(info.Task.Spec.Value, &spec); err != nil { |
| return err |
| } |
| stopped := context.Bool("exit") |
| // if the container will still be running after the checkpoint make sure that |
| // we pause the container and give us time to checkpoint the filesystem before |
| // it resumes execution |
| if !stopped { |
| if _, err := tasks.Pause(ctx, &execution.PauseRequest{ |
| ContainerID: id, |
| }); err != nil { |
| return err |
| } |
| defer func() { |
| if _, err := tasks.Resume(ctx, &execution.ResumeRequest{ |
| ContainerID: id, |
| }); err != nil { |
| logrus.WithError(err).Error("ctr: unable to resume container") |
| } |
| }() |
| } |
| checkpoint, err := tasks.Checkpoint(ctx, &execution.CheckpointRequest{ |
| ContainerID: id, |
| Exit: context.Bool("exit"), |
| }) |
| if err != nil { |
| return err |
| } |
| image, err := imageStore.Get(ctx, spec.Annotations["image"]) |
| if err != nil { |
| return err |
| } |
| var additionalDescriptors []*descriptor.Descriptor |
| if context.Bool("binds") { |
| if additionalDescriptors, err = checkpointBinds(ctx, &spec, content); err != nil { |
| return err |
| } |
| } |
| var index ocispec.ImageIndex |
| for _, d := range append(checkpoint.Descriptors, additionalDescriptors...) { |
| index.Manifests = append(index.Manifests, ocispec.ManifestDescriptor{ |
| Descriptor: ocispec.Descriptor{ |
| MediaType: d.MediaType, |
| Size: d.Size_, |
| Digest: d.Digest, |
| }, |
| Platform: ocispec.Platform{ |
| OS: runtime.GOOS, |
| Architecture: runtime.GOARCH, |
| }, |
| }) |
| } |
| // add image to the index |
| index.Manifests = append(index.Manifests, ocispec.ManifestDescriptor{ |
| Descriptor: image.Target, |
| }) |
| // checkpoint rw layer |
| snapshotter, err := getSnapshotter(context) |
| if err != nil { |
| return err |
| } |
| differ, err := getDiffService(context) |
| if err != nil { |
| return err |
| } |
| rw, err := rootfs.Diff(ctx, id, fmt.Sprintf("checkpoint-rw-%s", id), snapshotter, differ) |
| if err != nil { |
| return err |
| } |
| index.Manifests = append(index.Manifests, ocispec.ManifestDescriptor{ |
| Descriptor: rw, |
| Platform: ocispec.Platform{ |
| OS: runtime.GOOS, |
| Architecture: runtime.GOARCH, |
| }, |
| }) |
| data, err := json.Marshal(index) |
| if err != nil { |
| return err |
| } |
| // write the index to the content store |
| buf := bytes.NewReader(data) |
| desc, err := writeContent(ctx, content, ocispec.MediaTypeImageIndex, id, buf) |
| if err != nil { |
| return err |
| } |
| fmt.Println(desc.Digest.String()) |
| return nil |
| }, |
| } |
| |
| func checkpointBinds(ctx gocontext.Context, s *specs.Spec, store content.Store) ([]*descriptor.Descriptor, error) { |
| var out []*descriptor.Descriptor |
| for _, m := range s.Mounts { |
| if m.Type != "bind" { |
| continue |
| } |
| tar := archive.Diff(ctx, "", m.Source) |
| d, err := writeContent(ctx, store, images.MediaTypeContainerd1Resource, m.Source, tar) |
| if err := tar.Close(); err != nil { |
| return nil, err |
| } |
| if err != nil { |
| return nil, err |
| } |
| out = append(out, d) |
| } |
| return out, nil |
| } |
| |
| func writeContent(ctx gocontext.Context, store content.Store, mediaType, ref string, r io.Reader) (*descriptor.Descriptor, error) { |
| writer, err := store.Writer(ctx, ref, 0, "") |
| if err != nil { |
| return nil, err |
| } |
| defer writer.Close() |
| size, err := io.Copy(writer, r) |
| if err != nil { |
| return nil, err |
| } |
| if err := writer.Commit(0, ""); err != nil { |
| return nil, err |
| } |
| return &descriptor.Descriptor{ |
| MediaType: mediaType, |
| Digest: writer.Digest(), |
| Size_: size, |
| }, nil |
| } |