meow: Migration auf Redis

Das Monitoring-System meow erlaubt es, eigens konfigurierte Endpunkte mit einer Liveness Probe zu monitoren. Grundsätzlich ist meow als verteiltes System konzipiert, verfügt über eine HTTP-Schnittstelle und liesse sich so nicht nur auf einer virtuellen Maschine (IaaS) sondern auch als Cloud Function (PaaS) ausführen.

Einige Aspekte von meow stehen dem aber im Wege: So wird die Konfiguration über das Dateisystem in einer CSV-Datei verwaltet. Eine elegantere Lösung wäre es beispielsweise, die Konfiguration in Redis zu verwalten.

Die Voraussetzung für den folgenden Arbeitsauftrag ist, dass Sie mit Redis arbeiten können sowie meow in Betrieb genommen und konfiguriert haben. Hierzu gibt es folgende Hilfestellungen:

Arbeiten Sie auf Ihrem persönlichen Fork vom meow-Repository. Sichern Sie Ihren Code regelmässig in Ihrem persönlichen Repository. Für eine Rückmeldung zu Ihrem Code können Sie einen Pull Request einreichen.

Endpunkt-Konfiguration mit Redis

Als Repetition können Sie noch einmal die Redis-Playlist betrachten, insbesondere das Video zu den Endpunkten als Hashes.

Client-Library installieren

Wenn Sie die Endpunkte in Redis konfiguriert haben, soll als nächstes der Zugriff auf die Redis-Datenbank vom Go-Code aus bewerkstelligt werden. Redis bietet verschiedene Client-Libraries für Go. Der populärste ist Go Redis. Diese kann im meow-Arbeitsverzeichnis in einem Terminal folgendermassen installiert werden:

go get github.com/redis/go-redis/v9

Die Version v9 passt zur lokal vorinstallierten Redis-Version 7. (Für Redis-Version 6 müssten Sie v8 statt v9 verwenden.)

Redis-URL konfigurierbar machen

Damit eine Verbindung zu Redis aufgenommen werden kann, muss zuerst eine URL definiert werden. Lokal lautet diese jeweils localhost:6379. In einer produktiven Umgebung soll die URL jedoch konfigurierbar sein, etwa über eine Umgebungsvariable. (So ähnlich funktioniert das bereits mit der Umgebungsvariable CONFIG_URL in der probe-Komponente; siehe probeCmd/probe.go ganz oben in der main-Funktion.)

Implementieren Sie den Zugriff auf die Umgebungsvariable REDIS_URL entsprechend in configCmd/config.go.

Verbindung zu Redis aufnehmen

Eine Verbindung zu Redis kann folgendermassen erstellt werden:

rdb := redis.NewClient(&redis.Options{
    Addr:     [Redis URL from environment variable],
    Password: "", // no password set
    DB:       0,  // use default DB
})

Sie können den Code direkt in der main-Funktion von configCmd/config.go einfügen. Die Variable rdb vom Typ *redis.Client bietet Ihnen nun die bekannten Redis-Befehle als Methoden an, beispielsweise:

rdb.Set("key", "value", 0) // 0 as expiration value (never expires)
value := rdb.Get("key")
fmt.Println(value) // prints "value"

Funktionssignaturen anpassen

Die Funktionen getEndpoint, postEndpoint und getEndpoints ‒ sowie deleteEndpoint, sofern Sie die Zusatzaufgabe gemacht haben ‒ benötigen die Redis-Verbindung als zusätzlichen Parameter. Ergänzen Sie die Parameterlisten entsprechend (Parametername: beispielsweise rdb, Datentyp: *redis.Client).

Vorher (Beispiel):

func getEndpoints(w http.ResponseWriter, r *http.Request)

Nachher (Beispiel):

func getEndpoints(w http.ResponseWriter, r *http.Request, rdb *redis.Client)

Sie können nun innerhalb dieser Funktionen auf die Redis-Datenbank zugreifen.

Führen Sie im Terminal noch den Befehl go mod tidy aus, damit alle Abhängigkeiten korrekt aufgelöst werden.

Datenzugriffe implementieren

Mithilfe des KEYS-Befehls erhalten Sie eine Liste aller Schlüssel anhand eines gegebenen Musters. In der Musterlösung habe ich das Präfix endpoint: verwendet; Sie können sich somit alle Endpunkte mit dem Redis-Befehl KEYS endpoint:* auflisten lassen.

Funktion getEndpoints anpassen

Im Go-Code können Sie hierzu die Methode rdb.Keys verwenden (getEndpoints-Funktion):

keys, err := rdb.Keys("endpoint:*").Result()
if err != nil {
    log.Printf("fetch keys by pattern endpoint:*: %v", err)
    w.WriteHeader(http.StatusInternalServerError)
}
for _, key := range keys {
    fmt.Println(key) // TODO: replace with calls to rdb.HGet
}

In der unteren Schleife können Sie nun mittels rdb.HGet auf die einzelnen Felder zugreifen, beispielsweise:

identifier, err := rdb.HGet(key, "identifier").Result()

Wobei identifier der gesuchte Wert und err ein möglicherweise auftretender Fehler ist.

Alternativ können Sie sich mittels rdb.HGetAll eine Map zurückgeben lassen. (H steht bekanntlich als Präfix für “Hash”, was nichts anderes als eine Map ist.):

fields, err := rdb.HGetAll(key).Result()

In beiden Fällen müssen Sie den Fehler err angemessen behandeln. Den payload erstellen Sie anschliessend anhand der Informationen aus Redis, wobei Sie den bestehenden Code anpassen müssen.

Weitere Funktionen

Für die weiteren Funktionen getEndpoint (Singular) und postEndpoint (schreibender Zugriff) benötigen Sie neben den bereits bekannten Redis-Funktionen noch HSET, um einen Hash zu erstellen (rdb.HSet). Als Key können Sie das Präfix endpoint: mit dem Identifier kombinieren.

Änderungen testen

Testen Sie Ihre Anpassungen, indem Sie die config-Komponente folgendermassen starten:

REDIS_URL=localhost:6379 go run configCmd/config.go

Falls Sie die kostenlose Redis-Instanz aus der Cloud verwenden, müssen Sie die URL entsprechend anpassen.

Zum Testen können Sie die curl-Befehle verwenden, die Sie im entsprechenden Aufgabenblock umgesetzt haben bzw. dem entsprechenden Video entnehmen können.

Code bereinigen

Wenn alles funktioniert, können Sie den bestehenden Code bereinigen, indem Sie Dateizugriffe, das Flag -file usw. entfernen. Testen Sie das Programm nach den Änderungen, damit Sie nicht versehentlich zu viel Code entfernen.