Hallo allerseits!
Heute möchten wir darüber sprechen, wie das Hotelreservierungsservice-Team von 
Ostrovok.ru das Problem des Wachstums von Mikroservices gelöst hat, dessen Aufgabe es ist, Informationen mit unseren Lieferanten auszutauschen. Über seine Erfahrung berichtet der 
unsterbliche DevOps-Teamleiter in Ostrovok.ru.
Der Microservice war zunächst klein und führte folgende Funktionen aus:
- eine Anfrage von einem lokalen Dienst annehmen;
- eine Anfrage an einen Partner stellen;
- normalisieren Sie die Antwort;
- Geben Sie das Ergebnis an den Abfragedienst zurück.
Mit der Zeit wuchs der Service jedoch zusammen mit der Anzahl der Partner und Anfragen an sie.
Als der Service wuchs, traten verschiedene Probleme auf. Verschiedene Lieferanten stellen ihre eigenen Regeln auf: Jemand begrenzt die maximale Anzahl von Verbindungen, jemand beschränkt Kunden auf weiße Listen.
Infolgedessen mussten wir die folgenden Probleme lösen:
- Es ist wünschenswert, mehrere feste externe IP-Adressen zu haben, damit Sie diese Partnern zum Hinzufügen zu weißen Listen zur Verfügung stellen können.
- haben einen einzigen Pool von Verbindungen zu allen Lieferanten, so dass bei der Skalierung unseres Microservices die Anzahl der Verbindungen minimal bleibt,
- Beenden Sie SSL und halten Sie keepalivean einem Ort, wodurch die Belastung der Partner selbst verringert wird.
Sie dachten lange nicht nach und fragten sich sofort, was sie wählen sollten: Nginx oder Haproxy.
Zuerst schwang das Pendel in Richtung Nginx, da ich mit seiner Hilfe die meisten Probleme im Zusammenhang mit HTTP / HTTPS löste und immer mit dem Ergebnis zufrieden war.
Das Schema war einfach: Es wurde eine Anfrage an unseren neuen Proxyserver auf Nginx mit einer Domäne der Form 
<partner_tag>.domain.local . In Nginx gab es eine 
map , in der 
<partner_tag> der Adresse des Partners entsprach. Eine Adresse wurde von 
map und 
proxy_pass wurde an diese Adresse 
proxy_pass .
Hier ist eine Beispielkarte, mit der wir die Domain analysieren und den Upstream aus der Liste auswählen:
 
Und so sieht " 
snippet.d/upstreams_map " aus:
 “one” “http://one.domain.net”; “two” “https://two.domain.org”; 
Hier haben wir 
server{} :
 server { listen 80; location / { proxy_http_version 1.1; proxy_pass $upstream_address$request_uri; proxy_set_header Host $upstream_host; proxy_set_header X-Forwarded-For ""; proxy_set_header X-Forwarded-Port ""; proxy_set_header X-Forwarded-Proto ""; } }  
Alles ist cool, alles funktioniert. Es ist möglich, diesen Artikel zu beenden, wenn nicht für eine Nuance.
Bei Verwendung von proxy_pass wird die Anforderung in der Regel unter Verwendung des HTTP / 1.0-Protokolls ohne 
keepalive direkt an die gewünschte Adresse gesendet und sofort nach Abschluss der Antwort geschlossen. Selbst wenn wir 
proxy_http_version 1.1 nichts ohne Upstream ( 
proxy_http_version ).
Was zu tun ist? Der erste Gedanke ist, alle Lieferanten in den Upstream zu bringen, wobei der Server die Adresse des Lieferanten ist, den wir benötigen, und in der 
map "tag" "upstream_name" behalten.
Fügen Sie eine weitere 
map um das Schema zu analysieren:
 
Und erstellen Sie 
upstreams mit Tag-Namen:
  upstream one { keepalive 64; server one.domain.com; } upstream two { keepalive 64; server two.domain.net; } 
