Raspberry Pi 3/4 mittels PXE vom Netzwerk booten

In diesem Artikel habe ich zusammen gefasst, wie ich meine Rasperry Pi's davon überzeugt habe, dass sie vom Netz booten sollen. Ich halte von SD-Karten nicht sonderlich viel, da sie je nach Qualität die Tendenz haben früher oder später kaputt zu werden. Ganz ohne geht es vielleicht gar nicht, wenn man etwa einen Rasperry Pi 3 verwendet. Dazu später mehr.

Weil ich leider alles was ich mir erarbeitet habe auch mindestens genauso schnell wieder vergesse, habe ich mich dazu entschieden, den gesamten Vorgang hier zu dokumentieren, um später ein Nachschlagewerk zu haben, falls ich einmal einen Fehler suchen oder einen zusätzlichen Raspberry Pi mit Netzwerk-Boot einrichten muss.

Die Voraussetzungen im Netzwerk werden hier nur kurz angeschnitten.

Netzwerk

Ich verwende im Netzbereich hauptsächlich Unifi Komponenten von Ubuquiti. Das Herzstück bildet eine UDM Pro, weiters sind einige Switches und WLAN-Router verbaut. Für das Booten vom Netz wird ein TFTP-Server und ein NFS-Server benötigt.

Um in den UDM Pro Settings PXE Boot zu aktivieren muss beim betroffenen Netzwerk die Option "DHCP Network Boot" aktiviert und entsprechend konfiguriert sein:

Hier ist jeweils die IP-Adresse des Servers bzw. der Name des Boot-Images anzugeben.

Wie man einen TFTP-Server bzw. NFS-Server installiert, werde ich hier nicht besprechen, dazu gibt es genug Doku im Netz. Wichtig ist bei meiner Konfiguration, dass das TFTP-Root-Verzeichnis und das freigegebene NAS-Share auf das selbe Verzeichnis zeigen (in meinem Fall ist das /mnt/nas). Andernfalls wird die nachfolgende Beschreibung nicht funktionieren und es müssten entsprechende Anpassungen vorgenommen werden.

Raspberry Pi 4

Zuerst wird das gewünschte Image mit dem Raspberry Pi Imager auf eine SD Karte geschrieben. Ich verwende Raspberry Pi OS Lite (64-bit), also ohne Desktop, da ich auf einem Server keine grafische Umgebung benötige. In den Optionen kann auch gleich der richtige Hostname eingetragen, SSH aktiviert und das Passwort für den User Pi gesetzt werden. Wenn das Image geschrieben wurde, die SD Karte in den Pi einlegen und booten.

Um die aktuellsten Updates zu installieren führt man

sudo apt-get update && sudo apt-get upgrade

aus.

Mittels sudo raspi-config wird in den Advanced Options auf die Verwendung des neuesten Bootloaders umgeschaltet (Latest). Die anschließende Frage, ob die Settings für den Bootloader auf die Default-Werte zurück gesetzt werden sollen, habe ich bejaht. Um den Bootloader zu aktualisieren (falls ein Update ansteht), muss der Raspi rebootet werden.

Nun müssen wir die ID des Raspberry Pi ermittlen, dies geschieht mit folgendem Kommando:

vcgencmd otp_dump | grep 28: | sed s/.*://g

Der Befehl sollte eine ID in folgendem Format ausgeben:

36f73078

Diese ID wird für die Folder am NFS-Server verwendet. Zuerst wird der Ordner für das Root-Filesystem angelegt:

pi@nfs-server:/mnt/nas $ mkdir /mnt/nas/rpi4-36f73078

Nun wechselt man wieder auf den Raspberry Pi 4 und mounted das Share. Dazu wird am ein Ordner unter /mnt angelegt:

sudo mkdir /mnt/nfs

Das NFS-Share wird in den eben angelegten Ornder mit folgendem Befehl eingehängt: 

sudo mount -t nfs -o nfsvers=3 192.168.225.10:/mnt/nas /mnt/nfs/ -vvv

Anschließend wird das komplette Root-Verzeichnis ("/") mit allen Subfoldern und Files auf das NFS-Share kopiert:

