Manchmal möchte man zusätzlich zum normalen System noch eine virtuelle Maschine
laufen lassen, was mit qemu ja auch schnell und einfach funktioniert. Soll dies
dauerhaft erfolgen muss qemu als Dienst laufen. Hier könnte man z.B. libvirt
verwenden oder man erzeugt pro VM einen Dienst was mit
runit auch sehr einfach ist. Allerdings führt ein
SIGTERM, was zum stoppen die Dienstes Verwendung findet, nicht zum
Herunterfahren der Maschine, sondern das Signal schaltet sie aus. Es gibt aber
über die Monitoring-Konsole von qemu die Möglichkeit ein passendes Signal
abzusetzen, so dass die VM normal herunter gefahren wird.
Damit das Signal zum Beenden nicht direkt bei qemu landet brauchen wir einen Wrapper, der angegebene Signale abfängt und die nötigen Aktionen ausführt. Dafür eignet sich eine Scriptsprache wie etwa Python. Hier ein Codebeispiel:
import time
import signal
class A:
def __init__(self):
self.loop = True
signal.signal(signal.SIGTERM, self.signal_handler)
while self.loop:
print("do some thing")
time.sleep(1)
print("stop")
def signal_handler(self, signum, frame):
if signum == 15:
print("get SIGTERM")
self.loop = False
Es gibt verschiedene Möglichkeiten unter python einen neuen Prozess zu erzeugen,
in dem Beispiel hier verwende ich pexpect
. Damit kann man auch mit dem Prozess
sprechen und auf entsprechende Ausgaben reagieren, was wir hier nicht benötigen.
Um einen neuen Kindprozess zu erstellen reicht dabei ein einfaches child =
pexpect.spawn(cmd)
wobei in cmd
das Programm mit all seinen Parametern
steht. Mit child.isalive()
kann überprüft werden ob der Prozess noch läuft.
Um mit qemu während der Laufzeit sprechen zu können nutzen wir den Parameter
-monitor telnet:127.0.0.1:Port,server,nowait
wobei Port durch einen freien
TPC-Port zu ersetzen ist.
Über diesen Port können wir nun Kontakt zu der qemu Instanz aufnehmen. Das
geht über telnet
, nc
oder eine Socket-Verbindung. Um nicht weitere
Abhängigkeiten in das Projekt zu ziehen, nutzen wir die Möglichkeiten des
socket
Modules.
import sys
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", port))
s.send(b"system_powerdown\n")
state = 1
for t in range(30):
if child.isalive()
time.sleep(1)
else:
state = child.exitstatus
break
sys.exit(state)
Damit senden wir das system_powerdown
Signal an dem Monitor und warten für
eine vorgegebene Zeit auf das Prozessende und geben den Status zurück.
Damit der VM-Dienst unter runit laufen kann, erstellen wir die passenden Verzeichnisse und Dateien, z.B.
mkdir -p /etc/sv/vm1
cd /etc/sv/vm1/
ln -s /run/runit/supervise.vm1 supervise
touch run
chmod -x run
Der Inhalt von run ist dann z.B.:
#!/bin/sh
exec chpst -u jack:users /usr/local/bin/qemu_sv.py /home/jack/kvm/vm1.qcow2 2>&1
Damit würde die VM mit den Rechten von jack
laufen und das Image im
angegebenen Pfad verwenden.
Um den Dienst zu aktivieren reicht als root ein ln -s /etc/sv/vm1 /var/service
.
Für alle die nicht selbst Scripten wollen gibt es hier den Link dazu.
Alternativ kann man natürlich auch ein kleines Programm dafür schreiben, z.B.
in nim, wenn man das dann noch statisch baut (unter
musl libc) mit nim c -d:release --opt:size --passL:-static qemu_sv.nim
, lässt
es sich auch ohne weitere Abhängigkeiten verteilen.
Hier
gibt es den Quellcode dafür.
Um einen vollständigen Netzzugang der VM zu ermöglichen, bietet es sich an die
Netzwerkkarte im bridged
Modus zu betreiben und die Nutzung qemu über die
Datei /etc/qemu/bridge.conf
zu erlauben.
$ cat /etc/qemu/bridge.conf
allow br0
Nun kann z.B. über die Datei /etc/rc.local
die Brücke mit
ip link add name br0 type bridge
ip link set br0 up
ip link set eth0 up
ip link set eth0 master br0
aufgebaut werden.
Es wäre sicherlich sinnvoll die Parameter für qemu aus einer Datei zu lesen und diese im run-Script mit anzugeben. Ich freue mich über Rückmeldungen.