Der Server selbst wurde geringfügig geändert, um das Schema zu berücksichtigen und anstelle der Adresse den Namen des Upstreams zu verwenden:
 server { listen 80; location / { proxy_http_version 1.1; proxy_pass $upstream_scheme$upstream_prefix$request_uri; proxy_set_header Host $upstream_host; proxy_set_header X-Forwarded-For ""; proxy_set_header X-Forwarded-Port ""; proxy_set_header X-Forwarded-Proto ""; } }  
Großartig. Die Lösung funktioniert, fügen Sie die 
keepalive Direktive in jedem Upstream hinzu, setzen Sie 
proxy_http_version 1.1 , - jetzt haben wir einen Verbindungspool und alles funktioniert wie es sollte.
Dieses Mal können Sie den Artikel definitiv beenden und Tee trinken gehen. Oder nicht?
Während wir Tee trinken, kann einer der Lieferanten die IP-Adresse oder die Adressgruppe unter derselben Domain (hi, Amazon) ändern, wodurch einer der Lieferanten auf dem Höhepunkt unserer Teeparty abfällt.
Was tun? Nginx hat eine interessante Nuance: Während des Neuladens kann es die Server im 
upstream an neue Adressen nüchtern machen und Datenverkehr auf sie übertragen. Im Allgemeinen auch eine Lösung. Werfen Sie 
cron reload nginx alle 5 Minuten und trinken Sie weiterhin Tee.
Trotzdem schien es mir eine mittelmäßige Entscheidung zu sein, und so begann ich, Haproxy schief anzusehen.
Haproxy kann DNS- 
dns resolvers angeben und den 
dns cache konfigurieren. Daher aktualisiert Haproxy den 
dns cache wenn die darin enthaltenen Einträge abgelaufen sind, und ersetzt Adressen für Upstream, wenn sie sich geändert haben.
Großartig! Jetzt liegt es an den Einstellungen.
Hier ist ein kurzes Konfigurationsbeispiel für Haproxy:
 frontend http bind *:80 http-request del-header X-Forwarded-For http-request del-header X-Forwarded-Port http-request del-header X-Forwarded-Proto capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 acl host_present hdr(host) -m len gt 0 use_backend %[req.hdr(host),lower,field(1,'.')] if host_present default_backend default resolvers dns hold valid 1s timeout retry 100ms nameserver dns1 1.1.1.1:53 backend one http-request set-header Host one.domain.com server one--one.domain.com one.domain.com:80 resolvers dns check backend two http-request set-header Host two.domain.net server two--two.domain.net two.domain.net:443 resolvers dns check ssl verify none check-sni two.domain.net sni str(two.domain.net) 
Diesmal scheint alles so zu funktionieren, wie es sollte. Das einzige, was ich an Haproxy nicht mag, ist die Komplexität der Konfigurationsbeschreibung. Sie müssen viel Text erstellen, um einen vorgelagerten Text hinzuzufügen. Aber Faulheit ist der Motor des Fortschritts: Wenn Sie nicht dasselbe schreiben möchten, schreiben Sie einen Generator.
Ich hatte bereits eine Karte von Nginx mit dem Format 
"tag" "upstream" , daher habe ich beschlossen, sie als Grundlage zu verwenden, ein Haproxy-Backend zu analysieren und basierend auf diesen Werten zu generieren.
 
Jetzt müssen wir nur noch einen neuen Host in nginx_map hinzufügen, den Generator starten und die fertige Haproxy-Konfiguration erhalten.
Das ist wahrscheinlich alles für heute. Dieser Artikel bezieht sich eher auf den einleitenden Artikel und widmete sich dem Problem der Auswahl einer Lösung und ihrer Integration in die aktuelle Umgebung.
Im nächsten Artikel werde ich ausführlicher darauf eingehen, auf welche Fallstricke wir bei der Verwendung von Haproxy gestoßen sind, welche Metriken sich für die Überwachung als nützlich erwiesen haben und welche genau im System optimiert werden sollten, um die maximale Leistung der Server zu erzielen.
Vielen Dank für Ihre Aufmerksamkeit, wir sehen uns!