sudo rsync -xa --progress --exclude /mnt/nfs / /mnt/nfs/rpi4-36f73078/

Jetzt ist ein guter Zeitpunkt einen Kaffee oder ein kühles Blondes zu genießen, das Kopieren nimmt schon eine gewisse Zeit in Anspruch. Wenn der Kopier-Vorgang abgeschlossen ist, kann man mittels

ls /mnt/nfs/rpi4-36f73078/boot

feststellen, dass der Folder "boot" leer ist. Damit der Raspberry Pi 4 von TFTP-Server booten kann, muss das boot-Verzeichnis ins TFTP-Root Verzeichnis kopiert werden. Damit es nicht doppelt vorhanden ist, werden wir es in das Root-Filesystem kopieren und am TFTP-Root Folder mit einen symbolic Link arbeiten (wir erinnern uns: TFTP-Root = NFS-Share). Zuerst kopiert man also das Boot-Verzeichnis in das Root-Filesystem auf den NAS-Server:

sudo rsync -xa --progress /boot /mnt/nfs/rpi4-36f73078/

Nun sollten die Files korrekt vorhanden sein, eine kurze Kontrolle kann nicht schaden:

ls /mnt/nfs/rpi4-36f73078/boot

Auf dem TFTP/NFS-Server legen wir nun einen symbolic Link an (TFTP-Root = NFS-Share), der auf das Boot-Verzeichnis im Root-Filesystem zeigt:

pi@nfs-server:/mnt/nas $ ln -s rpi4-36f73078/boot/ 36f73078

Der Folder "36f73078" ist jener Ort, wo der Raspberry Pi 4 sein Boot-Image suchen wird.

Das TFTP-Root-Verzeichnis bzw. das NAS-Share sollten dann etwa wie folgt aussehen:

pi@nfs-server:/mnt/nas $ ls -al
drwxrwxrwx 6 root root 4096 Feb 11 01:00 .
drwxr-xr-x 3 root root 4096 Oct 10 16:52 ..
lrwxrwxrwx 1 pi pi 19 Feb 11 01:00 36f73078 -> rpi4-36f73078/boot/
drwxr-xr-x 18 pi pi 4096 Sep 22 05:06 rpi4-36f73078

Jetzt kann bereits die file system tab für den Raspberry Pi4 geändert werden, damit das Root-Filesystem vom NFS-Server anstelle von der SD-Karte gemountet wird.

pi@nfs-server:/mnt/nas $ sudo nano rpi4-36f73078/etc/fstab

Die bestehenden Einträge werden mit einem vorgestellten "#" auskommentiert oder gelöscht, und der neue Eintrag für "/" hinzugefügt. Das sollte dann so aussehen:

pi@nfs-server:/mnt/nas $ cat rpi4-36f73078/etc/fstab
proc /proc proc defaults 0 0
192.168.225.10:/mnt/nfs/rpi4-36f73078 / nfs defaults,noatime 0 1
#PARTUUID=0dbcbea5-01 /boot vfat defaults 0 2
#PARTUUID=0dbcbea5-02 / ext4 defaults,noatime 0 1

Als nächstes müssen die Start-Parameter im cmdline.txt geändert werden, um den Boot vom Netzwerk zu ermögichen:

pi@nfs-server:/mnt/nas $ sudo nano /mnt/nas/rpi4-36f73078/boot/cmdline.txt

Die Eintrag wird wie folgt geändert:

console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.225.10:/mnt/nas/rpi4-36f73078,nfsvers=3 rw ip=dhcp rootwait elevator=deadline

Jetzt muss als letzter Schritt noch der Netzwerk-Boot aktiviert werden. Dies geschieht wieder mit dem Befehl sudo raspi-config, dort im Menü Advanced Options/Boot Order den Network Boot auswählen. Danach muss der Raspberry Pi 4 noch einmal von der SD Karte gebootet werden damit die Einstellung wirksam wird. Sobald der Raspi hochgefahren ist, kann er wieder niedergefahren und die SD Karte entfernt werden.

