High-Availability (Hochverfülgbarkeit) der Home-Automation

High-Availability (Hochverfülgbarkeit) der Home-Automation

Am 15. Januar 2024 habe ich einen neuen Raspi 4 B inklusive dem empfohlenen offiziellen Netzteil erhalten und damit begonnen, die bisher von einem Linux-PC gesteuerten Automationen zu Hause (Stichwort SmartHome) auf diesen Raspi zu migrieren.
Spätestens nachdem ich in einem Forum gelesen habe, dass ein Benutzer während den Ferien erlebt hat, dass ein Netzteil seines Raspis ausgefallen ist und in der Folge seine Steuerungen zu Hause (Jalousien, Gartenbewässerung, etc.) komplett ausgefallen sind, beschloss ich, einen Weg zur Hochverfügbarkeit der Home-Automation zu beschreiten.
Hier wird dieser Weg beschrieben.

Hardware
Am 21. Februar 2021 erhielt ich die neue Hardware:
- Raspberry Pi 4 8G Model B
- Official Raspberry Pi 4 Power Adapter USB-C Schwarz
- SanDisk Max Endurance 32GB
Erstmals wollte ich einen Raspi mit 8GB RAM ausprobieren und sicherheitshalber bestellte ich das schwarze Netzteil (im Unterschied zum weissen Modell, welches im Januar 2024 gekauft wurde); vielleicht erkaufe ich mir damit eine gewisse Sicherheit, obwohl ich nicht weiss, ob das schwarze und weisse Netzteil bzgl. Hardware wirklich unterschiedlich sind.
Als SD-Karte entschied ich mich für eine 'SanDisk Max Endurance' in der Hoffnung, dass diese lange haltbar ist.

Installation des Raspberry Pi 4 B
Der neue Raspi wurde gleich eingerichtet wie derjenige vom Januar 2024.
Positioniert wurde der neue Raspi diesmal nicht im Untergeschoss, sondern im Obergeschoss, um eine örtliche Redundanz zu etablieren.

Zwei Rechner zu einem Hochverfügbarkeits-System verbinden
Die zwei Raspis haben die Namen raspi und raspi2. Anstatt nur via 'ping' zu schauen, ob beide Rechner aktiv sind, wird regelmässig je ein File auf den anderen Rechner kopiert. Falls dieser Vorgang nicht erfolgreich ist, setzt sich der entspechende Rechner automatisch als MASTER.
Als crontab wird auf beiden Rechnern folgendes eingetragen:

*/2 * * * * $HOME/smarthome/HA/ha.sh >> $HOME/smarthome/HA/ha.log 2>>$HOME/smarthome/HA/ha.errlog

Das File 'ha.sh' sieht wie folgt aus:
Zur besseren Darstellung wurde vor dem Einfügen folgender Befehl benutzt:
cat ha.sh | > ha.text

