Migrate Cinder volumes between two OpenStack clouds without copying data
When a legacy OpenStack (for example Rocky) and its modern replacement share the same Ceph cluster, volumes do not need to move: the legacy Cinder forgets them (unmanage) and the new Cinder adopts the existing RBD images (manage). The data never leaves Ceph. This guide covers the openstack CLI, the legacy cinder CLI and the Ansible equivalent, plus the snapshot/export fallback for isolated backends.
1. How manage / unmanage works
Cinder keeps a database record for each volume and a backing object on the storage backend (for Ceph, an RBD image named volume-UUID). Unmanage deletes only the record: the RBD image stays untouched. Manage does the reverse: it creates a new record pointing at an existing RBD image. Combined, the two calls transfer ownership of a volume from one Cinder to another with zero bytes copied.
Naming trap in the openstack CLI
The unified openstack CLI has no volume manage or volume unmanage subcommands. To remove a volume from Cinder management without deleting its backend data, use openstack volume delete --remote. To manage a volume that already exists on a backend, use openstack volume create --remote-source with either --host or --cluster. These operations require administrator privileges. The dedicated cinder CLI provides the more explicit cinder manage and cinder unmanage commands and remains documented by OpenStack.
2. Prepare the source instance
A volume can only be unmanaged when its status is available. Stop the instance, record everything you will need to rebuild it on the target (flavor, networks, fixed IPs, security groups, metadata, volume order), then release the volumes. The boot volume must not be deleted with the server: check delete_on_termination before deleting the instance.
# Legacy cloud (source credentials loaded)
openstack server stop <VM_NAME>
openstack server show <VM_NAME> -f json > <VM_NAME>.source.json # flavor, addresses, volumes_attached, metadata
openstack port list --server <VM_NAME> -f json > <VM_NAME>.ports.json
# Detach data volumes; make sure the boot volume survives the server deletion
openstack server remove volume <VM_NAME> <DATA_VOLUME_ID>
openstack server volume list <VM_NAME> # delete_on_termination must be False on the boot volume
openstack server delete --wait <VM_NAME>
# Every volume to migrate must now be "available"
openstack volume list --status available
3. Unmanage on the legacy cloud
Record the volume UUID and inventory its snapshots first. If snapshots must be preserved, unmanage each snapshot before the parent volume and keep its backend reference. After the calls, the records disappear from the legacy Cinder while the RBD objects remain in the pool.
# Legacy OpenStack: unmanage the volume — Cinder forgets it,
# the RBD image stays untouched in Ceph (this is not a data deletion)
openstack volume snapshot list --volume <VOLUME_ID>
openstack volume snapshot delete --remote <SNAPSHOT_ID> # repeat for each snapshot first
openstack volume delete --remote <VOLUME_ID>
# legacy cinder CLI equivalent: cinder unmanage <VOLUME_ID>
# Proof: the image is still there
rbd -p <CEPH_POOL> ls | grep volume-<VOLUME_ID>
rbd -p <CEPH_POOL> info volume-<VOLUME_ID>
4. Manage on the new cloud
The --host argument is the Cinder backend that owns the pool, in the form host@backend#pool as shown by openstack volume service list. Cinder assigns a new UUID and the RBD driver renames the image to volume-NEW_ID. The volume lands in the project of the credentials you use: run the command with the tenant's project, or transfer it afterwards.
# New OpenStack (target credentials loaded, ideally scoped to the tenant project)
openstack volume service list # find the cinder-volume host, e.g. new-cinder-host@rbd
openstack block storage volume manageable list new-cinder-host@rbd#rbd # lists RBD images not yet managed
# Adopt the existing RBD image as a Cinder volume
openstack volume create --remote-source source-name=volume-<VOLUME_ID> \
--host new-cinder-host@rbd#rbd --type <VOLUME_TYPE> <VM_NAME>_disk
# legacy cinder CLI equivalent: cinder manage --id-type source-name \
# --name <VM_NAME>_disk --volume-type <VOLUME_TYPE> \
# new-cinder-host@rbd#rbd volume-<VOLUME_ID>
# Restore what manage does not carry over: bootable flag and image metadata for the boot volume
openstack volume set --bootable <NEW_VOLUME_ID>
openstack volume set --image-property hw_disk_bus=virtio --image-property hw_vif_model=virtio \
--image-property os_type=linux <NEW_VOLUME_ID>
# Rebuild the instance on the target cloud, reusing the recorded fixed IP if continuity requires it
openstack port create --network <NET_ID> --fixed-ip ip-address=<OLD_FIXED_IP> <VM_NAME>-port
openstack server create --volume <NEW_VOLUME_ID> --flavor <FLAVOR> \
--port <VM_NAME>-port --security-group <SG> <VM_NAME>
5. Ansible equivalent
The openstack.cloud collection exposes both operations through one module. state: absent with the volume ID performs the unmanage; state: present with source_name and host performs the manage. Unmanage is asynchronous on the Cinder side, hence the retry loop.
- name: Release Cinder volumes on the legacy cloud (unmanage, RBD images untouched)
openstack.cloud.volume_manage:
cloud: "{{ cloud_source }}"
name: "{{ item.id }}" # must be the Cinder volume ID when state is absent
state: absent
loop: "{{ migration_volumes }}"
register: unmanage_result
retries: 3
delay: 3
until: unmanage_result is succeeded
- name: Adopt the same RBD images on the target cloud (manage)
openstack.cloud.volume_manage:
cloud: "{{ cloud_target }}"
name: "{{ item.name }}"
source_name: "volume-{{ item.id }}"
host: "{{ cinder_target_host }}" # e.g. new-cinder-host@rbd#rbd
volume_type: "{{ item.volume_type }}"
bootable: "{{ item.bootable }}"
state: present
loop: "{{ migration_volumes }}"
6. Fallback: snapshot, clone and export
When the two clouds do not share a storage backend, data has to move. Snapshot the volume, clone the snapshot into a volume, upload that volume as a Glance image, transfer the image (Glance-to-Glance or through S3), then create a volume from it on the target. Note that openstack image create --volume takes a volume, not a snapshot.
# Legacy cloud: snapshot, clone to a volume, then upload the volume as a Glance image
openstack volume snapshot create --volume <OLD_VOL_ID> snap-mig
openstack volume create --snapshot snap-mig vol-mig
openstack image create --volume vol-mig img-mig-<VM_NAME>
openstack image save --file img-mig-<VM_NAME>.raw img-mig-<VM_NAME>
# New cloud: import the image and boot from a volume
openstack image create --file img-mig-<VM_NAME>.raw --disk-format raw --container-format bare img-mig-<VM_NAME>
openstack volume create --image img-mig-<VM_NAME> --size <SIZE_GB> <VM_NAME>_disk
openstack server create --volume <VM_NAME>_disk --flavor <FLAVOR> --network <NET_ID> <VM_NAME>
7. Pitfalls to check first
- ■Both Cinder services must reach the same Ceph pool with a client key that has rwx on it; test with a throw-away volume before touching production data.
- ■Volume snapshots are separate Cinder resources. To preserve them, unmanage them before the parent volume, then manage the volume on the target before its snapshots with openstack volume snapshot create --volume TARGET_VOLUME --remote-source source-name=BACKEND_SNAPSHOT SNAPSHOT_NAME.
- ■Quotas apply on the target: a managed volume counts against the project's gigabytes and volumes quota.
- ■Keep a mapping table old UUID → new UUID → RBD image name; monitoring, backup jobs and CMDB entries reference volume IDs.
- ■Test rollback with a disposable volume. Before target-side writes, rollback consists of unmanaging target snapshots, then the target volume, and managing the volume followed by its snapshots back on the source. After writes resume, use an application-consistent data replication or restore plan instead of assuming metadata reversal is sufficient.