Nun sollte der Raspberry Pi vom Netzwerk booten.

Raspberry Pi 3

Der Raspberry Pi3 kann leider nicht direkt vom Netz booten, daher wird immer eine SD-Karte für den Bootvorgang benötigt. Ich habe mir dafür billige kleine SD Karten (8 GB) besorgt. Kosten nur ein paar Euro. Das Root-Filesystem kann aber sehr wohl auf den NFS-Server gelegt werden. Somit können die Zugriffe auf die SD Karte auf ein Minimum reduziert werden.

Auch hier beginnen wir wieder mit dem Schreiben des OS Image auf die SD Karte . Ich verwende wie gesagt Raspberry Pi OS Lite (64-bit), also ohne Desktop, da ich auf einem Server keine grafische Umgebung benötige. In den Optionen kann auch gleich der richtige Hostname eingetragen und das Passwort für den User Pi gesetzt werden. Wenn das Image geschrieben wurde, die SD Karte in den Pi einlegen und booten.

Mittels sudo raspi-config wird als erstes der SSH Server aktiviert (Interface Options/SSH), damit remote via SSH auf den Pi zugegriffen werden kann.

Nun müssen wir die ID des Raspberry Pi ermittlen, dies geschieht mit folgendem Kommando:

vcgencmd otp_dump | grep 28: | sed s/.*://g

Das Kommando sollte eine ID in folgendem Format ausgeben:

fee2c58f

Am NFS-Server wird nun der Folder für das Root-Filesystem angelegt:

mkdir /mnt/nfs/rpi3-fee2c58f

Am NFS-Server sollte dies nun so aussehen (Achtung: hier sind auch die Einträge für den vorher bereits konfigurierten Raspberry Pi 4 sichtbar!):

pi@nfs-server:/mnt/nas $ ls -al
drwxrwxrwx 6 root root 4096 Feb 11 01:00 .
drwxr-xr-x 3 root root 4096 Oct 10 16:52 ..
lrwxrwxrwx 1 pi pi 19 Feb 11 01:00 36f73078 -> rpi4-36f73078/boot/
drwxr-xr-x 18 pi pi 4096 Sep 22 05:06 rpi3-fee2c58
drwxr-xr-x 18 pi pi 4096 Sep 22 05:06 rpi4-36f73078

Damit ist alles vorbereitet und es kann wieder auf den Raspberry Pi 3 gewechselt werden. Dort muss ein Folder angelegt werden, in den das NFS-Share in weiterer Folge gemountet wird:

sudo mkdir /mnt/nfs

Das NFS-Share mit folgendem Befehl mounten: 

sudo mount -t nfs -o nfsvers=3 192.168.123.15:/mnt/nas /mnt/nfs/ -vvv

komplette SD Karte auf das NFS Share kopieren:

sudo rsync -xa --progress --exclude /mnt/nfs / /mnt/nfs/rpi3-fee2c58/

Anschließend habe ich die Daten der VFAT-Partition (die Partition wird später in den Ordner /boot gemounted) auf eine billige 8 GB SD-Karte kopiert.

In der cmdline.txt auf der 8 GB SD-Karte muss dem Raspberry Pi 3 nun bekannt gemacht werden, dass er das Root-Filesystem vom Netzwerk mounten soll. Dazu tragen wir folgende Zeile in das cmdline.txt ein:

console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.225.10:/mnt/nas/rpi3-fee2c58,nfsvers=3 rw ip=dhcp rootwait elevator=deadline

Nun wird die Filesystem Tab am NFS-Server anpasst, damit das Root-Filesystem am NAS gemountet wird. Zusätzlich wollen wir auch noch die VFAT Partition nach /boot mounten, damit Updates für selbige beim Update-Vorgang auch korrekt installiert werden können:

sudo nano /mnt/nfs/rpi3-fee2c58/etc/fstab

Folgende Einträge müssen erzeugt werden:

/dev/mmcplk0p1                      /boot vfat defaults         0 2
192.168.123.15:/mnt/nfs/rpi-fee2c58 /     nfs  defaults,noatime 0 1

