192 lines
4.8 KiB
Go
192 lines
4.8 KiB
Go
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.Index
|
|
for _, d := range append(checkpoint.Descriptors, additionalDescriptors...) {
|
|
index.Manifests = append(index.Manifests, 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, 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
|
|
}
|
|
rw.Platform = &ocispec.Platform{
|
|
OS: runtime.GOOS,
|
|
Architecture: runtime.GOARCH,
|
|
}
|
|
index.Manifests = append(index.Manifests, rw)
|
|
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
|
|
}
|