Read the latest cluster, wherever it lives
Disk offsets are split into 4 KiB clusters by default. An in-memory index records the latest overlay record for each changed cluster. Reads consult that index; writes append a new version instead of changing the base.
- There is no index entry for this cluster. Read the requested bytes from the base at the same logical offset.
- Read the current cluster from the latest overlay record or the base. Replace only the byte range the guest changed; preserve the rest.
- Write the cluster number and complete cluster data to the end of the overlay. Point the in-memory index at this newest record.
- The index now hits. Read the requested bytes from the new overlay record; the original base still has its original contents.
What crosses the virtio queue?
A block request has a 16-byte header, one or more data buffers, and a one-byte status. Its sector field uses 512-byte units, independently of the overlay's cluster size. A filesystem block is yet another layer; do not assume all three sizes are identical.
- Read: fill guest-writable data buffers from the backing store.
- Write: consume guest-provided data buffers and update the backing store.
- Flush: ask storage to synchronize, rather than transferring a payload.
- Get ID / unsupported requests: return the supported identification response or an unsupported status.
The device checks request layout, buffer direction and capacity bounds. It returns OK, I/O error or unsupported status, publishes the used descriptor chain, and notifies the guest as required by the queue's event state. A read-only device advertises that property and rejects writes.
The overlay is a log, not a second filesystem
The header stores a format magic, version, cluster size and logical disk size. Records contain a cluster number followed by that cluster's bytes. Opening an existing overlay scans its records to rebuild the latest-record index.
Repeated writes to the same cluster append repeated versions. This saves the cost of copying an entire base for each VM, but does not mean overlay growth is bounded by the amount of unique changed data. A small guest write can cause a full-cluster read/merge/append.
Other backing stores
Block specifications also support local files, read-only HTTP(S) storage, and mem:BYTES RAM disks. A plain writable -block image changes in place; append :ro for read-only file/memory specs. CoW explicitly separates the shared base from each VM's writes.
Give each VM its own overlay
With a daemon running, attach a base and a private overlay using the convenience flag:
./bin/kube-vmctl start --uid worker-a --detach \
--kernel ./bzImage \
--cmdline "console=hvc0 root=/dev/vda rw" \
--cow-block ./disks/base.img=./disks/worker-a.overlay
The canonical block specification is cow:/path/base.img?overlay=/path/vm.overlay. The CLI encodes --cow-block BASE=OVERLAY into that form. Booting without an initrd requires the root filesystem and block drivers to be built into the kernel.
Measure guest-visible I/O
The API and dashboard report per-disk operations, transferred bytes, errors, in-flight work and completion latency. Those metrics describe guest requests, not host physical I/O or guest filesystem free space; CoW write amplification is not included.