Die vorhandenen Einträge (PARTUUID=...) sind auszukommentieren oder zu löschen, das sollte dann etwa so aussehen:

Der Raspberry Pi sollte nun vom Netzwerk booten.

WICHTIG!

Falls einmal die Firmware des Switch aktualisiert werden muss, dann sollten zovor die vom Netz bootenden Raspis unbedingt niederfahren werden, sonst zieht man ihnen den Boden unter den Füßen weg! Es ist durchaus denkbar, dass das Filesystem dadurch eventuell Schaden nehmen könnte.

Quellen/hilfreiche Artikel:
https://rob-ferguson.me/how-to-pxe-boot-your-rpi/

New-MoveRequest scheitert sofort mit RemoteTransientException

Beim Verlegen einer Exchange 2016 Mailbox hatte ich das Problem, dass der Move-Request sofort mit einer RemoteTransientException abgebrochen hat, ohne überhaupt die Mailbox zugreifen zu können:

Die anschließende Recherche hat ergeben, dass die Mailbox-Datenbank in der die betroffene Mailbox liegt, am vorigen Tag dismounted wurde, da die Disk vollgelaufen ist. Die betroffene Mailbox war der Verursacher dieses Ausfalls (es wurden im großen Stil Daten in die Mailbox geschoben, ohne auf den freien Plattenplatz zu achten). Deshalb wurde die Mailbox in eine Quarantäne gezwungen. Da die Disk inzwischen erweitert wurde, konnte die Mailbox wieder freigeschaltet werden. Dazu benötigt man die Mailbox GUID (ExchangeGuid) und die MailboxDatabase GUID (Guid).

Auf dem Server bzw. den Servern wo die entsprechende Mailboxdatenbank liegt (bei einer DAG auf allen Servern, die eine Kopie der Datenbank halten), muss folgender Eintrag in der Registry gelöscht werden (die oben ermittelten Guids sind hier einzusetzen):

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MSExchangeIS\SERVERNAME\Private-MAILBOXDATABASEGUID\QuarantinedMailboxes\MAILBOXGUID

Nachdem der Registry-Key auf allen Servern gelöscht war, konnte der Move-Request erfolgreich abgesetzt werden.

 

Unifi USG: mehrere DynDNS Einträge beim selben Provider

Der Unifi Controller ist ein umfangreiches Tool zum Konfigurieren aller Unifi-Komponenten im Netz, aber wie jede Software hat auch er seine Schwächen. Es sind zum Beispiel nicht alle Funktionen die das USG bietet, via Controller konfigurierbar. Oder sie sind nur eingeschränkt konfigurierbar.

Zum Beispiel kann für Dynamic DNS pro Provider nur ein Hostname im Controller eingerichtet werden. Natürlich habe ich zwei Hostnamen beim gleichen Provider. Jetzt wäre eine Möglichkeit gewesen, mit einen der beiden Hostnamen zu einem andern Provider zu wechseln - wollte ich aber nicht und so habe zu suchen begonnen ob es nicht doch eine Möglichkeit gibt, am Unifi USG mehrere Hostnamen beim selben DynDNS-Provider upzudaten. Und ich wurde fündig.

Dazu muss man via SSH direkt am USG einsteigen und das Konfigurations-Tool starten:

admin@usg-irgendwo:~$ configure
[edit]

Nun folgende Zeile eingeben und am Ende 2x Tab drücken, dann bekommt man alle definierbaren Settings angezeigt (hier anhand des Providers NOIP, will man einen anderen Provider, dann einfach nach "service" 2x Tab und man bekommt alle im USG vorhandenen Provider angezeigt):

admin@usg-irgendwo# set service dns dynamic interface eth0 service noip
Possible completions:
  host-name     Hostname registered with DDNS service [REQUIRED]
  login         Login for DDNS service [REQUIRED]
  options       Additional options for ddclient. You must use the ddclient
                configuration syntax as the text is added directly to ddclient
                configuration file without any syntax checking.
  password      Password for DDNS service [REQUIRED]
  protocol      ddclient protocol used for DDNS service [REQUIRED FOR CUSTOM]
  server        Server to send DDNS update to [REQUIRED FOR CUSTOM]