#!/bin/bash # ha.sh 23Feb2024/uk # # High-Availability # cd $HOME/smarthome/HA # # Es kam am 23. Feb. 2024 vor, dass das File /etc/resolv.conf "leer" war: # grep -q 192.168.0.1 /etc/resolv.conf if test $? -ne 0 ; then echo "`uname -n`: /etc/resolv.conf ist 'leer' - Neustart! `date +%d.%m.%Y_%H:%M`" >> neustart.log sudo shutdown -r 0 fi # source PRIMARY.MASTER #echo "PRIMARY_MASTER=$PRIMARY_MASTER" # source MASTER.file if test "$MASTER" = "no" ; then sleep 2 fi # othermachine="none" thismachine=`uname -n` statusothermachine="ok" # if test "$thismachine" = "raspi" ; then othermachine="raspi2" fi if test "$thismachine" = "raspi2" ; then othermachine="raspi" fi if test "$othermachine" = "none" ; then # write/append to standard error echo "Machine=$thismachine - weder raspi noch raspi2 --> exit! `date +%d.%m.%Y_%H:%M`" >&2 exit 1 fi # hellofile=hello_from_$thismachine touch $hellofile # otherhellofile=hello_from_$othermachine # foblack="<font size=5 color=black><b>" fowhite="<font size=5 color=white><b>" fontend="</font>" # statusthismachine="ok" # scp -p $hellofile $othermachine:smarthome/HA/ >/dev/null if test $? -ne 0 ; then echo "$thismachine meldet: $othermachine nicht erreichbar! `date +%d.%m.%Y_%H:%M`" statusothermachine="notok" else ## echo "$thismachine meldet: $othermachine erreichbar!" : fi rm -f result.txt find -name $otherhellofile -mmin +4 > result.txt if test ! -s result.txt ; then ## echo "$otherhellofile ist jünger als 4 Min." othermcol="green" othermfont=$fowhite : else echo "$otherhellofile ist älter als 4 Min. - `date +%d.%m.%Y_%H:%M`" statusothermachine="notok" othermcol="red" othermfont=$foblack fi # if test "$thismachine" = "$PRIMARY_MASTER" ; then echo "MASTER='yes'" > MASTER.file_new echo "SLAVE=$othermachine" >> MASTER.file_new thismcol="green" thismfont=$fowhite else if test "$statusothermachine" = "notok" ; then echo "MASTER='yes'" > MASTER.file echo "SLAVE=$othermachine" >> MASTER.file_new echo "$thismachine wird Master - `date +%d.%m.%Y_%H:%M`" thismcol="green" thismfont=$fowhite othermcol="red" othermfont=$foblack else echo "MASTER='no'" > MASTER.file_new echo "SLAVE=$thismachine" >> MASTER.file_new thismcol="green" thismfont=$fowhite othermcol="green" othermfont=$fowhite fi fi # mv MASTER.file_new MASTER.file # source MASTER.file if test "$MASTER" = "yes" ; then thismachinemaster="YES" othermachinemaster="no" else thismachinemaster="no" othermachinemaster="YES" fi echo "<p>" > ha_new.html echo "<font size=2 color=black><b>High-Availabilty-Status</b><br></font>" >> ha_new.html echo "<font size=2 color=black><b>`date +%d.%m.%Y_%H:%M`</b></font>" >> ha_new.html echo "<p>" >> ha_new.html ##echo "`date +%d.%m.%Y_%H:%M`<br>" >> ha_new.html ##echo "$thismachine MASTER=$MASTER <br>" >> ha_new.html ##echo "$othermachine $statusothermachine <br>" >> ha_new.html ##echo "$othermachine MASTER=no <br>" >> ha_new.html echo "<p>" >> ha_new.html echo "<table border=1 cellpadding=3 cellspacing=9>" >> ha_new.html echo "<tr>" >> ha_new.html echo "<td>$foblack Machine</td><td align=center bgcolor=$thismcol>$thismfont $thismachine</td><td align=center bgcolor=$othermcol>$othermfont $othermachine</td>" >> ha_new.html echo "</tr>" >> ha_new.html echo "<tr>" >> ha_new.html echo "<td>$foblack Master</td><td align=center>$foblack $thismachinemaster</td><td align=center>$foblack $othermachinemaster</td>" >> ha_new.html echo "</tr>" >> ha_new.html echo "<tr>" >> ha_new.html echo "<td>$foblack Erreichbar</td><td align=center bgcolor=$thismcol>$thismfont $statusthismachine</td><td align=center bgcolor=$othermcol>$othermfont $statusothermachine</td>" >> ha_new.html echo "</tr>" >> ha_new.html echo "</table>" >> ha_new.html echo "<p>" >> ha_new.html ##fi # mv ha_new.html ha.html #
Das HTML-File-Fragment 'ha_new.html' dient dazu, auf einer Webseite den Status der beiden Rechner darzustellen. Ist z.B. der Rechner 'raspi' nicht erreichbar/down, dann sieht dieses HTML-Fragment wie folgt aus:

Das File PRIMARY.MASTER hat folgenden Inhalt: PRIMARY_MASTER='raspi' und ist auf beiden Rechnern identisch. Es dient dazu, zu definieren, welcher der beiden Rechner der Master ist, falls beide Rechner online sind.
Das File MASTER.file wird durch das oben beschriebene Script ha.sh alle zwei Minuten neu erstellt und sieht auf dem Master so aus:


MASTER='yes'
SLAVE=raspi2
und auf dem Slave so:

MASTER='no'
SLAVE=raspi

