mirror of
https://github.com/containers/podman.git
synced 2025-05-19 16:18:51 +08:00
Add support for checkpoint image
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 <areber@redhat.com> Signed-off-by: Radostin Stoyanov <radostin@redhat.com>
This commit is contained in:
@ -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.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")
|
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.")
|
flags.StringP("compress", "c", "zstd", "Select compression algorithm (gzip, none, zstd) for checkpoint archive.")
|
||||||
_ = checkpointCommand.RegisterFlagCompletionFunc("compress", common.AutocompleteCheckpointCompressType)
|
_ = checkpointCommand.RegisterFlagCompletionFunc("compress", common.AutocompleteCheckpointCompressType)
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ import (
|
|||||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||||
"github.com/containers/podman/v4/cmd/podman/utils"
|
"github.com/containers/podman/v4/cmd/podman/utils"
|
||||||
"github.com/containers/podman/v4/cmd/podman/validate"
|
"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/domain/entities"
|
||||||
"github.com/containers/podman/v4/pkg/rootless"
|
"github.com/containers/podman/v4/pkg/rootless"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,15 +23,16 @@ var (
|
|||||||
Restores a container from a checkpoint. The container name or ID can be used.
|
Restores a container from a checkpoint. The container name or ID can be used.
|
||||||
`
|
`
|
||||||
restoreCommand = &cobra.Command{
|
restoreCommand = &cobra.Command{
|
||||||
Use: "restore [options] CONTAINER [CONTAINER...]",
|
Use: "restore [options] CONTAINER|IMAGE [CONTAINER|IMAGE...]",
|
||||||
Short: "Restores one or more containers from a checkpoint",
|
Short: "Restores one or more containers from a checkpoint",
|
||||||
Long: restoreDescription,
|
Long: restoreDescription,
|
||||||
RunE: restore,
|
RunE: restore,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
return validate.CheckAllLatestAndCIDFile(cmd, args, true, false)
|
return validate.CheckAllLatestAndCIDFile(cmd, args, true, false)
|
||||||
},
|
},
|
||||||
ValidArgsFunction: common.AutocompleteContainers,
|
ValidArgsFunction: common.AutocompleteContainersAndImages,
|
||||||
Example: `podman container restore ctrID
|
Example: `podman container restore ctrID
|
||||||
|
podman container restore imageID
|
||||||
podman container restore --latest
|
podman container restore --latest
|
||||||
podman container restore --all`,
|
podman container restore --all`,
|
||||||
}
|
}
|
||||||
@ -60,7 +61,7 @@ func init() {
|
|||||||
_ = restoreCommand.RegisterFlagCompletionFunc(importFlagName, completion.AutocompleteDefault)
|
_ = restoreCommand.RegisterFlagCompletionFunc(importFlagName, completion.AutocompleteDefault)
|
||||||
|
|
||||||
nameFlagName := "name"
|
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)
|
_ = restoreCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone)
|
||||||
|
|
||||||
importPreviousFlagName := "import-previous"
|
importPreviousFlagName := "import-previous"
|
||||||
@ -78,7 +79,7 @@ func init() {
|
|||||||
)
|
)
|
||||||
_ = restoreCommand.RegisterFlagCompletionFunc("publish", completion.AutocompleteNone)
|
_ = 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)
|
_ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning)
|
||||||
|
|
||||||
flags.BoolVar(
|
flags.BoolVar(
|
||||||
@ -95,25 +96,51 @@ func restore(cmd *cobra.Command, args []string) error {
|
|||||||
var errs utils.OutputErrors
|
var errs utils.OutputErrors
|
||||||
podmanStart := time.Now()
|
podmanStart := time.Now()
|
||||||
if rootless.IsRootless() {
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if restoreOptions.Import == "" && restoreOptions.Name != "" {
|
|
||||||
return errors.Errorf("--name can only be used with --import")
|
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.Pod != "" {
|
if notImport && restoreOptions.IgnoreRootFS {
|
||||||
return errors.Errorf("--pod can only be used with --import")
|
return fmt.Errorf("--ignore-rootfs can only be used with image or --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 {
|
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")
|
inputPorts, err := cmd.Flags().GetStringSlice("publish")
|
||||||
@ -125,17 +152,20 @@ func restore(cmd *cobra.Command, args []string) error {
|
|||||||
argLen := len(args)
|
argLen := len(args)
|
||||||
if restoreOptions.Import != "" {
|
if restoreOptions.Import != "" {
|
||||||
if restoreOptions.All || restoreOptions.Latest {
|
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 {
|
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 {
|
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 == "" {
|
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)
|
responses, err := registry.ContainerEngine().ContainerRestore(context.Background(), args, restoreOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,6 +28,60 @@ archives. Not compressing the checkpoint archive can result in faster checkpoint
|
|||||||
archive creation.\
|
archive creation.\
|
||||||
The default is **zstd**.
|
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**, **-e**=*archive*
|
||||||
|
|
||||||
Export the checkpoint to a tar.gz file. The exported checkpoint can be used
|
Export the checkpoint to a tar.gz file. The exported checkpoint can be used
|
||||||
@ -145,6 +199,11 @@ Make a checkpoint for the container "mywebserver".
|
|||||||
# podman container checkpoint 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.
|
Dumps the container's memory information of the latest container into an archive.
|
||||||
```
|
```
|
||||||
# podman container checkpoint -P -e pre-checkpoint.tar.gz -l
|
# podman container checkpoint -P -e pre-checkpoint.tar.gz -l
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
podman\-container\-restore - Restores one or more containers from a checkpoint
|
podman\-container\-restore - Restores one or more containers from a checkpoint
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman container restore** [*options*] *container* [*container* ...]
|
**podman container restore** [*options*] *name* [...]
|
||||||
|
|
||||||
## DESCRIPTION
|
## 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
|
## OPTIONS
|
||||||
#### **--all**, **-a**
|
#### **--all**, **-a**
|
||||||
@ -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
|
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
|
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**.\
|
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*
|
#### **--pod**=*name*
|
||||||
|
|
||||||
Restore a container into the pod *name*. The destination pod for this restore
|
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
|
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)**).\
|
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.
|
This option requires at least CRIU 3.16.
|
||||||
|
|
||||||
@ -175,6 +178,15 @@ $ podman run --rm -p 2345:80 -d webserver
|
|||||||
# podman container restore -p 5432:8080 --import=dump.tar
|
# 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
|
## 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)**
|
**[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)**
|
||||||
|
|
||||||
|
@ -754,6 +754,9 @@ type ContainerCheckpointOptions struct {
|
|||||||
// TargetFile tells the API to read (or write) the checkpoint image
|
// TargetFile tells the API to read (or write) the checkpoint image
|
||||||
// from (or to) the filename set in TargetFile
|
// from (or to) the filename set in TargetFile
|
||||||
TargetFile string
|
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
|
// Name tells the API that during restore from an exported
|
||||||
// checkpoint archive a new name should be used for the
|
// checkpoint archive a new name should be used for the
|
||||||
// restored container
|
// restored container
|
||||||
@ -781,6 +784,9 @@ type ContainerCheckpointOptions struct {
|
|||||||
// ImportPrevious tells the API to restore container with two
|
// ImportPrevious tells the API to restore container with two
|
||||||
// images. One is TargetFile, the other is ImportPrevious.
|
// images. One is TargetFile, the other is ImportPrevious.
|
||||||
ImportPrevious string
|
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
|
// Compression tells the API which compression to use for
|
||||||
// the exported checkpoint archive.
|
// the exported checkpoint archive.
|
||||||
Compression archive.Compression
|
Compression archive.Compression
|
||||||
|
@ -132,6 +132,11 @@ func (c *Container) ControlSocketPath() string {
|
|||||||
return filepath.Join(c.bundlePath(), "ctl")
|
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
|
// CheckpointPath returns the path to the directory containing the checkpoint
|
||||||
func (c *Container) CheckpointPath() string {
|
func (c *Container) CheckpointPath() string {
|
||||||
return filepath.Join(c.bundlePath(), metadata.CheckpointDirectory)
|
return filepath.Join(c.bundlePath(), metadata.CheckpointDirectory)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/checkpoint-restore/go-criu/v5/stats"
|
"github.com/checkpoint-restore/go-criu/v5/stats"
|
||||||
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containers/buildah"
|
||||||
"github.com/containers/buildah/pkg/chrootuser"
|
"github.com/containers/buildah/pkg/chrootuser"
|
||||||
"github.com/containers/buildah/pkg/overlay"
|
"github.com/containers/buildah/pkg/overlay"
|
||||||
butil "github.com/containers/buildah/util"
|
butil "github.com/containers/buildah/util"
|
||||||
@ -34,6 +35,7 @@ import (
|
|||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/common/pkg/subscriptions"
|
"github.com/containers/common/pkg/subscriptions"
|
||||||
"github.com/containers/common/pkg/umask"
|
"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/define"
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
"github.com/containers/podman/v4/libpod/events"
|
||||||
"github.com/containers/podman/v4/pkg/annotations"
|
"github.com/containers/podman/v4/pkg/annotations"
|
||||||
@ -1098,6 +1100,124 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
|
|||||||
return nil
|
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 {
|
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
||||||
if len(c.Dependencies()) == 1 {
|
if len(c.Dependencies()) == 1 {
|
||||||
// Check if the dependency is an infra container. If it is we can checkpoint
|
// 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
|
// 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
|
// Create an archive for each volume associated with the container
|
||||||
if !options.IgnoreVolumes {
|
if !options.IgnoreVolumes {
|
||||||
@ -1168,7 +1288,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range c.config.NamedVolumes {
|
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)
|
volumeTarFileFullPath := filepath.Join(c.bundlePath(), volumeTarFilePath)
|
||||||
|
|
||||||
volumeTarFile, err := os.Create(volumeTarFileFullPath)
|
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")
|
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 {
|
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@ -1325,6 +1449,10 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
|
|||||||
if err := c.exportCheckpoint(options); err != nil {
|
if err := c.exportCheckpoint(options); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if err := c.createCheckpointImage(ctx, options); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Checkpointed container %s", c.ID())
|
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()
|
return criuStatistics, runtimeCheckpointDuration, c.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) importCheckpoint(input string) error {
|
func (c *Container) generateContainerSpec() error {
|
||||||
if err := crutils.CRImportCheckpointWithoutConfig(c.bundlePath(), input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the newly created config.json exists on disk
|
// Make sure the newly created config.json exists on disk
|
||||||
g := generate.NewFromSpec(c.config.Spec)
|
g := generate.NewFromSpec(c.config.Spec)
|
||||||
|
|
||||||
@ -1405,6 +1529,51 @@ func (c *Container) importCheckpoint(input string) error {
|
|||||||
return nil
|
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 {
|
func (c *Container) importPreCheckpoint(input string) error {
|
||||||
archiveFile, err := os.Open(input)
|
archiveFile, err := os.Open(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1446,7 +1615,11 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
|||||||
}
|
}
|
||||||
|
|
||||||
if options.TargetFile != "" {
|
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
|
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
|
// Restoring from an import means that we are doing migration
|
||||||
if options.TargetFile != "" {
|
if options.TargetFile != "" || options.CheckpointImageID != "" {
|
||||||
g.SetRootPath(c.state.Mountpoint)
|
g.SetRootPath(c.state.Mountpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1628,7 +1801,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.TargetFile != "" {
|
if options.TargetFile != "" || options.CheckpointImageID != "" {
|
||||||
for dstPath, srcPath := range c.state.BindMounts {
|
for dstPath, srcPath := range c.state.BindMounts {
|
||||||
newMount := spec.Mount{
|
newMount := spec.Mount{
|
||||||
Type: "bind",
|
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.
|
// When restoring from an imported archive, allow restoring the content of volumes.
|
||||||
// Volumes are created in setupContainer()
|
// Volumes are created in setupContainer()
|
||||||
if options.TargetFile != "" && !options.IgnoreVolumes {
|
if !options.IgnoreVolumes && (options.TargetFile != "" || options.CheckpointImageID != "") {
|
||||||
for _, v := range c.config.NamedVolumes {
|
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)
|
volumeFile, err := os.Open(volumeFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1770,11 +1943,16 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Non-fatal: removal of pre-checkpoint directory (%s) failed: %v", c.PreCheckPointPath(), err)
|
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{
|
cleanup := [...]string{
|
||||||
"restore.log",
|
"restore.log",
|
||||||
"dump.log",
|
"dump.log",
|
||||||
stats.StatsDump,
|
stats.StatsDump,
|
||||||
stats.StatsRestore,
|
stats.StatsRestore,
|
||||||
|
metadata.DevShmCheckpointTar,
|
||||||
metadata.NetworkStatusFile,
|
metadata.NetworkStatusFile,
|
||||||
metadata.RootFsDiffTar,
|
metadata.RootFsDiffTar,
|
||||||
metadata.DeletedFilesFile,
|
metadata.DeletedFilesFile,
|
||||||
|
@ -65,6 +65,76 @@ const (
|
|||||||
// InspectResponseFalse is a boolean False response for an inspect
|
// InspectResponseFalse is a boolean False response for an inspect
|
||||||
// annotation.
|
// annotation.
|
||||||
InspectResponseFalse = "FALSE"
|
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
|
// IsReservedAnnotation returns true if the specified value corresponds to an
|
||||||
|
@ -209,15 +209,16 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||||
query := struct {
|
query := struct {
|
||||||
Keep bool `schema:"keep"`
|
Keep bool `schema:"keep"`
|
||||||
LeaveRunning bool `schema:"leaveRunning"`
|
LeaveRunning bool `schema:"leaveRunning"`
|
||||||
TCPEstablished bool `schema:"tcpEstablished"`
|
TCPEstablished bool `schema:"tcpEstablished"`
|
||||||
Export bool `schema:"export"`
|
Export bool `schema:"export"`
|
||||||
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||||
PrintStats bool `schema:"printStats"`
|
PrintStats bool `schema:"printStats"`
|
||||||
PreCheckpoint bool `schema:"preCheckpoint"`
|
PreCheckpoint bool `schema:"preCheckpoint"`
|
||||||
WithPrevious bool `schema:"withPrevious"`
|
WithPrevious bool `schema:"withPrevious"`
|
||||||
FileLocks bool `schema:"fileLocks"`
|
FileLocks bool `schema:"fileLocks"`
|
||||||
|
CreateImage string `schema:"createImage"`
|
||||||
}{
|
}{
|
||||||
// override any golang type defaults
|
// override any golang type defaults
|
||||||
}
|
}
|
||||||
@ -243,6 +244,7 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
PreCheckPoint: query.PreCheckpoint,
|
PreCheckPoint: query.PreCheckpoint,
|
||||||
WithPrevious: query.WithPrevious,
|
WithPrevious: query.WithPrevious,
|
||||||
FileLocks: query.FileLocks,
|
FileLocks: query.FileLocks,
|
||||||
|
CreateImage: query.CreateImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Export {
|
if query.Export {
|
||||||
@ -341,8 +343,17 @@ func Restore(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
name := utils.GetName(r)
|
name := utils.GetName(r)
|
||||||
if _, err := runtime.LookupContainer(name); err != nil {
|
if _, err := runtime.LookupContainer(name); err != nil {
|
||||||
utils.ContainerNotFound(w, name, err)
|
// If container was not found, check if this is a checkpoint image
|
||||||
return
|
ir := abi.ImageEngine{Libpod: runtime}
|
||||||
|
report, err := ir.Exists(r.Context(), name)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find container or checkpoint image %s", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !report.Value {
|
||||||
|
utils.Error(w, http.StatusNotFound, errors.Errorf("failed to find container or checkpoint image %s", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
names = []string{name}
|
names = []string{name}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ type AttachOptions struct {
|
|||||||
// CheckpointOptions are optional options for checkpointing containers
|
// CheckpointOptions are optional options for checkpointing containers
|
||||||
type CheckpointOptions struct {
|
type CheckpointOptions struct {
|
||||||
Export *string
|
Export *string
|
||||||
|
CreateImage *string
|
||||||
IgnoreRootfs *bool
|
IgnoreRootfs *bool
|
||||||
Keep *bool
|
Keep *bool
|
||||||
LeaveRunning *bool
|
LeaveRunning *bool
|
||||||
|
@ -32,6 +32,21 @@ func (o *CheckpointOptions) GetExport() string {
|
|||||||
return *o.Export
|
return *o.Export
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCreateImage set field CreateImage to given value
|
||||||
|
func (o *CheckpointOptions) WithCreateImage(value string) *CheckpointOptions {
|
||||||
|
o.CreateImage = &value
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCreateImage returns value of field CreateImage
|
||||||
|
func (o *CheckpointOptions) GetCreateImage() string {
|
||||||
|
if o.CreateImage == nil {
|
||||||
|
var z string
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
return *o.CreateImage
|
||||||
|
}
|
||||||
|
|
||||||
// WithIgnoreRootfs set field IgnoreRootfs to given value
|
// WithIgnoreRootfs set field IgnoreRootfs to given value
|
||||||
func (o *CheckpointOptions) WithIgnoreRootfs(value bool) *CheckpointOptions {
|
func (o *CheckpointOptions) WithIgnoreRootfs(value bool) *CheckpointOptions {
|
||||||
o.IgnoreRootfs = &value
|
o.IgnoreRootfs = &value
|
||||||
|
@ -22,9 +22,7 @@ import (
|
|||||||
|
|
||||||
// Prefixing the checkpoint/restore related functions with 'cr'
|
// Prefixing the checkpoint/restore related functions with 'cr'
|
||||||
|
|
||||||
// CRImportCheckpoint it the function which imports the information
|
func CRImportCheckpointTar(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
|
||||||
// from checkpoint tarball and re-creates the container from that information
|
|
||||||
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
|
|
||||||
// First get the container definition from the
|
// First get the container definition from the
|
||||||
// tarball to a temporary directory
|
// tarball to a temporary directory
|
||||||
dir, err := ioutil.TempDir("", "checkpoint")
|
dir, err := ioutil.TempDir("", "checkpoint")
|
||||||
@ -39,7 +37,12 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
|
|||||||
if err := crutils.CRImportCheckpointConfigOnly(dir, restoreOptions.Import); err != nil {
|
if err := crutils.CRImportCheckpointConfigOnly(dir, restoreOptions.Import); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return CRImportCheckpoint(ctx, runtime, restoreOptions, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRImportCheckpoint it the function which imports the information
|
||||||
|
// from checkpoint tarball and re-creates the container from that information
|
||||||
|
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions, dir string) ([]*libpod.Container, error) {
|
||||||
// Load spec.dump from temporary directory
|
// Load spec.dump from temporary directory
|
||||||
dumpSpec := new(spec.Spec)
|
dumpSpec := new(spec.Spec)
|
||||||
if _, err := metadata.ReadJSONFile(dumpSpec, dir, metadata.SpecDumpFile); err != nil {
|
if _, err := metadata.ReadJSONFile(dumpSpec, dir, metadata.SpecDumpFile); err != nil {
|
||||||
@ -48,7 +51,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
|
|||||||
|
|
||||||
// Load config.dump from temporary directory
|
// Load config.dump from temporary directory
|
||||||
ctrConfig := new(libpod.ContainerConfig)
|
ctrConfig := new(libpod.ContainerConfig)
|
||||||
if _, err = metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil {
|
if _, err := metadata.ReadJSONFile(ctrConfig, dir, metadata.ConfigDumpFile); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,6 @@ func CRImportCheckpointConfigOnly(destination, input string) error {
|
|||||||
options := &archive.TarOptions{
|
options := &archive.TarOptions{
|
||||||
// Here we only need the files config.dump and spec.dump
|
// Here we only need the files config.dump and spec.dump
|
||||||
ExcludePatterns: []string{
|
ExcludePatterns: []string{
|
||||||
"volumes",
|
|
||||||
"ctr.log",
|
"ctr.log",
|
||||||
"artifacts",
|
"artifacts",
|
||||||
stats.StatsDump,
|
stats.StatsDump,
|
||||||
@ -62,6 +61,7 @@ func CRImportCheckpointConfigOnly(destination, input string) error {
|
|||||||
metadata.DeletedFilesFile,
|
metadata.DeletedFilesFile,
|
||||||
metadata.NetworkStatusFile,
|
metadata.NetworkStatusFile,
|
||||||
metadata.CheckpointDirectory,
|
metadata.CheckpointDirectory,
|
||||||
|
metadata.CheckpointVolumesDirectory,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err = archive.Untar(archiveFile, destination, options); err != nil {
|
if err = archive.Untar(archiveFile, destination, options); err != nil {
|
||||||
|
@ -28,6 +28,11 @@ func CheckForCriu(version int) bool {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCriuVestion() (int, error) {
|
||||||
|
c := criu.MakeCriu()
|
||||||
|
return c.GetCriuVersion()
|
||||||
|
}
|
||||||
|
|
||||||
func MemTrack() bool {
|
func MemTrack() bool {
|
||||||
features, err := criu.MakeCriu().FeatureCheck(
|
features, err := criu.MakeCriu().FeatureCheck(
|
||||||
&rpc.CriuFeatures{
|
&rpc.CriuFeatures{
|
||||||
|
@ -178,6 +178,7 @@ type ContainerExportOptions struct {
|
|||||||
type CheckpointOptions struct {
|
type CheckpointOptions struct {
|
||||||
All bool
|
All bool
|
||||||
Export string
|
Export string
|
||||||
|
CreateImage string
|
||||||
IgnoreRootFS bool
|
IgnoreRootFS bool
|
||||||
IgnoreVolumes bool
|
IgnoreVolumes bool
|
||||||
Keep bool
|
Keep bool
|
||||||
@ -205,6 +206,7 @@ type RestoreOptions struct {
|
|||||||
IgnoreStaticIP bool
|
IgnoreStaticIP bool
|
||||||
IgnoreStaticMAC bool
|
IgnoreStaticMAC bool
|
||||||
Import string
|
Import string
|
||||||
|
CheckpointImage bool
|
||||||
Keep bool
|
Keep bool
|
||||||
Latest bool
|
Latest bool
|
||||||
Name string
|
Name string
|
||||||
|
@ -563,6 +563,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
|
|||||||
Compression: options.Compression,
|
Compression: options.Compression,
|
||||||
PrintStats: options.PrintStats,
|
PrintStats: options.PrintStats,
|
||||||
FileLocks: options.FileLocks,
|
FileLocks: options.FileLocks,
|
||||||
|
CreateImage: options.CreateImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.All {
|
if options.All {
|
||||||
@ -592,8 +593,9 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
|
|||||||
|
|
||||||
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
||||||
var (
|
var (
|
||||||
cons []*libpod.Container
|
containers []*libpod.Container
|
||||||
err error
|
checkpointImageImportErrors []error
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
restoreOptions := libpod.ContainerCheckpointOptions{
|
restoreOptions := libpod.ContainerCheckpointOptions{
|
||||||
@ -619,17 +621,49 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case options.Import != "":
|
case options.Import != "":
|
||||||
cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options)
|
containers, err = checkpoint.CRImportCheckpointTar(ctx, ic.Libpod, options)
|
||||||
case options.All:
|
case options.All:
|
||||||
cons, err = ic.Libpod.GetContainers(filterFuncs...)
|
containers, err = ic.Libpod.GetContainers(filterFuncs...)
|
||||||
|
case options.Latest:
|
||||||
|
containers, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
||||||
default:
|
default:
|
||||||
cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
for _, nameOrID := range namesOrIds {
|
||||||
|
logrus.Debugf("lookup container: %q", nameOrID)
|
||||||
|
ctr, err := ic.Libpod.LookupContainer(nameOrID)
|
||||||
|
if err == nil {
|
||||||
|
containers = append(containers, ctr)
|
||||||
|
} else {
|
||||||
|
// If container was not found, check if this is a checkpoint image
|
||||||
|
logrus.Debugf("lookup image: %q", nameOrID)
|
||||||
|
img, _, err := ic.Libpod.LibimageRuntime().LookupImage(nameOrID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("no such container or image: %s", nameOrID)
|
||||||
|
}
|
||||||
|
restoreOptions.CheckpointImageID = img.ID()
|
||||||
|
mountPoint, err := img.Mount(ctx, nil, "")
|
||||||
|
defer img.Unmount(true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
importedContainers, err := checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options, mountPoint)
|
||||||
|
if err != nil {
|
||||||
|
// CRImportCheckpoint is expected to import exactly one container from checkpoint image
|
||||||
|
checkpointImageImportErrors = append(
|
||||||
|
checkpointImageImportErrors,
|
||||||
|
errors.Errorf("unable to import checkpoint from image: %q: %v", nameOrID, err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
containers = append(containers, importedContainers[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reports := make([]*entities.RestoreReport, 0, len(cons))
|
|
||||||
for _, con := range cons {
|
reports := make([]*entities.RestoreReport, 0, len(containers))
|
||||||
|
for _, con := range containers {
|
||||||
criuStatistics, runtimeRestoreDuration, err := con.Restore(ctx, restoreOptions)
|
criuStatistics, runtimeRestoreDuration, err := con.Restore(ctx, restoreOptions)
|
||||||
reports = append(reports, &entities.RestoreReport{
|
reports = append(reports, &entities.RestoreReport{
|
||||||
Err: err,
|
Err: err,
|
||||||
@ -638,6 +672,13 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
CRIUStatistics: criuStatistics,
|
CRIUStatistics: criuStatistics,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, importErr := range checkpointImageImportErrors {
|
||||||
|
reports = append(reports, &entities.RestoreReport{
|
||||||
|
Err: importErr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/containers/podman/v4/libpod/events"
|
"github.com/containers/podman/v4/libpod/events"
|
||||||
"github.com/containers/podman/v4/pkg/api/handlers"
|
"github.com/containers/podman/v4/pkg/api/handlers"
|
||||||
"github.com/containers/podman/v4/pkg/bindings/containers"
|
"github.com/containers/podman/v4/pkg/bindings/containers"
|
||||||
|
"github.com/containers/podman/v4/pkg/bindings/images"
|
||||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||||
"github.com/containers/podman/v4/pkg/domain/entities/reports"
|
"github.com/containers/podman/v4/pkg/domain/entities/reports"
|
||||||
"github.com/containers/podman/v4/pkg/errorhandling"
|
"github.com/containers/podman/v4/pkg/errorhandling"
|
||||||
@ -331,6 +332,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
|
|||||||
options.WithIgnoreRootfs(opts.IgnoreRootFS)
|
options.WithIgnoreRootfs(opts.IgnoreRootFS)
|
||||||
options.WithKeep(opts.Keep)
|
options.WithKeep(opts.Keep)
|
||||||
options.WithExport(opts.Export)
|
options.WithExport(opts.Export)
|
||||||
|
options.WithCreateImage(opts.CreateImage)
|
||||||
options.WithTCPEstablished(opts.TCPEstablished)
|
options.WithTCPEstablished(opts.TCPEstablished)
|
||||||
options.WithPrintStats(opts.PrintStats)
|
options.WithPrintStats(opts.PrintStats)
|
||||||
options.WithPreCheckpoint(opts.PreCheckPoint)
|
options.WithPreCheckpoint(opts.PreCheckPoint)
|
||||||
@ -396,8 +398,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
ids = []string{}
|
||||||
ctrs = []entities.ListContainer{}
|
|
||||||
)
|
)
|
||||||
if opts.All {
|
if opts.All {
|
||||||
allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{})
|
allCtrs, err := getContainersByContext(ic.ClientCtx, true, false, []string{})
|
||||||
@ -407,20 +408,42 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
// narrow the list to exited only
|
// narrow the list to exited only
|
||||||
for _, c := range allCtrs {
|
for _, c := range allCtrs {
|
||||||
if c.State == define.ContainerStateExited.String() {
|
if c.State == define.ContainerStateExited.String() {
|
||||||
ctrs = append(ctrs, c)
|
ids = append(ids, c.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctrs, err = getContainersByContext(ic.ClientCtx, false, false, namesOrIds)
|
getImageOptions := new(images.GetOptions).WithSize(false)
|
||||||
|
hostInfo, err := ic.Info(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, nameOrID := range namesOrIds {
|
||||||
|
ctrData, _, err := ic.ContainerInspect(ic.ClientCtx, []string{nameOrID}, entities.InspectOptions{})
|
||||||
|
if err == nil && len(ctrData) > 0 {
|
||||||
|
ids = append(ids, ctrData[0].ID)
|
||||||
|
} else {
|
||||||
|
// If container was not found, check if this is a checkpoint image
|
||||||
|
inspectReport, err := images.GetImage(ic.ClientCtx, nameOrID, getImageOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("no such container or image: %s", nameOrID)
|
||||||
|
}
|
||||||
|
checkpointRuntimeName, found := inspectReport.Annotations[define.CheckpointAnnotationRuntimeName]
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("image is not a checkpoint: %s", nameOrID)
|
||||||
|
}
|
||||||
|
if hostInfo.Host.OCIRuntime.Name != checkpointRuntimeName {
|
||||||
|
return nil, fmt.Errorf("container image \"%s\" requires runtime: \"%s\"", nameOrID, checkpointRuntimeName)
|
||||||
|
}
|
||||||
|
ids = append(ids, inspectReport.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reports := make([]*entities.RestoreReport, 0, len(ctrs))
|
reports := make([]*entities.RestoreReport, 0, len(ids))
|
||||||
for _, c := range ctrs {
|
for _, id := range ids {
|
||||||
report, err := containers.Restore(ic.ClientCtx, c.ID, options)
|
report, err := containers.Restore(ic.ClientCtx, id, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reports = append(reports, &entities.RestoreReport{Id: c.ID, Err: err})
|
reports = append(reports, &entities.RestoreReport{Id: id, Err: err})
|
||||||
}
|
}
|
||||||
reports = append(reports, report)
|
reports = append(reports, report)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user