Nun konnte ich den ersten DynDNS-Hostnamen konfigurieren:

admin@usg-irgendwo# set service dns dynamic interface eth0 service noip1 host-name hostname1.ddns.net
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip1 login [email protected]
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip1 password [email protected]
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip1 protocol noip
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip1 server dynupdate.no-ip.com
[edit]

Für den zweiten Hostnamen vergibt man nach "service" einfach einen anderen Namen:

admin@usg-irgendwo# set service dns dynamic interface eth0 service noip2 host-name hostname2.ddns.net
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip2 login [email protected]
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip2 password [email protected]
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip2 protocol noip
[edit]
admin@usg-irgendwo# set service dns dynamic interface eth0 service noip2 server dynupdate.no-ip.com
[edit]

Um sicher zu stellen, dass das USG nicht seine eigene WAN-Adresse registriert sondern die tatsächliche öffentliche Adresse eines vorgelagerten Modems, kann man ihn dazu zwingen die eigene öffentliche Adresse via Web-Abfrage zu ermitteln:

admin@usg-irgendwo# set service dns dynamic interface eth0 web dyndns
[edit]

Die konfigurierten Einstellungen können wie folgt angezeigt und kontrolliert werden:

admin@usg-irgendwo# show service dns dynamic interface eth0
+service noip1 {
+    host-name hostname1.ddns.net
+    login [email protected]
+    password [email protected]
+    protocol noip
+    server dynupdate.no-ip.com
+}
+service noip2 {
+    host-name hostname2.ddns.net
+    login [email protected]
+    password [email protected]
+    protocol noip
+    server dynupdate.no-ip.com
+}
+web dyndns
[edit]

Die Settings müssen gespeichert werden:

admin@usg-irgendwo# commit
[edit]
admin@usg-irgendwo# save
Saving configuration to '/config/config.boot'...
Done
[edit]
admin@usg-irgendwo# exit
admin@usg-irgendwo:~$

Das Schöne dabei ist: wenn man WAN-Failover konfiguriert hat, werden bei einem Failover die DynDNS Hostnamen automatisch aktualisiert (was lustiger Weise genau während dem Schreiben dieses Artikels passiert ist).

Damit die DynDNS-Einstellungen beim nächsten Provisionieren des USG via Unifi-Controller nicht verloren gehen, müssen die diese exportiert und am Unifi-Controller abgelegt werden. Dazu folgt demnächst ein eigener Blog-Eintrag.

Unifi USG WAN Failover

Das Unifi USG ist beim WAN Failover aus meiner bescheidenen Sicht etwas übereifrig beim Umschalten. So kommt es mehr oder weniger täglich vor, dass binnen einer Minute ein Failover von WAN auf WAN2 erfolgt und in der selben Minute wieder von WAN2 auf WAN zurück geschaltet wid: 

WAN iface [eth0] transition to state [inactive]	19:36	01-02-2021
WAN iface [eth2] transition to state [active]	19:36	01-02-2021
WAN iface [eth0] transition to state [active]	19:36	01-02-2021
WAN iface [eth2] transition to state [failover]	19:36	01-02-2021

Im Unifi-Controller kann man da gar nichts konfigurieren, dies geht nur über SSH direkt am USG. Die LoadBalancer-Konfiguration bzw. den Status kann man mit folgenden Befehlen anzeigen lassen:

admin@usg-irgendwo:~$ show load-balance watchdog
Group wan_failover
  eth0
  status: Running
  pings: 353
  fails: 4
  run fails: 0/3
  route drops: 11
  ping gateway: ping.ubnt.com - REACHABLE
  last route drop   : Mon Feb  1 20:21:12 2021
  last route recover: Mon Feb  1 20:21:49 2021

  eth2
  status: Running
  failover-only mode
  pings: 230
  fails: 11
  run fails: 0/3
  route drops: 6
  ping gateway: ping.ubnt.com - REACHABLE
  last route drop   : Mon Feb  1 20:41:24 2021
  last route recover: Mon Feb  1 20:41:48 2021

 

