Citrix XEN Server -> KVM/QEMU

Für die Arbeit konvertiere ich gerade eine Menge Citrix XEN Server VMs auf umgänglichere (OpenSource) Systeme. Und weil unsere Anforderungen sehr homogen sind, wir eigentlich nur Ressourcen trennen wollen und keine echten Security-Container benötigen, migriere ich fast alles in LXC-Container.

Für eins der Systeme - ein 32-Bit Debian Squeeze - klappt das leider nicht, denn das ist aus verschiedenen Gründen schwer auf ein modernes System migrierbar. Wir entschieden uns damals für eine VM, um keinen Ärger mehr mit ausfallender Hardware zu haben - stattdessen haben wir jetzt Bitrot auf proprietärem Citrix-Bloat - auch nicht schön. Um echte Hardwareemulation kommen wir aber nicht herum, deshalb ist das nächste Mittel der Wahl ein KVM/QEMU Host.

Der Task ist nun also, diese VM möglichst ohne Neuinstallation und möglichst geringer Downtime von Citrix XEN-Server nach KVM/QEMU zu migrieren. Ich hab dazu im Netz leider nichts gefunden, deshalb schreib ich es in mein Blog. Ich muss dazu noch sagen, dass ich neu bin bei beiden Systemen, also keine Erfahrung damit habe. Es klappte aber trotzdem.

Zunächst muss man die VM mal exportieren. Beim ersten Versuch verwendete ich dazu xe-export und exportierte in ein .img, später verwendete ich einfach .hva Snapshots aus unseren Backups. Ich weiss nicht ob es einen Unterschied gibt, für mich sieht das jedenfalls ziemlich gleich aus.

Wie erwartet lässt sich keins der Files "einfach so" mit QEMU verwenden. Was ist das also?

# file ./image.hva
image.hva: tar archive

Oha, ein TAR-File. Ich hätte jetzt mehr einen Binaryblob erwartet, aber gut tar xfv ./image.hva entpackt es. Heraus kommen ein Configfile im xml-Format, und ein Verzeichnis mit tausenden einzelnen Files mit Daten drin. Fühlt sich jetzt fast an wie Pythonchallenge. Apropos Python, im Netz geistert tatsächlich ein Pythonscript herum, dass diese Einzelfiles zusammensetzt. Leider bugged es (bei mir), und ich kann es nicht reparieren, weil ich kein Python kann. Es kann aber nicht viel tun, denn diese Files sind - wie sich nach genauerem Hinsehen herausstellt - einfach durchnummerierte 1M große Chunks eines Abbilds eines Blockdevices mit Lücken - vermutlich eine Art Sparse-Image Support von Citrix. Wie auch immer, ein kleines Shellscript bringt das in Ordnung:

#!/bin/bash

> image.raw
top=`ls -1 ???????? | sort | tail -1`

for i in $(seq 0 $top); do
    file=$(printf "%08d" $i)
    echo "processing $file of $top"

    if [ -f $file ]; then
        cat $file >> image.raw
    else
        dd if=/dev/zero of=image.raw bs=1M count=1 conv=notrunc oflag=append 2>/dev/null
    fi
done

Heraus kommt...

# fdisk -l image.raw

Disk image.raw: 8589 MB, 8589934592 bytes
255 heads, 63 sectors/track, 1044 cylinders, total 16777216 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000425e0

    Device Boot      Start         End      Blocks   Id  System
image.raw1   *        2048    15988735     7993344   83  Linux
image.raw2        15990782    16775167      392193    5  Extended
image.raw5        15990784    16775167      392192   82  Linux swap / Solaris

Tatsächlich ein Image mit Partitionstabelle! Eine Unit = 512 Bytes, Start 2048, also startet die Root-Partition bei Byte 1048576. Mal mounten...

# sudo mount -o loop,offset=1048576 image.raw /mnt 
# ls /mnt
bin   data  etc   initrd.img      lib         media  opt   root  selinux  sys  usr  vmlinuz
boot  dev   home  initrd.img.old  lost+found  mnt    proc  sbin  srv      tmp  var  vmlinuz.old

So gut wie befreit. :-)

Dummerweise ist da ein XEN-Kernel drauf, der eine XEN dom0 braucht und mit QEMU nicht booten mag. Deshalb neuen Kernel installieren:

# sudo chroot /mnt
# sudo apt-get update
# sudo apt-get install linux-image-2.6-486

Okay, das war jetzt dreckig. Die Grub-Config ist auch zerschossen. Ich muss gestehen, ich kenn mich auch mit Grub nicht besonders aus - ich muss den selten anfassen, und Debian verwende ich auch nicht so oft. Mein Plan ist nun einfach, das System in der chroot *irgendwie* boot-fähig zu konfigurieren, und später, wenn /proc und /sys wieder da sind, den Grub sauber zu installieren. Deshalb editiere ich die /boot/grub/grub.cfg direkt, lösche dort die Einträge für die alten XEN-Kernel raus und repariere die Parameter für den neuen Kernel:

(...)
### BEGIN /etc/grub.d/10_linux ###
menuentry 'Debian GNU/Linux, with Linux 2.6.32-5-486' --class debian --class gnu-linux --class gnu --class os {
        insmod ext2
        set root='(hd0,0)'
        search --no-floppy --fs-uuid --set ebdf03dd-2468-4872-a4ea-6e2277d45987
        echo        'Loading Linux 2.6.32-5-486 ...'
        linux        /boot/vmlinuz-2.6.32-5-486 root=/dev/sda1 ro console=ttyS0,115200n8 console=tty0 
        echo        'Loading initial ramdisk ...'
        initrd        /boot/initrd.img-2.6.32-5-486
}
(...)

Dass die Platte /dev/sda sein wird, hab ich den Kernel-Bootmessages beim ersten Fehlversuch entnommen. Apropos Bootmessages: Ich muss QEMU im -nographic Modus starten, weil ich gerade kein X hier habe (und vermutlich später auch keins haben werde), schreibt der Kernel auf die serielle Console (console=ttyS0,115200n8), die QEMU emuliert. Die Ausgabe landet dann direkt im aufrufenden Terminal.

getty muss mir ausserdem ein Terminal auf /dev/ttyS0 aufmachen, sonst kann ich mich nicht einloggen, denn das Netzwerk wird nicht funktionieren. Weil auf diesem Debian noch ein SystemV init.d läuft, brauch' ich keinen Upstart-Job, sondern es reicht ein Eintrag in der guten alten /etc/inittab:

(...)
s0:2345:respawn:/sbin/getty -L 115200 ttyS0 vt102
(...)

Jetzt die chroot schließen, /mnt unmounten, qemu starten:

# sudo kvm -hda ./image.raw -nographic 
(Stille ... Kernel ... Stille)

Debian GNU/Linux 6.0 sup ttyS0

sup login: 

:-)

Das System läuft - nur halt ohne Netz. Das muss ich jetzt fixen (und danach den Grub reparieren), aber vermutlich ist das irgendwie straightforward Bridging oder so. Wenn es sich lohnt das zu bloggen, mach ich das später noch.

Anm.d.Red.: Hab ich nie. Es war aber tatsächlich nicht so wild. Weil wir Bonding und VLANs brauchen, hatte ich auf dem Host-System manuell Bonding, VLANs und Bridges für jedes VLAN konfiguriert, und kvm per libvirt so konfiguriert, dass diese Bridges in die virtuelle Maschine gemappt werden. Genau so klappt es auch mit LXC-Containern recht gut.

Tags: Citrix XEN, KVM, QEMU