In einem privaten Projekt habe ich die Idee verfolgt, Wettervorhersagen für die Schweiz als Service über Social Media anzubieten. Für die Darstellung der Informationen habe ich auf Tilemaps zurückgegriffen. Die Programmierung des Bots erfolgte in Python. In diesem Blogpost soll es um die Automatisierung und das Deployment in der Cloud gehen.
In Teil 1 dieser Miniserie habe ich beschrieben, wie das Kartendesign des Wetter-Bots entstanden ist. In Teil 2 ging’s dann um die Programmierung des Bots an sich. Wir haben also Python-Code, der eine Wetter-API abfragt, die Resultate verarbeiten, visualisieren und mit Metadaten (Prognosezeitpunkt, Prognosegegenstand, Legende) anreichern kann und das Resultat über vier Mastodon-Accounts – @meteo_light und @meteoplus_light bzw. @meteo und @meteoplus – veröffentlicht. Der Code dafür ist auf GitHub einsehbar.
Heute geht’s darum, das ganze so zu deployen, dass das Programm selbständig dreimal am Tag ausgeführt wird und Wetterprognosen in die Welt sendet.
GitHub Actions und Runners
GitHub hat das Konzept von GitHub Actions. GitHub Actions sind eigentlich massgeschneidert für Continuous Integration– und Continuous Delivery-Workflows der Softwareentwicklung: Code in GitHub-Repositories kann damit automatisch durch die Abfolge von Build, Test und Deploy geschleust werden. Aber auch andere Funktionalitäten von GitHub können mit Actions (teil)automatisiert werden: beispielsweise kann man neu eröffnete Issues automatisch taggen lassen. Auf dem GitHub Marketplace gibt es eine Vielzahl von Actions für ganz unterschiedliche Verwendungszwecke. GitHub hat einen recht grosszügigen free tier für Actions: mit einem Gratis-Account hat man pro Repository und Monat 2’000 Minuten Laufzeit kostenlos zur Verfügung (wobei die eigentliche Laufzeit für Windows-Maschinen mit dem Faktor 2, für Mac OS-Maschinen mit dem Faktor 10 multipliziert wird).
Für das Ausführen von GitHub Actions können Nutzerinnen und Nutzer zwischen durch GitHub bereitgestellten virtuellen Windows-, Mac OS- und Linux-Maschinen als sogenannte «Runner» wählen. Alternativ kann man eigene Runner selbst hosten. GitHub Actions werden durch Ereignisse gestartet. Ein solches Ereignis kann wie oben beschrieben beispielsweise das Eröffnen eines Issues sein, das Erstellen eines Pull Requests oder das Pushen von Code in das Repository. Einmal ausgelöst, führt eine GitHub Action dann einen Workflow, also eine Reihe von Steps, aus.
Setup von GitHub Actions
GitHub Actions werden in je einer YAML-Datei im Verzeichnis .github/workflows/ innerhalb des jeweiligen Repositories konfiguriert. Im Repository selbst gibt es ein «Actions»-Tab. In diesem sieht man die für das jeweilige Repository konfigurierten Actions und den Status von vergangenen Ausführungen von Actions. Je nach Konfiguration der Actions können diese aus dem Tab heraus auch manuell gestartet werden – aber dazu gleich mehr.
Zuerst gilt es Actions im Repository zu erlauben. Unter Settings → Actions → General ist Allow all actions and reusable workflows der Default, wenn man ein neues Repository eröffnet. Möchte man auch ins Repository schreiben können, sollte unter Workflow permissions zudem Read and write permissions gesetzt werden (das ist für unsere Anwendung aus einem etwas speziellen Grund wünschenswert).
Cronjobs mit GitHub Actions
GitHub Actions können nicht nur auf Ereignisse wie Repository-Pushs reagieren. Sie können stattdessen auch als eine Art Cronjob fungieren, das heisst einen Workflow ohne menschliches Zutun regelmässig ausführen. Das ist genau, was wir für unseren Wetter-Bot brauchen!
Am besten lässt sich die für emoji-weather verwendete GitHub Action am konkreten Beispiel erklären. Unten abgebildet ist die YAML-Datei, die die Publikation der morgendlichen Wettervorhersage auslöst. Sie ist unter folgendem Pfad im Repository abgelegt (der Dateiname tut nichts weiter zur Sache, die Endung sollte aber yml sein): emoji-weather/.github/workflows/masto_morning.yml
Auf Zeile 1 ist der Name der Action definiert. Dieser erscheint im «Actions»-Tab und allfälligen Benachrichtigungen zur Action (zum Beispiel, wenn diese fehlschlägt). Zeilen 3 bis 7 definieren, wann die Action ausgeführt wird. Dabei decken Zeilen 4 und 5 die regelmässige Ausführung ab. Die Zeichenfolge ’24 4 * * *‘ ist Cronjob-Syntax und bedeutet: Ausführung an jedem Tag um 04:24 morgens. Webseiten wie crontab.guru helfen bei der richtigen Konfiguration. Wichtig: Die Zeit entspricht der UTC-Zeit, nicht einer irgendwie definierten lokalen Zeit. Im Beispiel habe ich zudem «24» gewählt, da ich mir denke, dass die GitHub-Server zu «geraden Zeiten» speziell ausgelastet sein dürften. Sowieso: GitHub macht beim Timing einen «best effort». Man sollte sich nicht darauf verlassen, dass die Action exakt zum definierten Zeitpunkt anläuft.
Zeile 12 definiert den Runner. Ich habe mich im Beispiel für ein Ubuntu-System entschieden, weil es für meinen Anwendungsfall keine Rolle spielt und Ubuntu wie oben angesprochen aktuell das günstigste System ist. In den Zeilen 14 bis 27 werden auf dem Runner der Repository-Inhalt ausgecheckt bzw. bezogen, Python 3.9 und dann alle notwendigen Zusatzpakete installiert. Damit ist der Setup-Teil abgeschlossen.
In den Zeilen 29 bis 48 erfolgt dann der eigentliche Aufruf unseres Python-Skripts emoji-weather-mastodon.py und anschliessend das Committen und Pushen einer Datei last-run.text in das Repository. Diese Datei wird bei jeder Ausführung des Python-Skripts durch dieses erzeugt. Sie enthält einen Timestamp der letzten Ausführung und dient lediglich dazu, dass die GitHub Action bis auf Widerruf ausgeführt wird. Denn: Ergeben sich durch eine GitHub Action keine Veränderungen im Repository, deaktiviert GitHub die Action nach einer vordefinierten Zeit – wenn ich mich richtig erinnere: nach 60 Tagen. last-run.text fungiert also als eine Art «keep alive».
Environment Variables und Repository Secrets
Im Block, der das Python-Skript aufruft, verstecken sich noch einige interessante Details:
In den Zeilen 31 bis 39 werden Umgebungsvariablen (environment variables) gesetzt. Hier definiere ich unter anderem, dass es sich – wenn das Python-Skript aus dieser Action heraus aufgerufen wird – um einen operational run handelt (Zeile 32) – im Gegensatz zu einem test run. Das Python-Skript verhält sich an unterschiedlichen Stellen anders, in Abhängigkeit vom run mode: Im Test-Modus werden die Wetterprognosen beispielsweise nicht über die «echten» Mastodon-Accounts gepostet, sondern nur über einen Test-Account, dem niemand folgt.
In den Zeilen 33 bis 37 werden diverse Credentials als Umgebungsvariablen abgespeichert. Dies erfolgt mit sogenannten Repository Secrets. Es ist schon fast ein trope: Wie viele öffentlich einsehbare GitHub-Repository mag es geben, in denen Nutzerinnen und Nutzer irgendwo in ihrem Quellcode Credentials (Passwörter, OAuth-Tokens und Ähnliches) hardcodiert und damit aus Versehen veröffentlicht haben? Kürzlich ist das auch der Organisation GitHub passiert… Best Practice um solche Sicherheitsrisiken zu vermeiden ist die Verwendung von Repository Secrets. Diese werden im Repository unter Settings → Secrets and variables → Actions erstellt. Man definiert einen Namen und den Inhalt des eigentlichen «Secret».
Die Umgebungsvariablen, die durch die GitHub Action gesetzt worden sind, werden dann zur Laufzeit des Python-Skripts durch dieses wie folgt ausgelesen:
Testing und letzte Worte
Das Python-Skript emoji-weather-mastodon.py ist so programmiert, dass es zu Testzwecken auch lokal ausgeführt werden kann. In diesem Fall erkennt das Skript selbständig, dass es im Modus local testing laufen soll. Die diversen Credentials holt es sich dann nicht aus Umgebungsvariablen (diese sind in diesem Kontext anders als auf dem GitHub-Runner ja gar nicht gesetzt), sondern mit import Secrets aus einer Custom-Python-Klasse. Diese ist nur in meiner lokalen Kopie des Repository vorhanden. Ganz wichtig: Damit das auch so bleibt und ich die Credentials nicht wie GitHub aus Versehen leake, wird der Eintrag Secrets.py in der Datei .gitignore eingefügt. Das verhindert, dass ich die Datei Secrets.py ins Repository auf GitHub pushen kann.
Das war der dritte und letzte Teil meiner Miniserie zum Wetter-Bot. Wie am Ende von Teil 2 versprochen resultieren aus der ganzen Fingerübung mit dem Wetter-Bot hoffentlich auch für Dich, geneigter Leser oder geneigte Leserin, transferable skills. Ich konnte ähnliche Mechanismen wie hier gezeigt auch schon im Projektkontext verwenden. Beispielsweise haben wir so schon Zeitreihen von Messwerten aus Webseiten (des Kunden) gescraped, für die keine API bestand. Oder wir haben automatisch die Nachführung und Publikation datengestützter Reports ausgelöst, wenn sich die darunterliegenden Daten verändert hatten.
Den Python-Code, die GitHub Actions und alles andere, was für das Funktionieren von emoji-weather notwendig ist, kannst Du auf GitHub anschauen.
Es würde mich freuen, wenn Du mit dem Knowhow etwas Interessantes anstellst. Noch viel mehr, wenn Du mich wissen lässt, was.
Entdecke mehr von digital.ebp.ch
Subscribe to get the latest posts sent to your email.