admin@usg-irgendwo:~$ show load-balance status
Group wan_failover
  interface   : eth0
  carrier     : up
  status      : active
  gateway     : 192.168.0.1
  route table : 201
  weight      : 100%
  flows
      WAN Out : 795000
      WAN In  : 174000
    Local Out : 6466

  interface   : eth2
  carrier     : up
  status      : failover
  gateway     : 192.168.1.1
  route table : 202
  weight      : 0%
  flows
      WAN Out : 7339
      WAN In  : 0
    Local Out : 70

Zur Prüfung der WAN-Verbindung wird also "ping.ubnt.com" angepingt. Geht das 3 Mal schief (run fails) wird umgeschaltet. Hier kann man die Konfiguration natürlich auch ändern, aber man sollte zumindest ungefähr wissen was man da gerade macht, sonst steht man vielleicht schnell ohne funktionierende WAN-Verbindung da ;-)

Konfiguration starten:

admin@usg-irgendwo:~$ configure
[edit]

Ändern des Pingziels:

admin@usg-irgendwo# set load-balance group wan_failover interface eth0 route-test type ping target
Possible completions:
  <x.x.x.x>     IPv4 address to ping
  <h:h:h:h:h:h:h:h>
                Ipv6 address to ping

Beispiel anhand des CloudFlare DNS als Pingziel: 

admin@usg-irgendwo# set load-balance group wan_failover interface eth0 route-test type ping target 1.1.1.1

Das Ping-Intervall kann wie folgt konfiguriert werden:

admin@usg-irgendwo# set load-balance group wan_failover interface eth0 route-test interval
Possible completions:
  <1-65535>     Ping interval in seconds (default 10)

Befehl zum festlegen der Anzahl an Ping-Fehlversuchen bis ein WAN-Failover eingeleitet wird:

admin@usg-irgendwo# set load-balance group wan_failover interface eth0 route-test count failure
Possible completions:
  <1-65535>     Number of failures before link is down (default 3)

Einstellen der Anzahl an erfolgreichen Pings bis wieder zurück geschaltet wird:

admin@usg-irgendwo# set load-balance group wan_failover interface eth0 route-test count success
Possible completions:
  <1-65535>     Number of successful pings for link up (default 3)

Die oben angeführten Einstellungen kann man für beide WAN Adapter (eth0 und eth2) getrennt vornehmen.

Hat man alle Settings bereit müssen sie abgesegnet werden:

admin@usg-irgendwo# commit
[edit]
admin@usg-irgendwo# save
Saving configuration to '/config/config.boot'...
Done
[edit]

Damit die Konfiguration beim nächsten Provisioning nicht überschrieben wird, muss diese noch exportiert und am Unifi-Controller abgelegt werden. Wie das genau funktioniert soll aber Inhalt eines der nächsten Beiträge sein.

Exchange Anti Malware Updates prüfen

Da Exchange FIPFS mal gerne seine Update- oder Proxy-Settings vergisst, ist es ratsam selbige regelmäßig zu kontrollieren.

Datum des letzten Updates der Malware Definitionen für alle Server anzeigen:

Get-ExchangeServer|%{Invoke-Command -ComputerName $_.Name -ScriptBlock { Add-PSSnapin Microsoft.Forefront.Filtering.management.powershell;$Date=(Get-EngineUpdateInformation).LastUpdated;Write-Host "$($env:computername) $($Date)"}}

Proxy Settings setzen und Engine Update starten:

Get-ExchangeServer <Servername>|%{Invoke-Command -ComputerName $_.Name -ScriptBlock {Add-PSSnapin Microsoft.Forefront.Filtering.management.powershell;Set-ProxySettings -Enabled $true -Server <ProxyServer> -Port <Port>;Start-EngineUpdate}}

Parameter <Servername>: Exchange Server auf dem das Kommando ausgeführt werden soll.
Parameter <ProxyServer>: Proxy-Server über den die Updates geladen werden sollen.
Parameter <Port>: Port des Proxy-Servers.