I’m trying to understand the “optimized volume transfer” with zfs and whether it supports the use case I want.
Here’s a quick demo of the issue. nuc1 and nuc2 are both ubuntu 20.04 with incus 0.6-202403181632-ubuntu20.04 (Zabbly packages), and the default storage pool is zfs. These are independent (non-clustered) hosts.
On nuc1, I create and snapshot a container, and make two copies of it:
root@nuc1:~# incus init images:ubuntu/22.04 foobase
Creating foobase
root@nuc1:~# incus snapshot create foobase baseline
root@nuc1:~# time incus copy foobase foo1
real 0m10.368s
user 0m0.028s
sys 0m0.021s
root@nuc1:~# time incus copy foobase foo2
real 0m12.642s
user 0m0.025s
sys 0m0.021s
I expect foo1 and foo2 to be shallow copies sharing the same snapshot and underlying image; the fact that they use very little space seems to confirm this, and they do contain the same snapshot.
root@nuc1:~# zfs list zfs/lxd/containers/foobase
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foobase 208K 17.2G 293M legacy
root@nuc1:~# zfs list zfs/lxd/containers/foo1
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foo1 208K 17.2G 293M legacy
root@nuc1:~# zfs list zfs/lxd/containers/foo2
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foo2 208K 17.2G 293M legacy
root@nuc1:~# incus snapshot list foobase
+----------+----------------------+------------+----------+
| NAME | TAKEN AT | EXPIRES AT | STATEFUL |
+----------+----------------------+------------+----------+
| baseline | 2024/03/21 11:19 GMT | | NO |
+----------+----------------------+------------+----------+
root@nuc1:~# incus snapshot list foo1
+----------+----------------------+------------+----------+
| NAME | TAKEN AT | EXPIRES AT | STATEFUL |
+----------+----------------------+------------+----------+
| baseline | 2024/03/21 11:19 GMT | | NO |
+----------+----------------------+------------+----------+
root@nuc1:~# incus snapshot list foo2
+----------+----------------------+------------+----------+
| NAME | TAKEN AT | EXPIRES AT | STATEFUL |
+----------+----------------------+------------+----------+
| baseline | 2024/03/21 11:19 GMT | | NO |
+----------+----------------------+------------+----------+
root@nuc1:~# zfs get origin zfs/lxd/containers/foobase
NAME PROPERTY VALUE SOURCE
zfs/lxd/containers/foobase origin zfs/lxd/images/576b5965670cd19ffa17c34dc98dcc3320957423333620238a6fd78bb4d6d6f0@readonly -
root@nuc1:~# zfs get origin zfs/lxd/containers/foo1
NAME PROPERTY VALUE SOURCE
zfs/lxd/containers/foo1 origin zfs/lxd/images/576b5965670cd19ffa17c34dc98dcc3320957423333620238a6fd78bb4d6d6f0@readonly -
Now I want to be able to copy these to a remote host. I’m hoping that because they have a shared origin and/or snapshot in common, this would only copy the differences after copying the first.
root@nuc1:~# time incus copy foobase nuc2:
real 0m14.383s
user 0m0.153s
sys 0m0.057s
root@nuc1:~# time incus copy foo1 nuc2:
real 0m13.111s
user 0m0.138s
sys 0m0.060s
root@nuc1:~# time incus copy foo2 nuc2:
real 0m11.272s
user 0m0.165s
sys 0m0.025s
However, it appears that the containers were all copied in their entirety, rather than just the differences from the shared snapshot, since (a) the copy took a long time, and (b) they all use 293M of storage on the target:
root@nuc2:~# zfs list zfs/lxd/containers/foobase
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foobase 293M 135G 293M legacy
root@nuc2:~# zfs list zfs/lxd/containers/foo1
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foo1 293M 135G 293M legacy
root@nuc2:~# zfs list zfs/lxd/containers/foo2
NAME USED AVAIL REFER MOUNTPOINT
zfs/lxd/containers/foo2 293M 134G 293M legacy
root@nuc2:~# zfs get origin zfs/lxd/containers/foobase
NAME PROPERTY VALUE SOURCE
zfs/lxd/containers/foobase origin - -
root@nuc2:~# zfs get origin zfs/lxd/containers/foo1
NAME PROPERTY VALUE SOURCE
zfs/lxd/containers/foo1 origin - -
The use case is as follows. I want to pre-build a bunch of containers, say A, B, C, D, all cloned from the same image, on host X. Then I want to transfer them to host Y (where I will be building a fresh VM image which incorporates these containers), and I want to retain the shared copy-on-write base image.
I wondered if I first needed to transfer the underlying image:
root@nuc1:~# incus image copy 576b5965670c nuc2:
Image copied successfully!
… but repeating the experiment I got the same results.
Is there a way to achieve what I’m looking for? Or does the shared base image only work when doing local copies?
Thanks,
Brian.
EDIT: I discovered the --refresh
flag, and it works for incremental updates within the same instance, but it doesn’t seem to make any difference for clones with a shared snapshot or origin. First deleting the instances from nuc2:
root@nuc1:~# incus copy --refresh foobase nuc2:
root@nuc1:~# time incus copy --refresh foo1 nuc2:
real 0m25.822s
user 0m0.183s
sys 0m0.058s
root@nuc2:~# zfs list | grep zfs/lxd/containers/foo
zfs/lxd/containers/foo1 299M 134G 294M legacy
zfs/lxd/containers/foobase 299M 134G 294M legacy
EDIT 2: what if I pre-clone the target from the same image, and then try to refresh it?
root@nuc2:~# incus delete foobase foo1
root@nuc2:~# incus init 576b5965670c foobase
Creating foobase
root@nuc2:~# incus init 576b5965670c foo1
Creating foo1
root@nuc2:~#
root@nuc1:~# time incus copy --refresh foobase nuc2:
Error: Failed instance creation: Error transferring instance data: Failed migration on target: Failed creating instance on target: Failed receiving snapshot volume "foobase/baseline": Problem with zfs receive: ([exit status 1 write |1: broken pipe]) cannot receive new filesystem stream: destination 'zfs/lxd/containers/foobase' is a clone
must destroy it to overwrite it
real 0m0.654s
user 0m0.132s
sys 0m0.053s
Nope