Include named volumes in container migration

When migrating a container with associated volumes, the content of
these volumes should be made available on the destination machine.

This patch enables container checkpoint/restore with named volumes
by including the content of volumes in checkpoint file. On restore,
volumes associated with container are created and their content is
restored.

The --ignore-volumes option is introduced to disable this feature.

Example:

 # podman container checkpoint --export checkpoint.tar.gz <container>

The content of all volumes associated with the container are included
in `checkpoint.tar.gz`

 # podman container checkpoint --export checkpoint.tar.gz --ignore-volumes <container>

The content of volumes is not included in `checkpoint.tar.gz`. This is
useful, for example, when the checkpoint/restore is performed on the
same machine.

 # podman container restore --import checkpoint.tar.gz

The associated volumes will be created and their content will be
restored. Podman will exit with an error if volumes with the same
name already exist on the system or the content of volumes is not
included in checkpoint.tar.gz

 # podman container restore --ignore-volumes --import checkpoint.tar.gz

Volumes associated with container must already exist. Podman will not
create them or restore their content.

Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
Radostin Stoyanov
2020-12-18 20:07:08 +00:00
parent 2b35876c8d
commit 288ccc4c84
10 changed files with 116 additions and 4 deletions

View File

@ -57,6 +57,7 @@ func init() {
_ = checkpointCommand.RegisterFlagCompletionFunc(exportFlagName, completion.AutocompleteDefault)
flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting")
flags.BoolVar(&checkpointOptions.IgnoreVolumes, "ignore-volumes", false, "Do not export volumes associated with container")
validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest)
}
@ -68,6 +69,9 @@ func checkpoint(cmd *cobra.Command, args []string) error {
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
return errors.Errorf("--ignore-rootfs can only be used with --export")
}
if checkpointOptions.Export == "" && checkpointOptions.IgnoreVolumes {
return errors.Errorf("--ignore-volumes can only be used with --export")
}
responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions)
if err != nil {
return err

View File

@ -62,6 +62,7 @@ func init() {
flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint")
flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip")
flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address")
flags.BoolVar(&restoreOptions.IgnoreVolumes, "ignore-volumes", false, "Do not export volumes associated with container")
validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest)
}
@ -73,6 +74,9 @@ func restore(_ *cobra.Command, args []string) error {
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
return errors.Errorf("--ignore-rootfs can only be used with --import")
}
if restoreOptions.Import == "" && restoreOptions.IgnoreVolumes {
return errors.Errorf("--ignore-volumes can only be used with --import")
}
if restoreOptions.Import == "" && restoreOptions.Name != "" {
return errors.Errorf("--name can only be used with --import")
}

View File

@ -52,6 +52,12 @@ exported to a tar.gz file it is possible with the help of **--ignore-rootfs**
to explicitly disable including changes to the root file-system into
the checkpoint archive file.
#### **--ignore-volumes**
This option must be used in combination with the **--export, -e** option.
When this option is specified, the content of volumes associated with
the container will not be included into the checkpoint tar.gz file.
## EXAMPLE
podman container checkpoint mywebserver

View File

@ -85,6 +85,13 @@ exported checkpoint with **--name, -n**.
Using **--ignore-static-mac** tells Podman to ignore the MAC address if it was
configured with **--mac-address** during container creation.
#### **--ignore-volumes**
This option must be used in combination with the **--import, -i** option.
When restoring containers from a checkpoint tar.gz file with this option,
the content of associated volumes will not be restored.
## EXAMPLE
podman container restore mywebserver

View File

@ -703,6 +703,9 @@ type ContainerCheckpointOptions struct {
// important to be able to restore a container multiple
// times with '--import --name'.
IgnoreStaticMAC bool
// IgnoreVolumes tells the API to not export or not to import
// the content of volumes associated with the container
IgnoreVolumes bool
}
// Checkpoint checkpoints a container

View File

