Qemu als runit Service

von Markus Berger am Montag, 7. September 2020

Einleitung

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.

Abfangen der Signale

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

Child Prozess forken

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.

Kommando an die Monitorkonsole senden

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.

Das runit Script

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.

Das Script

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.

Bridge Setup

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.

Verbesserungen

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.