meow: Migration auf Valkey

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 du mit Valkey
arbeiten kannst sowie meow in Betrieb genommen und konfiguriert hast. Hierzu gibt es folgende Hilfestellungen:

Arbeiten auf deinem persönlichen Fork vom meow-Repository. Sichere deinen Code regelmässig im persönlichen Repository. Für eine Rückmeldung zu deinem Code kannst du einen Pull Request einreichen.

Endpunkt-Konfiguration mit Valkey

Als Repetition können Sie noch einmal die Redis-Playlist (Valkey ist ein Fork von Redis, aber genau gleich zu bedienen.) betrachten, insbesondere das Video zu den Endpunkten als Hashes.

Client-Library installieren

Wenn du die Endpunkte in Valkey konfiguriert hast, soll als nächstes der Zugriff auf die Valkey-Datenbank vom Go-Code aus bewerkstelligt werden. Valkey bietet die Library valkey-go für den Zugriff von Go aus an. Diese kann im meow-Arbeitsverzeichnis in einem Terminal folgendermassen installiert werden:

go get github.com/valkey-io/valkey-go

Valkey-URL konfigurierbar machen

Damit eine Verbindung zu Redis aufgenommen werden kann, muss zuerst eine URL definiert werden. Diese soll über eine Umgebungsvariable konfigurierbar sein. So ähnlich funktioniert das bereits mit der Umgebungsvariable CONFIG_URL in der probe-Komponente; siehe cmd/probe/main.go ganz oben in der main-Funktion.

Implementieren Sie den Zugriff auf die Umgebungsvariable VALKEY_URL entsprechend in cmd/config/main.go.

Verbindung zu Redis aufnehmen

Eine Verbindung zu Valkey kann folgendermassen erstellt werden:

options := valkey.ClientOption{
    InitAddress: []string{"valkey.frickelcloud.ch:6379"},
    SelectDB:    0, // TODO: use your number
}
client, err := valkey.NewClient(options)

Die Datenbank-Nummer fÜr SelectDB wird im Unterricht kommuniziert. (Alle haben ihre eigene Datenbank.)

Du kannst den Code direkt in der main-Funktion von cmd/config/main.go einfügen. Die Variable client vom Typ valkey.Client bietet nun die bekannten Valkey-Befehle an, wobei das Builder-Pattern zum Einsatz kommt, beispielsweise:

// SET purpose=meow
if err = client.Do(ctx, client.B().Set().Key("purpose").Value("meow").Build()).Error(); err != nil {
    log.Fatalf("set purpose=meow: %v", err)
}

// GET purpose
result, err := client.Do(ctx, client.B().Get().Key("purpose").Build()).AsBytes()
if err != nil {
    log.Fatalf("get purpose: %v", err)
}
fmt.Println(string(result))

Funktionssignaturen anpassen

Die Funktionen getEndpoint, postEndpoint und getEndpoints ‒ sowie deleteEndpoint, sofern du die Zusatzaufgabe gemacht hast ‒ benötigen die Valkey-Verbindung als zusätzlichen Parameter. Ergänze die Parameterlisten entsprechend (Parametername: beispielsweise vk, Datentyp: valkey.Client).

Vorher (Beispiel):

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

Nachher (Beispiel):

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

Du kannst nun innerhalb dieser Funktionen auf die Valkey-Datenbank zugreifen.

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

Datenzugriffe implementieren

Mithilfe des KEYS-Befehls erhälst du eine Liste aller Schlüssel anhand eines gegebenen Musters. In der Musterlösung habe ich das Präfix endpoint: verwendet; man kann somit alle Endpunkte mit dem Keys-Befehl KEYS endpoint:* auflisten lassen.

Funktion getEndpoints anpassen

Mithilfe der Keys()-Methode kann man alle Schlüssel in Erfahrung bringen, die auf ein bestimmtes Muster passen:

keys, err := client.Do(ctx, client.B().Keys().Pattern("endpoints:*").Build()).AsStrSlice()
if err != nil {
    log.Fatalf("get keys for endpoints:*: %v", err)
}
fmt.Println(keys)

Die komplette Map zu einem Key, z.B. zu endpoints:m346 erhält man dann folgendermassen:

kvs, err := client.Do(ctx, client.B().Hgetall().Key("endpoints:m346").Build()).AsStrMap()
if err != nil {
    log.Fatalf("hget endpoints:m346: %v", err)
}
fmt.Println(kvs)

In allen Fällen muss der Fehler err angemessen behandelt werden. Den payload erstellt man anschliessend anhand der Informationen aus Valkey, wobei der bestehende Code angepasst werden muss.

Weitere Funktionen

Für die weiteren Funktionen getEndpoint (Singular) und postEndpoint (schreibender Zugriff) wird neben den bereits bekannten Valkey-Funktionen noch HSET benötigt, um einen Hash zu erstellen. Als Key kann man das Präfix endpoint: mit dem Identifier kombinieren.

Änderungen testen

Teste deine Anpassungen, indem du die config-Komponente folgendermassen startest:

VALKEY_URL=valkey.frickelbude.ch go run cmd/config/main.go

Zum Testen kannst du die curl-Befehle verwenden, die du im letzten Aufgabenblock umgesetzt hast.

Code bereinigen

Wenn alles funktioniert, kannst du den bestehenden Code bereinigen, indem du Dateizugriffe, das Flag -file usw. entfernst. Testen das Programm nach den Änderungen, damit du nicht versehentlich zu viel Code entfernst.