Istio: Das Service-Mesh für verteilte Systeme

Ein Weg von A nach B

Wie kann man nun Istio für Content-Based-Routing, Load-Balancing oder Canary-Deployments nutzen?

Abb. 3: Bob v1 bekommt 80 Prozent Traffic, v2 nur 20 Prozent

Zunächst muss man die beiden Ziele mit DestinationRules definieren:

.DestinationRule für den BobService
----
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: BobService
spec:
host: BobService
subsets:
- name: Bob1
labels:
version: v1
- name: Bob2
labels:
version: Bob
----

Die beiden Subsets in der Definition bestimmen je eine Workload, die via Name (BobService) und Label (v1, v2) zu identifizieren sind. Im nächsten Schritt dienen die Regeln als Ziele für einen VirtualService:

.Asymetrisches Loadbalancing
----
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: Balance-Bob
spec:
hosts:
- BobService
http:
- route:
- destination:
host: BobService
subset: Bob1
weight: 80
- destination:
host: BobService
subset: Bob2
weight: 20
----

Die Dokumente kann man wie gewohnt via kubectl apply -f <datei> an Kubernetes senden, wo der Pilot sie aufgreift und die entsprechenden Proxies konfiguriert. Allerdings ist es dadurch auch möglich, eine ungültige Konfiguration zu erzeugen. Im obigen Beispiel ist das der Fall, wenn die Gewichte der einzelnen Routen nicht 100 Prozent ergeben. Nutzt man hingegen istioctl create oder istioctl replace um die Konfiguration zu übergeben, hilft Galley beim Validieren und gibt eine Fehlermeldung aus (seltsamerweise wird die ungültige Konfiguration aktuell trotzdem akzeptiert).

Auf eine ähnliche Weise kann der Verkehr für angemeldete Besucher oder einen bestimmten Browser an eine Version eines Dienstes geleitet werden und der restliche Verkehr an eine andere Version. Damit kann man beispielsweise Anfragen von Internet Explorer 6 an eine andere (alte) Version der Anwendung senden als den Verkehr von zeitgemäßen Browsern.

Die Fähigkeiten des Routing hören hier noch lange nicht auf. Es ist außerdem denkbar, den Datenverkehr zu duplizieren, um eine neue Version einer Applikation mit den (Live-)Daten zu testen.

Allerdings ist das auf Inhalten basierende Routing davon abhängig, was Envoy unterstützt. Aktuell sind das unter Anderem HTTP, HTTP/2, gRPC, Redis, MongoDB und DynamoDB. Es steht zu erwarten, dass in Zukunft noch mehr Protokolle hinzukommen.

Fehlertoleranz

Wie bereits erwähnt, müssen verteilte Systeme auf Ausfälle einzelner Komponenten oder des Verbindungsnetzwerks reagieren können.

Es ist möglich, über eine HTTP-Retry-Regel Istio anzuweisen, dass der Aufruf im Fehlerfall erneut geschehen soll:

.Retry-Logik in Istio
----
kind: VirtualService
metadata:
name: Bob-Retry-Service
spec:
hosts:
- BobService
http:
- route:
- destination:
host: BobService
retries:
attempts: 3
perTryTimeout: 2s
----

Das Beispiel versucht, den BobService bis zu drei Mal aufzurufen, jedes Mal mit einer Wartezeit von bis zu zwei Sekunden. Ebenso kann man über eine Circuit-Breaker-Regel Istio anweisen, Versuche das Backend zu erreichen für eine Zeit zu unterbinden, wenn bei vorherigen Aufrufen zu viele Fehler vorkamen.

Absichtlich Fehler machen

Istio bietet die genannten Mechanismen zum Schutz gegen Ausfälle an, aber letztlich muss die Anwendung doch darauf reagieren können. Um diese Reaktionen testen zu können, bietet Istio die Funktion an, Fehler in den Datenstrom zu injizieren.

.Injection von 25 Prozent 404-Fehlern 
----
kind: VirtualService
metadata:
name: Bob-Fehler-Service
spec:
hosts:
- BobService
http:
- fault:
abort:
percent: 25
httpStatus: 404
route:
- destination:
host: BobService
----

Man sieht, dass der aufrufende Service sich nicht alle Funktionen zur Fehlervermeidung sparen kann. Liefert der aufgerufene Dienst einen Fehler zurück, den Istio nicht korrigieren konnte, ist es trotzdem notwendig auf den 404-Code zu reagieren und entsprechenden Fallback-Code auszuführen (oder man reicht den Fehlercode an den Aufrufer durch).