Software auf den beiden Rechnern, die zum Hochverfügbarkeits-System gehören
Die Software auf beiden Rechnern sollte generell identisch gehalten werden. Dies kann z.B. nach einem Backup (siehe unten) auch mittels einem Script überprüft werden.
Auf beiden Rechnern wird ja alle zwei Minuten getestet, ob der andere ebenfalls funktioniert und erreichbar ist. Dieser Status ist jeweils im File (siehe oben) $HOME/smarthome/HA/MASTER.file gespeichert (MASTER='yes' oder MASTER='no'). In allen Scripts auf beiden Rechnern lesen wir nun gleich zu Beginn diesen Status wie folgt aus:


source $HOME/smarthome/HA/MASTER.file if test "$MASTER" = "no" ; then echo "Not MASTER - exit! `date +%d.%m.%Y_%H:%M`" exit fi
und können jeweils das via cron aufgerufene Script gleich beenden.

Dies wurde genauso implementiert für die Steuerung der Storen bzw. Rollläden und ebenso für die automatisierte Gartenbewässerung inklusive der Sicherheitsüberwachung der Bewässerung, welche das Wasserventil schliesst, sollte es zu einer Zeit, wo keine Bewässerung vorgesehen ist, noch geöffnet sein.

Wichtig ist allerdings. dass die beiden redundanten Rechner stets auf dem aktuellen Stand der Daten sind. Wo immer in einem Script daher (auf dem MASTER) neue Daten generiert werden, wird sichergestellt, dass diese auf den SLAVE kopiert werden:


scp -p -o ConnectTimeout=5 Daten.file $SLAVE:smarthome/Verzeichnis_auf_slave/
Damit ist sichergestellt, dass beide Maschinen zu jeder Zeit bezüglich der Daten auf dem gleichen Stand sind und im Falle eine 'Failovers', also des nahtlosen und automatischen Umschaltens auf das redundante System, die Arbeiten problemlos und quasi kontinuierlich mit aktuellen Daten weitergeführt werden können.

 

Backup der beiden redundanten Rechner
Ein Backup der selbst geschriebenen Software ist immer eine Notwendigkeit; am besten immer dann, wenn die Software modifiziert wurde!
Aus diesem Grund habe ich auf einem dritten Rechner ein Verzeichnis namens "RASPIsave" eingerichtet und dort folgendes Script gespeichert:


#!/bin/bash # saveRASPIS_3.sh 24Feb2024/uk # Version für raspi3 # cd $HOME/RASPIsave USER=$LOGNAME # mkdir -p raspi mkdir -p raspi2 # machine=raspi #mkdir -p $machine/home rsync -avHx --exclude RASPIsave --delete $USER@$machine:/home/$USER $machine/home mkdir -p $machine/var/www/html rsync -avHx --delete $USER@$machine:/var/www/html/ $machine/var/www/html mkdir -p $machine/etc/network/if-up.d rsync -avHx --delete $USER@$machine:/etc/network/if-up.d/ $machine/etc/network/if-up.d mkdir -p $machine/usr/lib/cgi-bin rsync -avHx --delete $USER@$machine:/usr/lib/cgi-bin/ $machine/usr/lib/cgi-bin mkdir -p $machine/usr/local/bin rsync -avHx --delete $USER@$machine:/usr/local/bin/ $machine/usr/local/bin/ # machine=raspi2 #mkdir -p $machine/home rsync -avHx --exclude RASPIsave --delete $USER@$machine:/home/$USER $machine/home mkdir -p $machine/var/www/html rsync -avHx --delete $USER@$machine:/var/www/html/ $machine/var/www/html mkdir -p $machine/etc/network/if-up.d rsync -avHx --delete $USER@$machine:/etc/network/if-up.d/ $machine/etc/network/if-up.d mkdir -p $machine/usr/lib/cgi-bin rsync -avHx --delete $USER@$machine:/usr/lib/cgi-bin/ $machine/usr/lib/cgi-bin mkdir -p $machine/usr/local/bin rsync -avHx --delete $USER@$machine:/usr/local/bin/ $machine/usr/local/bin/ # cd /home/$USER/RASPIsave # # Find *errlog - Files grösser als Null: # echo " " echo "*errlog - Files grösser als Null von OCTAVE: " echo " " for file in `find ./ -name "*errlog" | grep octave` ; do if test -s $file ; then ll $file fi done # echo " " echo "*errlog - Files grösser als Null OHNE OCTAVE: " echo " " for file in `find ./ -name "*errlog" | grep -v octave` ; do if test -s $file ; then ll $file fi done #
Mittels dem Befehl ./saveRASPIS_3.sh werden danach von beiden Raspis die jeweiligen Directories auf die Unterverzeichnisse raspi bzw. raspi2 kopiert - unter Beibehaltung der Daten und Rechte.
Damit dies automatisch funktioniert, müssen vorher wie unter Erfahrungen mit einem Raspberry Pi 4 B (Raspi) im Abschnitt "Login via SSH ohne Passwort" die Schlüssel auf den beteiligten Rechnern deponiert werden!
Am Schluss dieses Backup-Scriptes wird gleich auch noch kontrolliert, ob es von irgendwelchen cronjobs Fehlermeldungen gegeben hat. Seit vielen Jahren definiere ich cron-jobs wie folgt:
*/2 * * * * $HOME/smarthome/HA/ha.sh >> $HOME/smarthome/HA/ha.log 2>>$HOME/smarthome/HA/ha.errlog
Damit kann nun jederzeit festgestellt werden, ob es irgendwo Files des Typs *.errlog gibt, die grösser als Null sind. Falls ja, ist bei einem cron-job ein Fehler aufgetreten.
Um - zumindest grob - zu checken, ob die Software auf beiden redundanten Rechnern identisch ist, hilft das Script differ.sh:

