Skip to content

Commit

Permalink
Add support for checkpoint image
Browse files Browse the repository at this point in the history
This is an enhancement proposal for the checkpoint / restore feature of
Podman that enables container migration across multiple systems with
standard image distribution infrastructure.

A new option `--create-image <image>` has been added to the
`podman container checkpoint` command. This option tells Podman to
create a container image.  This is a standard image with a single layer,
tar archive, that that contains all checkpoint files. This is similar to
the current approach with checkpoint `--export`/`--import`.

This image can be pushed to a container registry and pulled on a
different system.  It can also be exported locally with `podman image
save` and inspected with `podman inspect`. Inspecting the image would
display additional information about the host and the versions of
Podman, criu, crun/runc, kernel, etc.

`podman container restore` has also been extended to support image
name or ID as input.

Suggested-by: Adrian Reber <[email protected]>
Signed-off-by: Radostin Stoyanov <[email protected]>
  • Loading branch information
rst0git committed Apr 20, 2022
1 parent fca3397 commit 756ecd5
Show file tree
Hide file tree
Showing 17 changed files with 533 additions and 68 deletions.
4 changes: 4 additions & 0 deletions cmd/podman/containers/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func init() {
flags.BoolVarP(&checkpointOptions.PreCheckPoint, "pre-checkpoint", "P", false, "Dump container's memory information only, leave the container running")
flags.BoolVar(&checkpointOptions.WithPrevious, "with-previous", false, "Checkpoint container with pre-checkpoint images")

createImageFlagName := "create-image"
flags.StringVarP(&checkpointOptions.CreateImage, createImageFlagName, "", "", "Create checkpoint image with specified name")
_ = checkpointCommand.RegisterFlagCompletionFunc(createImageFlagName, completion.AutocompleteNone)

flags.StringP("compress", "c", "zstd", "Select compression algorithm (gzip, none, zstd) for checkpoint archive.")
_ = checkpointCommand.RegisterFlagCompletionFunc("compress", common.AutocompleteCheckpointCompressType)

Expand Down
72 changes: 51 additions & 21 deletions cmd/podman/containers/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/utils"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand All @@ -23,15 +23,16 @@ var (
Restores a container from a checkpoint. The container name or ID can be used.
`
restoreCommand = &cobra.Command{
Use: "restore [options] CONTAINER [CONTAINER...]",
Use: "restore [options] CONTAINER|IMAGE [CONTAINER|IMAGE...]",
Short: "Restores one or more containers from a checkpoint",
Long: restoreDescription,
RunE: restore,
Args: func(cmd *cobra.Command, args []string) error {
return validate.CheckAllLatestAndCIDFile(cmd, args, true, false)
},
ValidArgsFunction: common.AutocompleteContainers,
ValidArgsFunction: common.AutocompleteContainersAndImages,
Example: `podman container restore ctrID
podman container restore imageID
podman container restore --latest
podman container restore --all`,
}
Expand Down Expand Up @@ -60,7 +61,7 @@ func init() {
_ = restoreCommand.RegisterFlagCompletionFunc(importFlagName, completion.AutocompleteDefault)

nameFlagName := "name"
flags.StringVarP(&restoreOptions.Name, nameFlagName, "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)")
flags.StringVarP(&restoreOptions.Name, nameFlagName, "n", "", "Specify new name for container restored from exported checkpoint (only works with image or --import)")
_ = restoreCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone)

importPreviousFlagName := "import-previous"
Expand All @@ -78,7 +79,7 @@ func init() {
)
_ = restoreCommand.RegisterFlagCompletionFunc("publish", completion.AutocompleteNone)

flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with --import)")
flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with image or --import)")
_ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning)

flags.BoolVar(
Expand All @@ -95,25 +96,51 @@ func restore(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
podmanStart := time.Now()
if rootless.IsRootless() {
return errors.New("restoring a container requires root")
return fmt.Errorf("restoring a container requires root")
}
if restoreOptions.Import == "" && restoreOptions.ImportPrevious != "" {
return errors.Errorf("--import-previous can only be used with --import")

// Find out if this is an image
inspectOpts := entities.InspectOptions{}
imgData, _, err := registry.ImageEngine().Inspect(context.Background(), args, inspectOpts)
if err != nil {
return err
}
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
return errors.Errorf("--ignore-rootfs can only be used with --import")

hostInfo, err := registry.ContainerEngine().Info(context.Background())
if err != nil {
return err
}
if restoreOptions.Import == "" && restoreOptions.IgnoreVolumes {
return errors.Errorf("--ignore-volumes can only be used with --import")

for i := range imgData {
restoreOptions.CheckpointImage = true
checkpointRuntimeName, found := imgData[i].Annotations[define.CheckpointAnnotationRuntimeName]
if !found {
return fmt.Errorf("image is not a checkpoint: %s", imgData[i].ID)
}
if hostInfo.Host.OCIRuntime.Name != checkpointRuntimeName {
return fmt.Errorf("container image \"%s\" requires runtime: \"%s\"", imgData[i].ID, checkpointRuntimeName)
}
}

notImport := (!restoreOptions.CheckpointImage && restoreOptions.Import == "")

if notImport && restoreOptions.ImportPrevious != "" {
return fmt.Errorf("--import-previous can only be used with image or --import")
}
if restoreOptions.Import == "" && restoreOptions.Name != "" {
return errors.Errorf("--name can only be used with --import")
if notImport && restoreOptions.IgnoreRootFS {
return fmt.Errorf("--ignore-rootfs can only be used with image or --import")
}
if restoreOptions.Import == "" && restoreOptions.Pod != "" {
return errors.Errorf("--pod can only be used with --import")
if notImport && restoreOptions.IgnoreVolumes {
return fmt.Errorf("--ignore-volumes can only be used with image or --import")
}
if notImport && restoreOptions.Name != "" {
return fmt.Errorf("--name can only be used with image or --import")
}
if notImport && restoreOptions.Pod != "" {
return fmt.Errorf("--pod can only be used with image or --import")
}
if restoreOptions.Name != "" && restoreOptions.TCPEstablished {
return errors.Errorf("--tcp-established cannot be used with --name")
return fmt.Errorf("--tcp-established cannot be used with --name")
}

inputPorts, err := cmd.Flags().GetStringSlice("publish")
Expand All @@ -125,17 +152,20 @@ func restore(cmd *cobra.Command, args []string) error {
argLen := len(args)
if restoreOptions.Import != "" {
if restoreOptions.All || restoreOptions.Latest {
return errors.Errorf("Cannot use --import with --all or --latest")
return fmt.Errorf("cannot use --import with --all or --latest")
}
if argLen > 0 {
return errors.Errorf("Cannot use --import with positional arguments")
return fmt.Errorf("cannot use --import with positional arguments")
}
}
if (restoreOptions.All || restoreOptions.Latest) && argLen > 0 {
return errors.Errorf("--all or --latest and containers cannot be used together")
return fmt.Errorf("--all or --latest and containers cannot be used together")
}
if argLen < 1 && !restoreOptions.All && !restoreOptions.Latest && restoreOptions.Import == "" {
return errors.Errorf("you must provide at least one name or id")
return fmt.Errorf("you must provide at least one name or id")
}
if argLen > 1 && restoreOptions.Name != "" {
return fmt.Errorf("--name can only be used with one checkpoint image")
}
responses, err := registry.ContainerEngine().ContainerRestore(context.Background(), args, restoreOptions)
if err != nil {
Expand Down
59 changes: 59 additions & 0 deletions docs/source/markdown/podman-container-checkpoint.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,60 @@ archives. Not compressing the checkpoint archive can result in faster checkpoint
archive creation.\
The default is **zstd**.

#### **--create-image**=*image*

Create a checkpoint image from a running container. This is a standard OCI image
created in the local image store. It consists of a single layer that contains
all of the checkpoint files. The content of this image layer is in the same format as a
checkpoint created with **--export**. A checkpoint image can be pushed to a
standard container registry and pulled on a different system to enable container
migration. In addition, the image can be exported with **podman image save** and
inspected with **podman inspect**. Inspecting a checkpoint image would display
additional information, stored as annotations, about the host environment used
to do the checkpoint:

- **io.podman.annotations.checkpoint.name**: Human-readable name of the original
container.

- **io.podman.annotations.checkpoint.rawImageName**: Unprocessed name of the
image used to create the original container (as specified by the user).

- **io.podman.annotations.checkpoint.rootfsImageID**: ID of the image used to
create the original container.

- **io.podman.annotations.checkpoint.rootfsImageName**: Image name used to
create the original container.

- **io.podman.annotations.checkpoint.podman.version**: Version of Podman used to
create the checkpoint.

- **io.podman.annotations.checkpoint.criu.version**: Version of CRIU used to
create the checkpoint.

- **io.podman.annotations.checkpoint.runtime.name**: Container runtime (e.g.,
runc, crun) used to create the checkpoint.

- **io.podman.annotations.checkpoint.runtime.version**: Version of the container
runtime used to create the checkpoint.

- **io.podman.annotations.checkpoint.conmon.version**: Version of conmon used
with the original container.

- **io.podman.annotations.checkpoint.host.arch**: CPU architecture of the host
on which the checkpoint was created.

- **io.podman.annotations.checkpoint.host.kernel**: Version of Linux kernel
of the host where the checkpoint was created.

- **io.podman.annotations.checkpoint.cgroups.version**: cgroup version used by
the host where the checkpoint was created.

- **io.podman.annotations.checkpoint.distribution.version**: Version of host
distribution on which the checkpoint was created.

- **io.podman.annotations.checkpoint.distribution.name**: Name of host
distribution on which the checkpoint was created.

#### **--export**, **-e**=*archive*

Export the checkpoint to a tar.gz file. The exported checkpoint can be used
Expand Down Expand Up @@ -145,6 +199,11 @@ Make a checkpoint for the container "mywebserver".
# podman container checkpoint mywebserver
```

Create a checkpoint image for the container "mywebserver".
```
# podman container checkpoint --create-image mywebserver-checkpoint-1 mywebserver
```

Dumps the container's memory information of the latest container into an archive.
```
# podman container checkpoint -P -e pre-checkpoint.tar.gz -l
Expand Down
20 changes: 16 additions & 4 deletions docs/source/markdown/podman-container-restore.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
podman\-container\-restore - Restores one or more containers from a checkpoint

## SYNOPSIS
**podman container restore** [*options*] *container* [*container* ...]
**podman container restore** [*options*] *name* [...]

## DESCRIPTION
**podman container restore** restores a container from a checkpoint. The *container IDs* or *names* are used as input.
**podman container restore** restores a container from a container checkpoint or
checkpoint image. The *container IDs*, *image IDs* or *names* are used as input.

## OPTIONS
#### **--all**, **-a**
Expand Down Expand Up @@ -106,14 +107,16 @@ If the **--name, -n** option is used, Podman will not attempt to assign the same
address to the *container* it was using before checkpointing as each IP address can only
be used once and the restored *container* will have another IP address. This also means
that **--name, -n** cannot be used in combination with **--tcp-established**.\
*IMPORTANT: This OPTION is only available in combination with __--import, -i__.*
*IMPORTANT: This OPTION is only available for a checkpoint image or in combination
with __--import, -i__.*

#### **--pod**=*name*

Restore a container into the pod *name*. The destination pod for this restore
has to have the same namespaces shared as the pod this container was checkpointed
from (see **[podman pod create --share](podman-pod-create.1.md#--share)**).\
*IMPORTANT: This OPTION is only available in combination with __--import, -i__.*
*IMPORTANT: This OPTION is only available for a checkpoint image or in combination
with __--import, -i__.*

This option requires at least CRIU 3.16.

Expand Down Expand Up @@ -175,6 +178,15 @@ $ podman run --rm -p 2345:80 -d webserver
# podman container restore -p 5432:8080 --import=dump.tar
```

Start a container with the name "foobar-1". Create a checkpoint image "foobar-checkpoint". Restore the container from the checkpoint image with a different name.
```
# podman run --name foobar-1 -d webserver
# podman container checkpoint --create-image foobar-checkpoint foobar-1
# podman inspect foobar-checkpoint
# podman container restore --name foobar-2 foobar-checkpoint
# podman container restore --name foobar-3 foobar-checkpoint
```

## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-container-checkpoint(1)](podman-container-checkpoint.1.md)**, **[podman-run(1)](podman-run.1.md)**, **[podman-pod-create(1)](podman-pod-create.1.md)**, **criu(8)**

Expand Down
6 changes: 6 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 756ecd5

Please sign in to comment.