@ -791,8 +791,8 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
}
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
if (len(c.config.NamedVolumes) > 0) || (len(c.Dependencies()) > 0) {
return errors.Errorf("Cannot export checkpoints of containers with named volumes or dependencies")
if len(c.Dependencies()) > 0 {
return errors.Errorf("Cannot export checkpoints of containers with dependencies")
}
logrus.Debugf("Exporting checkpoint image of container %q to %q", c.ID(), options.TargetFile)
@ -870,6 +870,47 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
}
}
// Folder containing archived volumes that will be included in the export
expVolDir := filepath.Join(c.bundlePath(), "volumes")
// Create an archive for each volume associated with the container
if !options.IgnoreVolumes {
if err := os.MkdirAll(expVolDir, 0700); err != nil {
return errors.Wrapf(err, "error creating volumes export directory %q", expVolDir)
}
for _, v := range c.config.NamedVolumes {
volumeTarFilePath := filepath.Join("volumes", v.Name+".tar")
volumeTarFileFullPath := filepath.Join(c.bundlePath(), volumeTarFilePath)
volumeTarFile, err := os.Create(volumeTarFileFullPath)
if err != nil {
return errors.Wrapf(err, "error creating %q", volumeTarFileFullPath)
}
volume, err := c.runtime.GetVolume(v.Name)
if err != nil {
return err
}
input, err := archive.TarWithOptions(volume.MountPoint(), &archive.TarOptions{
Compression: archive.Uncompressed,
IncludeSourceDir: true,
})
if err != nil {
return errors.Wrapf(err, "error reading volume directory %q", v.Dest)
}
_, err = io.Copy(volumeTarFile, input)
if err != nil {
return err
}
volumeTarFile.Close()
includeFiles = append(includeFiles, volumeTarFilePath)
}
}
input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{
Compression: archive.Gzip,
IncludeSourceDir: true,
@ -898,6 +939,10 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
os.Remove(rootfsDiffPath)
os.Remove(deleteFilesList)
if !options.IgnoreVolumes {
os.RemoveAll(expVolDir)
}
return nil
}
@ -1193,6 +1238,30 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return err
}
// When restoring from an imported archive, allow restoring the content of volumes.
// Volumes are created in setupContainer()
if options.TargetFile != "" && !options.IgnoreVolumes {
for _, v := range c.config.NamedVolumes {
volumeFilePath := filepath.Join(c.bundlePath(), "volumes", v.Name+".tar")
volumeFile, err := os.Open(volumeFilePath)
if err != nil {
return errors.Wrapf(err, "Failed to open volume file %s", volumeFilePath)
}
defer volumeFile.Close()
volume, err := c.runtime.GetVolume(v.Name)
if err != nil {
return errors.Wrapf(err, "Failed to retrieve volume %s", v.Name)
}
mountPoint := volume.MountPoint()
if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil {
return errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint)
}
}
}
// Before actually restarting the container, apply the root file-system changes
if !options.IgnoreRootfs {
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")

View File

@ -275,6 +275,7 @@ func Restore(w http.ResponseWriter, r *http.Request) {
Import bool `schema:"import"`
Name string `schema:"name"`
IgnoreRootFS bool `schema:"ignoreRootFS"`
IgnoreVolumes bool `schema:"ignoreVolumes"`
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
}{

View File

@ -54,6 +54,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
"rootfs-diff.tar",
"network.status",
"deleted.files",
"volumes",
},
}
dir, err := ioutil.TempDir("", "checkpoint")
@ -83,8 +84,21 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
}
// This should not happen as checkpoints with these options are not exported.
if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
if len(config.Dependencies) > 0 {
return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies")
}
// Volumes included in the checkpoint should not exist
if !restoreOptions.IgnoreVolumes {
for _, vol := range config.NamedVolumes {
exists, err := runtime.HasVolume(vol.Name)
if err != nil {
return nil, err
}
if exists {
return nil, errors.Errorf("volume with name %s already exists. Use --ignore-volumes to not restore content of volumes", vol.Name)
}
}
}
ctrID := config.ID

View File

@ -173,6 +173,7 @@ type CheckpointOptions struct {
All bool
Export string
IgnoreRootFS bool
IgnoreVolumes bool
Keep bool
Latest bool
LeaveRunning bool
@ -187,6 +188,7 @@ type CheckpointReport struct {
type RestoreOptions struct {
All bool
IgnoreRootFS bool
IgnoreVolumes bool
IgnoreStaticIP bool
IgnoreStaticMAC bool
Import string

View File

@ -487,6 +487,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
TCPEstablished: options.TCPEstablished,
TargetFile: options.Export,
IgnoreRootfs: options.IgnoreRootFS,
IgnoreVolumes: options.IgnoreVolumes,
KeepRunning: options.LeaveRunning,
}
@ -525,6 +526,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
TargetFile: options.Import,
Name: options.Name,
IgnoreRootfs: options.IgnoreRootFS,
IgnoreVolumes: options.IgnoreVolumes,
IgnoreStaticIP: options.IgnoreStaticIP,
IgnoreStaticMAC: options.IgnoreStaticMAC,
}