Stellen wir uns vor, ich würde gerne folgende Sachen tun:
- Ich möchte ein paar Dienste in meinem privaten Netzwerk laufen lassen (das ich auch bei mir auf dem Freifunkrouter konfiguriert habe, Tutorial dazu später)
- Ich möchte aber gleichzeitig auch Dienste für das Freifunknetz anbieten.
Natürlich könnte man dafür sich zwei separate Geräte hinstellen… Aber eigentlich wäre das Ressourcen-Verschwendung. Schließlich verbraucht selbst ein kleiner Computer schon im Leerlauf rund 40 bis 50% seiner Nennleistung. Allerdings ohne dabei nennenswert Arbeit zu verrichten. Es ist also wünschenswert, dass wir nur eine Maschine hinstellen und die dafür auch ordentlich beschäftigen.
Damit die Sicherheit nicht zu kurz kommt, wäre es aber schön, dass die Anwendungen auf dem gemeinsamen Rechner nichts voneinander wissen und ich sie auch sonst sauber zwischen Freifunknetz und privatem Netzwerk trennen kann.
In diesem Blogpost werde ich eine Lösung vorstellen, die Virtuelle Maschinen nutzt. Diese werden wir über VLANs in verschiedene, voneinander getrennte Netzwerke zuordnen. Physisch betrachtet geht dabei alles über das gleiche Kabel. :D
Hardware
Ein Freifunker-Freund hatte mich auf den Wyse 5070 Thin-Client von Dell aufmerksam gemacht. Im Oktober 2022 gab es den ziemlich günstig, für rund 85€, auf Ebay zu haben. Ausgestattet mit Quad-Core-CPU, 8 GiB RAM, und 64 GiB SSD (und das alles auch noch lautlos, passiv gekühlt) kann man über den Preis überhaupt nicht meckern. Dadurch, dass die CPU die Virtualisierungserweiterungen des Befehlssatzes unterstützt, kann man das Gerät auch gut als Mikro-Server für unsere Zwecke einsetzen.
Software
Bei der Auswahl des Host-Betriebssystems kamen mehrere Möglichkeiten in Frage (eigentlich fast jedes Linux). Mir war bei der Distro-Wahl vor allem wichtig, dass der Host keinen überflüssigen Schnick-Schnack hat. Außer der Virtualisierungssoftware, am besten so wenig wie möglich, um die Performance nicht zu beeinträchtigen.
Ich habe gehört, dass Alpine-Linux ganz toll sein soll. Vor allem ist es klein: In Docker-Containern wird es deswegen ganz gerne als Basis verwendet. Ich wollte das sowieso mal ausprobieren und so fiel mir die Wahl relativ leicht.
Die Installation ging recht einfach von der Hand. Dafür gibt es genug Tutorials im Internet, sodass ich das hier nicht weiter ausführen werde.
Man sollte als nicht-Alpine-Profi vor allem beachten, dass die meisten spannenden Pakete aus dem Community-Feed kommen. Dieser muss allerdings erst unter /etc/apk/repositories
auskommentiert werden, damit er aktiv wird. Falls du dich also wunderst, warum du so gut wie gar keine Pakete findest, weiß’te nun Bescheid. (Die Info hat mich übrigens länger als 10 Minuten googlen gekostet. Scheinen in der Alpine-Community alle Member offensichtlich zu finden…)
Nachdem das System erstmal grundlegend eingerichtet ist (siehe setup-alpine
script), bietet es sich an, gleich die ganzen Virtualisierungssachen zu installieren.
KVM und libvirtd installieren
Für mein Setup möchte ich eine etwas angenehmere (graphische) Administrationsschnittstelle haben. Deshalb sind in der folgenden Liste noch ein paar zusätzliche Pakete drin, auf die man vielleicht auch verzichten könnte, wenn man es noch schlanker haben möchte.
sudo apk add bridge dbus libvirt-client libvirt-daemon openrc polkit \
py3-libvirt py3-libxml2 qemu-img qemu-system-x86_64 qemu-modules \
terminus-font virt-manager virt-install vlan
Damit das ganze dann so richtig schön wuppt, müssen nur noch ein paar Kleinigkeiten erledigt werden. Zuerst fügen wir unseren Nutzer mit
sudo addgroup $USER libvirt
der libvirt-Gruppe hinzu. Dadurch kann man mit diesem Nutzer VMs verwalten.
Anschließend noch ein paar Dienste anschalten:
sudo rc-update add libvirtd
sudo rc-update add dbus
sudo rc-update add libvirt-guests
Netzwerk-Einrichtung auf Alpine-Host
KVM hat als Standard-Netzwerk-Setup ein separates Subnetz für die VMs, das durch NAT über den Host-Netzwerkadapter geht.
Das ist zwar nicht ganz genau das, was ich haben wollte, aber ein sehr nützliches Standard-Setup. So kommen VMs, die man einrichtet “einfach mal eben schnell” ins Internet, ohne dass man dafür groß konfigurieren müsste.
Damit das NAT funktioniert, muss das Kernelmodul tun
geladen und zum autostart hinzufügt werden:
sudo modprobe tun
sudo echo "tun" >> /etc/modules-load.d/tun.conf
Nun kann man die Netzwerkkonfiguration für unser spezielles Szenario anpassen. Dazu bearbeitet man die Datei /etc/network/interfaces
, sodass sie ungefähr so aussieht:
auto lo
iface lo inet loopback
auto eth0.100
iface eth0.100 inet dhcp
# interface and bridge for Home-Network-VM
iface eth0.101 inet manual
auto br1
iface br1 inet dhcp
bridge_ports eth0.101
bridge_stp off
bridge_fd 0.0
post-up ip link set dev eth0.101 mtu 1500
# interface and bridge for Freifunk-VM
iface eth0.102 inet manual
auto br2
iface br2 inet dhcp
bridge_ports eth0.102
bridge_stp off
bridge_fd 0.0
post-up ip link set dev eth0.102 mtu 1500
VLAN 100 ist dabei das VLAN, in dem der Host drin ist, VLAN 101 ist für die Heimnetz-VMs und VLAN 102 wird später noch mit dem Heimnetz verbunden. Die Interfaces sind dabei bewusst nochmal in eine Bridge gepackt, weil KVM nicht direkt mit den Interfaces, sondern nur mit Bridges umgehen kann.
Diese Einstellungen kann man mit /etc/init.d/networking restart
anwenden. Aber Achtung: Nach dem Anwenden ist der Rechner solange nicht erreichbar, bis wir am Freifunkrouter die VLANs entsprechend eingestellt haben.
VLANS am FF-Router einrichten
Nun sind wir an dem Punkt, wo wir auf dem Router das VLAN-Setup aufsetzen. Dazu im LuCI-Webinterface einloggen und dann auf die Switch-Konfigurationsseite gehen. Die findet man unter Netzwerk -> Switch.
Mit dem grünen Knopf VLAN hinzufügen, kann man ein weiteres VLAN erstellen. Dieses hat standardmäßig eine fortlaufende Nummer. Wir haben allerdings auf dem Thin-Client die VLANS 100, 101 und 102 konfiguriert. Deshalb werden wir das auf dem Router auch so anlegen.
Sobald man die IDs eingetragen hat, kann man die VLANs den entsprechenden Ports zuordnen. Tagged heißt dabei, dass das VLAN mit seiner ID auf dem entsprechenden Port ausgegeben wird, untagged bedeutet, dass es ohne die ID ausgegeben wird. Pro Port kann immer nur maximal ein VLAN untagged sein.
Ein bisschen speziell ist der Port CPU. Da muss jedes VLAN als tagged hinzugefügt werden. Falls man das vergisst, kann es nicht geswitched werden. Das äußert sich dann dadurch, dass es so aussieht, als sei das VLAN nicht vorhanden und kann manchmal etwas Zeit kosten, bis man darauf kommt (wenn man, so wie ich Schussel, z.B. 30 Minuten deswegen debuggt…).
Hat man die VLANs abgespeichert (und angewendet), wechselt man in die Interface-Konfiguration unter Netzwerk -> Netzwerkschnittstellen. Im Reiter Geräte müsste nun ein Adapter mit dem Namen der Schnittstelle, gefolgt von einem Punkt und der ID des neuen VLANs auftauchen. Also z.B. eth1.42
für das VLAN 42 auf der Schnittstelle eth1
.
Falls das VLAN nicht auftaucht, ist nun ein guter Zeitpunkt, zu schauen, ob das VLAN auch am CPU-Port als tagged eingestellt ist. ;-)
Abschließend fügt man im Reiter Netzwerkschnittstellen den entsprechenden VLAN-Adapter noch den Interface zu, zu welchem er gehören soll. Für dieses Beispiel kommt eth1.100
in das Heimnetz, eth1.101
in das IOT-Netzwerk und eth1.102
wird in die Brücke br-dhcp
eingefügt, damit es dort Access zum Freifunk-Netz hat.
Unter Umständen müssen diese interfaces erst angelegt werden und auch noch ein paar Firewallregeln angepasst werden. Das ist aber eine Sache für einen anderen Post. :)
VMs in VLAN stecken
Wer der losen Anleitung bis hierher gefolgt ist, wird feststellen, dass zwar der Virtualisierungshost schon erreichbar ist, aber die VMs noch nicht in ihren eigenen VLANs sind. Das kann man nun auf zwei Arten erledigen. Endweder man nutzt virsh
, das CLI-Werkzeug zur Verwaltung der VMs oder man nutz den graphischen We über virt-manager. Ich mag den graphischen Weg lieber, weil er relativ intuitiv ist.
Ein weiterer Vorteil ist, dass man virt-manager
auch auf dem lokalen PC starten und eine remote-Verbindung zum VM-Host aufbauen kann.
Dort wählt man nun die zu bearbeitende Virtuelle Maschine und geht in der Info-Übersicht auf den entsprechenden Netzwerkadapter. Die Netzwerkquelle ändert man entsprechend auf Bridge und gibt in das Textfeld darunter den Namen der entsprechenden Bridge für das korrekte VLAN an.
Die Bridge br2 sorgt dafür, dass die VM im VLAN 102 ist und somit eine IP-Adresse aus dem Freifunknetz bekommt.
Weiterführender Stuff
Ein USB-Gerät in die VM hängen
Die graphische Ausgabe vom Virtmanager war ein bisschen widerspenstig, was das Hinzufügen von USB-Geräten anging. Ganz egal, was ich getan hatte, die Geräte wollten einfach nicht auftauchen.
Abhilfe hat folgendes geschaffen:
Falls nicht sowieso schon geschehen, die Pakete qemu-hw-usb-host
und qemu-hw-usb-redirect
nachinstallieren. Danach eine XML-Datei für das USB-Gerät nach diesem Tutorial anlegen: Red Hat Tutorial. Dabei die Definitionsdatei usb_device.xml
nennen.
Schlussendlich das Gerät mit diesem Kommando hinzufügen:
virsh -c qemu:///system attach-device ${VM-NAME} --file usb_device.xml --persistent
Auch interessantes Tutorial: https://david.wragg.org/blog/2009/03/using-usb-pass-through-under-libvirt.html
VMs automatisch neustarten
Bei der längeren Benutzung des oben beschriebenen Setups habe ich festgestellt, dass manchmal VMs aus irgendwelchen Gründen nicht mehr laufen. Warum, weiß ich auch nicht. Vielleicht hat irgendetwas einen reboot ausgelöst. Anyway kann man einstellen, dass eine VM bei einem Crash neu startet. Dazu muss die XML-Datei der VM editiert werden.
Man sucht nach dem Attribut <on_crash>destroy</on_crash>
und ersetzt es durch <on_crash>restart</on_crash>
. Anschließend muss die VM neu gestartet werden, damit die Änderungen wirksam werden.
Siehe auch auf serverfault.
VMs starten, sobald Host startet
Im BIOS kann man einen Server oft so einstellen, dass er nach einem Stromausfall von selbst bootet, sobald wieder Strom am Netzteil anliegt. Allerdings hatte ich festgestellt, dass das nicht heißt, dass dann auch alle VMs mitstarten. Über virsh
kann man jedoch auch konfigurieren, dass das passiert:
virsh autostart $VM_NAME
Danach booten die VMs, für die das eingestellt wurde, sobald KVM gestartet wird.
Quellen und weiterführende Links
- https://lunar.computer/posts/kvm-qemu-libvirt-alpine/
- https://wiki.alpinelinux.org/wiki/KVM
- https://johnsiu.com/blog/alpine-kvm/
- https://wiki.alpinelinux.org/wiki/Vlan