#!/bin/bash # differ.sh 22Mar2024/uk # cd $HOME/RASPIsave echo "Comparing *.sh Scripts:" for file in `find raspi/ -name "*.sh" | grep -v makeLink.sh | grep -v _alt | grep -v _new ` ; do for file2 in `find raspi2/ -name "*.sh" | grep -v makeLink.sh | grep -v _alt | grep -v _new` ; do fileA=`basename $file` fileB=`basename $file2` if test "$fileA" = "$fileB" ; then diff $file $file2 if test $? -ne 0 ; then echo "^^^$file" fi fi done done # echo "Comparing *.m Files:" for file in `find raspi/ -name "*.m" | grep -v _alt | grep -v _new ` ; do for file2 in `find raspi2/ -name "*.m" | grep -v _alt | grep -v _new` ; do fileA=`basename $file` fileB=`basename $file2` if test "$fileA" = "$fileB" ; then diff $file $file2 if test $? -ne 0 ; then echo "^^^$file" fi fi done done # echo "Comparing *.f Files:" for file in `find raspi/ -name "*.f" | grep -v _alt | grep -v _new ` ; do for file2 in `find raspi2/ -name "*.f" | grep -v _alt | grep -v _new` ; do fileA=`basename $file` fileB=`basename $file2` if test "$fileA" = "$fileB" ; then diff $file $file2 if test $? -ne 0 ; then echo "^^^$file" fi fi done done # echo "Comparing *.exe Files:" for file in `find raspi/ -name "*.exe" | grep -v _alt | grep -v _new ` ; do for file2 in `find raspi2/ -name "*.exe" | grep -v _alt | grep -v _new` ; do fileA=`basename $file` fileB=`basename $file2` if test "$fileA" = "$fileB" ; then diff $file $file2 if test $? -ne 0 ; then echo "^^^$file" fi fi done done #
Mittels dem Befehl ./differ.sh werden (in diesem Fall) in den Backups der beiden Rechner jeweils die Shell-Scripts (*.sh), die Octave-Scripts (*.m) sowie die Source-Codes (*.f) und die Executables (*.exe) - falls vergessen wurde, das Programm auf beiden Rechnern zu kompilieren bzw. das ausführbare Programm auf den anderen Rechner zu kopieren - der Fortran-Programme miteinander verglichen und bei Differenzen eine entsprechende Ausgabe gemacht.
Wenn keine Unterschiede auftreten kann man in Ruhe schlafen gehen, weil man dann die Gewissheit hat, dass die Software-Versionen auf beiden Rechnern identisch und die Rechner somit bereit sind, im Falle eines Hardwareproblems die Aufgabe des 'Kollegen' zu übernehmen.

 


Last Update: 30Apr2024 - Created: 21Feb2024 / uk