summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2022-04-21 11:58:56 -0400
committerGitHub <noreply@github.com>2022-04-21 11:58:56 -0400
commitcb09c26c6519c2fff8be7285d585672a9066f2da (patch)
tree4adfde8375b9a76d6942b612fe2bc75aae49124c /libpod
parente47dda5d81772fcc1c2417062303681a72780bbd (diff)
parentbbe1063a5ae681ad04a049518c6087421b919f2e (diff)
downloadpodman-cb09c26c6519c2fff8be7285d585672a9066f2da.tar.gz
podman-cb09c26c6519c2fff8be7285d585672a9066f2da.tar.bz2
podman-cb09c26c6519c2fff8be7285d585672a9066f2da.zip
Merge pull request #13505 from rst0git/checkpoint-image-1
Add support for checkpoint image
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container_api.go6
-rw-r--r--libpod/container_internal.go5
-rw-r--r--libpod/container_internal_linux.go202
-rw-r--r--libpod/define/annotations.go70
4 files changed, 271 insertions, 12 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 0b6139335..ebefed600 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -754,6 +754,9 @@ type ContainerCheckpointOptions struct {
// TargetFile tells the API to read (or write) the checkpoint image
// from (or to) the filename set in TargetFile
TargetFile string
+ // CheckpointImageID tells the API to restore the container from
+ // checkpoint image with ID set in CheckpointImageID
+ CheckpointImageID string
// Name tells the API that during restore from an exported
// checkpoint archive a new name should be used for the
// restored container
@@ -781,6 +784,9 @@ type ContainerCheckpointOptions struct {
// ImportPrevious tells the API to restore container with two
// images. One is TargetFile, the other is ImportPrevious.
ImportPrevious string
+ // CreateImage tells Podman to create an OCI image from container
+ // checkpoint in the local image store.
+ CreateImage string
// Compression tells the API which compression to use for
// the exported checkpoint archive.
Compression archive.Compression
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index c7567a55e..b051b7f2d 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -132,6 +132,11 @@ func (c *Container) ControlSocketPath() string {
return filepath.Join(c.bundlePath(), "ctl")
}
+// CheckpointVolumesPath returns the path to the directory containing the checkpointed volumes
+func (c *Container) CheckpointVolumesPath() string {
+ return filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory)
+}
+
// CheckpointPath returns the path to the directory containing the checkpoint
func (c *Container) CheckpointPath() string {
return filepath.Join(c.bundlePath(), metadata.CheckpointDirectory)
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 9991003d6..f8d0a124c 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -24,6 +24,7 @@ import (
"github.com/checkpoint-restore/go-criu/v5/stats"
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
"github.com/containernetworking/plugins/pkg/ns"
+ "github.com/containers/buildah"
"github.com/containers/buildah/pkg/chrootuser"
"github.com/containers/buildah/pkg/overlay"
butil "github.com/containers/buildah/util"
@@ -34,6 +35,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/subscriptions"
"github.com/containers/common/pkg/umask"
+ is "github.com/containers/image/v5/storage"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/annotations"
@@ -1098,6 +1100,124 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
return nil
}
+func (c *Container) addCheckpointImageMetadata(importBuilder *buildah.Builder) error {
+ // Get information about host environment
+ hostInfo, err := c.Runtime().hostInfo()
+ if err != nil {
+ return fmt.Errorf("getting host info: %v", err)
+ }
+
+ criuVersion, err := criu.GetCriuVestion()
+ if err != nil {
+ return fmt.Errorf("getting criu version: %v", err)
+ }
+
+ rootfsImageID, rootfsImageName := c.Image()
+
+ // Add image annotations with information about the container and the host.
+ // This information is useful to check compatibility before restoring the checkpoint
+
+ checkpointImageAnnotations := map[string]string{
+ define.CheckpointAnnotationName: c.config.Name,
+ define.CheckpointAnnotationRawImageName: c.config.RawImageName,
+ define.CheckpointAnnotationRootfsImageID: rootfsImageID,
+ define.CheckpointAnnotationRootfsImageName: rootfsImageName,
+ define.CheckpointAnnotationPodmanVersion: version.Version.String(),
+ define.CheckpointAnnotationCriuVersion: strconv.Itoa(criuVersion),
+ define.CheckpointAnnotationRuntimeName: hostInfo.OCIRuntime.Name,
+ define.CheckpointAnnotationRuntimeVersion: hostInfo.OCIRuntime.Version,
+ define.CheckpointAnnotationConmonVersion: hostInfo.Conmon.Version,
+ define.CheckpointAnnotationHostArch: hostInfo.Arch,
+ define.CheckpointAnnotationHostKernel: hostInfo.Kernel,
+ define.CheckpointAnnotationCgroupVersion: hostInfo.CgroupsVersion,
+ define.CheckpointAnnotationDistributionVersion: hostInfo.Distribution.Version,
+ define.CheckpointAnnotationDistributionName: hostInfo.Distribution.Distribution,
+ }
+
+ for key, value := range checkpointImageAnnotations {
+ importBuilder.SetAnnotation(key, value)
+ }
+
+ return nil
+}
+
+func (c *Container) resolveCheckpointImageName(options *ContainerCheckpointOptions) error {
+ if options.CreateImage == "" {
+ return nil
+ }
+
+ // Resolve image name
+ resolvedImageName, err := c.runtime.LibimageRuntime().ResolveName(options.CreateImage)
+ if err != nil {
+ return err
+ }
+
+ options.CreateImage = resolvedImageName
+ return nil
+}
+
+func (c *Container) createCheckpointImage(ctx context.Context, options ContainerCheckpointOptions) error {
+ if options.CreateImage == "" {
+ return nil
+ }
+ logrus.Debugf("Create checkpoint image %s", options.CreateImage)
+
+ // Create storage reference
+ imageRef, err := is.Transport.ParseStoreReference(c.runtime.store, options.CreateImage)
+ if err != nil {
+ return errors.Errorf("Failed to parse image name")
+ }
+
+ // Build an image scratch
+ builderOptions := buildah.BuilderOptions{
+ FromImage: "scratch",
+ }
+ importBuilder, err := buildah.NewBuilder(ctx, c.runtime.store, builderOptions)
+ if err != nil {
+ return err
+ }
+ // Clean-up buildah working container
+ defer importBuilder.Delete()
+
+ if err := c.prepareCheckpointExport(); err != nil {
+ return err
+ }
+
+ // Export checkpoint into temporary tar file
+ tmpDir, err := ioutil.TempDir("", "checkpoint_image_")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(tmpDir)
+
+ options.TargetFile = path.Join(tmpDir, "checkpoint.tar")
+
+ if err := c.exportCheckpoint(options); err != nil {
+ return err
+ }
+
+ // Copy checkpoint from temporary tar file in the image
+ addAndCopyOptions := buildah.AddAndCopyOptions{}
+ importBuilder.Add("", true, addAndCopyOptions, options.TargetFile)
+
+ if err := c.addCheckpointImageMetadata(importBuilder); err != nil {
+ return err
+ }
+
+ commitOptions := buildah.CommitOptions{
+ Squash: true,
+ SystemContext: c.runtime.imageContext,
+ }
+
+ // Create checkpoint image
+ id, _, _, err := importBuilder.Commit(ctx, imageRef, commitOptions)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Created checkpoint image: %s", id)
+ return nil
+}
+
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
if len(c.Dependencies()) == 1 {
// Check if the dependency is an infra container. If it is we can checkpoint
@@ -1159,7 +1279,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
}
// Folder containing archived volumes that will be included in the export
- expVolDir := filepath.Join(c.bundlePath(), "volumes")
+ expVolDir := filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory)
// Create an archive for each volume associated with the container
if !options.IgnoreVolumes {
@@ -1168,7 +1288,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
}
for _, v := range c.config.NamedVolumes {
- volumeTarFilePath := filepath.Join("volumes", v.Name+".tar")
+ volumeTarFilePath := filepath.Join(metadata.CheckpointVolumesDirectory, v.Name+".tar")
volumeTarFileFullPath := filepath.Join(c.bundlePath(), volumeTarFilePath)
volumeTarFile, err := os.Create(volumeTarFileFullPath)
@@ -1266,6 +1386,10 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
return nil, 0, errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
}
+ if err := c.resolveCheckpointImageName(&options); err != nil {
+ return nil, 0, err
+ }
+
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil {
return nil, 0, err
}
@@ -1325,6 +1449,10 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
if err := c.exportCheckpoint(options); err != nil {
return nil, 0, err
}
+ } else {
+ if err := c.createCheckpointImage(ctx, options); err != nil {
+ return nil, 0, err
+ }
}
logrus.Debugf("Checkpointed container %s", c.ID())
@@ -1390,11 +1518,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
return criuStatistics, runtimeCheckpointDuration, c.save()
}
-func (c *Container) importCheckpoint(input string) error {
- if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil {
- return err
- }
-
+func (c *Container) generateContainerSpec() error {
// Make sure the newly created config.json exists on disk
g := generate.NewFromSpec(c.config.Spec)
@@ -1405,6 +1529,51 @@ func (c *Container) importCheckpoint(input string) error {
return nil
}
+func (c *Container) importCheckpointImage(ctx context.Context, imageID string) error {
+ img, _, err := c.Runtime().LibimageRuntime().LookupImage(imageID, nil)
+ if err != nil {
+ return err
+ }
+
+ mountPoint, err := img.Mount(ctx, nil, "")
+ defer img.Unmount(true)
+ if err != nil {
+ return err
+ }
+
+ // Import all checkpoint files except ConfigDumpFile and SpecDumpFile. We
+ // generate new container config files to enable to specifying a new
+ // container name.
+ checkpoint := []string{
+ "artifacts",
+ metadata.CheckpointDirectory,
+ metadata.CheckpointVolumesDirectory,
+ metadata.DevShmCheckpointTar,
+ metadata.RootFsDiffTar,
+ metadata.DeletedFilesFile,
+ metadata.PodOptionsFile,
+ metadata.PodDumpFile,
+ }
+
+ for _, name := range checkpoint {
+ src := filepath.Join(mountPoint, name)
+ dst := filepath.Join(c.bundlePath(), name)
+ if err := archive.NewDefaultArchiver().CopyWithTar(src, dst); err != nil {
+ logrus.Debugf("Can't import '%s' from checkpoint image", name)
+ }
+ }
+
+ return c.generateContainerSpec()
+}
+
+func (c *Container) importCheckpointTar(input string) error {
+ if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil {
+ return err
+ }
+
+ return c.generateContainerSpec()
+}
+
func (c *Container) importPreCheckpoint(input string) error {
archiveFile, err := os.Open(input)
if err != nil {
@@ -1446,7 +1615,11 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}
if options.TargetFile != "" {
- if err := c.importCheckpoint(options.TargetFile); err != nil {
+ if err := c.importCheckpointTar(options.TargetFile); err != nil {
+ return nil, 0, err
+ }
+ } else if options.CheckpointImageID != "" {
+ if err := c.importCheckpointImage(ctx, options.CheckpointImageID); err != nil {
return nil, 0, err
}
}
@@ -1531,7 +1704,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}
// Restoring from an import means that we are doing migration
- if options.TargetFile != "" {
+ if options.TargetFile != "" || options.CheckpointImageID != "" {
g.SetRootPath(c.state.Mountpoint)
}
@@ -1628,7 +1801,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return nil, 0, err
}
- if options.TargetFile != "" {
+ if options.TargetFile != "" || options.CheckpointImageID != "" {
for dstPath, srcPath := range c.state.BindMounts {
newMount := spec.Mount{
Type: "bind",
@@ -1678,9 +1851,9 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// When restoring from an imported archive, allow restoring the content of volumes.
// Volumes are created in setupContainer()
- if options.TargetFile != "" && !options.IgnoreVolumes {
+ if !options.IgnoreVolumes && (options.TargetFile != "" || options.CheckpointImageID != "") {
for _, v := range c.config.NamedVolumes {
- volumeFilePath := filepath.Join(c.bundlePath(), "volumes", v.Name+".tar")
+ volumeFilePath := filepath.Join(c.bundlePath(), metadata.CheckpointVolumesDirectory, v.Name+".tar")
volumeFile, err := os.Open(volumeFilePath)
if err != nil {
@@ -1770,11 +1943,16 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err != nil {
logrus.Debugf("Non-fatal: removal of pre-checkpoint directory (%s) failed: %v", c.PreCheckPointPath(), err)
}
+ err = os.RemoveAll(c.CheckpointVolumesPath())
+ if err != nil {
+ logrus.Debugf("Non-fatal: removal of checkpoint volumes directory (%s) failed: %v", c.CheckpointVolumesPath(), err)
+ }
cleanup := [...]string{
"restore.log",
"dump.log",
stats.StatsDump,
stats.StatsRestore,
+ metadata.DevShmCheckpointTar,
metadata.NetworkStatusFile,
metadata.RootFsDiffTar,
metadata.DeletedFilesFile,
diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go
index 3964a1237..a83fbff0b 100644
--- a/libpod/define/annotations.go
+++ b/libpod/define/annotations.go
@@ -65,6 +65,76 @@ const (
// InspectResponseFalse is a boolean False response for an inspect
// annotation.
InspectResponseFalse = "FALSE"
+
+ // CheckpointAnnotationName is used by Container Checkpoint when creating a
+ // checkpoint image to specify the original human-readable name for the
+ // container.
+ CheckpointAnnotationName = "io.podman.annotations.checkpoint.name"
+
+ // CheckpointAnnotationRawImageName is used by Container Checkpoint when
+ // creating a checkpoint image to specify the original unprocessed name of
+ // the image used to create the container (as specified by the user).
+ CheckpointAnnotationRawImageName = "io.podman.annotations.checkpoint.rawImageName"
+
+ // CheckpointAnnotationRootfsImageID is used by Container Checkpoint when
+ // creating a checkpoint image to specify the original ID of the image used
+ // to create the container.
+ CheckpointAnnotationRootfsImageID = "io.podman.annotations.checkpoint.rootfsImageID"
+
+ // CheckpointAnnotationRootfsImageName is used by Container Checkpoint when
+ // creating a checkpoint image to specify the original image name used to
+ // create the container.
+ CheckpointAnnotationRootfsImageName = "io.podman.annotations.checkpoint.rootfsImageName"
+
+ // CheckpointAnnotationPodmanVersion is used by Container Checkpoint when
+ // creating a checkpoint image to specify the version of Podman used on the
+ // host where the checkpoint was created.
+ CheckpointAnnotationPodmanVersion = "io.podman.annotations.checkpoint.podman.version"
+
+ // CheckpointAnnotationCriuVersion is used by Container Checkpoint when
+ // creating a checkpoint image to specify the version of CRIU used on the
+ // host where the checkpoint was created.
+ CheckpointAnnotationCriuVersion = "io.podman.annotations.checkpoint.criu.version"
+
+ // CheckpointAnnotationRuntimeName is used by Container Checkpoint when
+ // creating a checkpoint image to specify the runtime used on the host where
+ // the checkpoint was created.
+ CheckpointAnnotationRuntimeName = "io.podman.annotations.checkpoint.runtime.name"
+
+ // CheckpointAnnotationRuntimeVersion is used by Container Checkpoint when
+ // creating a checkpoint image to specify the version of runtime used on the
+ // host where the checkpoint was created.
+ CheckpointAnnotationRuntimeVersion = "io.podman.annotations.checkpoint.runtime.version"
+
+ // CheckpointAnnotationConmonVersion is used by Container Checkpoint when
+ // creating a checkpoint image to specify the version of conmon used on
+ // the host where the checkpoint was created.
+ CheckpointAnnotationConmonVersion = "io.podman.annotations.checkpoint.conmon.version"
+
+ // CheckpointAnnotationHostArch is used by Container Checkpoint when
+ // creating a checkpoint image to specify the CPU architecture of the host
+ // on which the checkpoint was created.
+ CheckpointAnnotationHostArch = "io.podman.annotations.checkpoint.host.arch"
+
+ // CheckpointAnnotationHostKernel is used by Container Checkpoint when
+ // creating a checkpoint image to specify the kernel version used by the
+ // host where the checkpoint was created.
+ CheckpointAnnotationHostKernel = "io.podman.annotations.checkpoint.host.kernel"
+
+ // CheckpointAnnotationCgroupVersion is used by Container Checkpoint when
+ // creating a checkpoint image to specify the cgroup version used by the
+ // host where the checkpoint was created.
+ CheckpointAnnotationCgroupVersion = "io.podman.annotations.checkpoint.cgroups.version"
+
+ // CheckpointAnnotationDistributionVersion is used by Container Checkpoint
+ // when creating a checkpoint image to specify the version of host
+ // distribution on which the checkpoint was created.
+ CheckpointAnnotationDistributionVersion = "io.podman.annotations.checkpoint.distribution.version"
+
+ // CheckpointAnnotationDistributionName is used by Container Checkpoint when
+ // creating a checkpoint image to specify the name of host distribution on
+ // which the checkpoint was created.
+ CheckpointAnnotationDistributionName = "io.podman.annotations.checkpoint.distribution.name"
)
// IsReservedAnnotation returns true if the specified value corresponds to an