Bonjour à tous!
Aujourd'hui, nous voulons parler de la manière dont l'équipe du service de réservation d'hôtels
Ostrovok.ru a résolu le problème de la croissance des microservices, dont la tâche est d'échanger des informations avec nos fournisseurs. À propos de son expérience raconte
éternel , DevOps Team Lead in Ostrovok.ru.
Au début, le microservice était petit et remplissait les fonctions suivantes:
- accepter une demande d'un service local;
- faire une demande à un partenaire;
- normaliser la réponse;
- renvoyer le résultat au service d'interrogation.
Cependant, le temps a passé, le service a grandi avec le nombre de partenaires et de demandes.
À mesure que le service se développait, divers types de problèmes ont commencé à apparaître. Différents fournisseurs proposent leurs propres règles: quelqu'un limite le nombre maximum de connexions, quelqu'un limite les clients à des listes blanches.
En conséquence, nous avons dû résoudre les problèmes suivants:
- il est souhaitable de disposer de plusieurs adresses IP externes fixes afin de pouvoir les fournir à des partenaires pour les ajouter aux listes blanches,
- disposer d'un pool unique de connexions avec tous les fournisseurs afin que lors de la mise à l'échelle de notre microservice, le nombre de connexions reste minimal,
- mettre fin à SSL et garder
keepalive
en un seul endroit, réduisant ainsi la charge sur les partenaires eux-mêmes.
Ils n'ont pas réfléchi longtemps et se sont immédiatement demandé quoi choisir: Nginx ou Haproxy.
Au début, le pendule a basculé vers Nginx, car j'ai résolu la plupart des problèmes associés à HTTP / HTTPS avec son aide et j'ai toujours été satisfait du résultat.
Le schéma était simple: une demande a été faite à notre nouveau serveur proxy sur Nginx avec un domaine de la forme
<partner_tag>.domain.local
, dans Nginx il y avait une
map
où
<partner_tag>
correspondait à l'adresse du partenaire. Une adresse a été extraite de la
map
et
proxy_pass
été créé pour cette adresse.
Voici un exemple de
map
avec laquelle nous analysons le domaine et sélectionnons l'amont dans la liste:
Et voici à quoi ressemble «
snippet.d/upstreams_map
»:
“one” “http://one.domain.net”; “two” “https://two.domain.org”;
Ici, nous avons le
server{}
-
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 ""; } }
Tout est cool, tout fonctionne. Il est possible de terminer cet article, sinon pour une nuance.
Lorsque vous utilisez proxy_pass, la demande va directement à l'adresse souhaitée, en règle générale, en utilisant le protocole HTTP / 1.0 sans
keepalive
et se ferme immédiatement une fois la réponse terminée. Même si nous
proxy_http_version 1.1
, rien ne changera sans en amont (
proxy_http_version ).
Que faire La première pensée est de mettre tous les fournisseurs en amont, où le serveur sera l'adresse du fournisseur dont nous avons besoin, et dans la
map
garder
"tag" "upstream_name"
.
Ajoutez une autre
map
pour analyser le schéma:
Et créez des
upstreams
avec des noms de balises:
upstream one { keepalive 64; server one.domain.com; } upstream two { keepalive 64; server two.domain.net; }
Le serveur lui-même est légèrement modifié pour prendre en compte le schéma et utiliser le nom de l'amont au lieu de l'adresse:
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 ""; } }
Super. La solution fonctionne, ajoutez la directive
keepalive
à chaque amont, définissez
proxy_http_version 1.1
, - nous avons maintenant un pool de connexions, et tout fonctionne comme il se doit.
Cette fois, vous pouvez certainement terminer l'article et aller boire du thé. Ou pas?
Après tout, pendant que nous buvons du thé, l'un des fournisseurs peut changer l'adresse IP ou le groupe d'adresses sous le même domaine (salut, Amazon), ainsi l'un des fournisseurs peut tomber à la hauteur même de notre thé.
Eh bien, que faire? Nginx a une nuance intéressante: lors du rechargement, il peut dégriser les serveurs à l'intérieur en
upstream
vers de nouvelles adresses et y mettre du trafic. En général, aussi une solution. Jetez
cron reload nginx
toutes les 5 minutes et continuez à boire du thé.
Mais cela me semblait encore une décision médiocre, alors j'ai commencé à regarder avec mépris vers Haproxy.
Haproxy a la possibilité de spécifier des
dns resolvers
et de configurer le
dns cache
. Ainsi, Haproxy mettra à jour le
dns cache
-
dns cache
si les entrées qu'il contient ont expiré et remplacera les adresses en amont si elles ont changé.
Super! Maintenant, c'est aux paramètres.
Voici un court exemple de configuration pour 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)
Tout semble fonctionner comme il se doit cette fois. La seule chose que je n'aime pas dans Haproxy est la complexité de la description de la configuration. Vous devez créer beaucoup de texte pour en ajouter un fonctionnant en amont. Mais la paresse est le moteur du progrès: si vous ne voulez pas écrire la même chose, écrivez un générateur.
J'avais déjà une carte de Nginx avec le format
"tag" "upstream"
, j'ai donc décidé de la prendre comme base, d'analyser et de générer un backend haproxy basé sur ces valeurs.
Maintenant, tout ce dont nous avons besoin est d'ajouter un nouvel hôte dans nginx_map, de démarrer le générateur et d'obtenir la configuration haproxy prête.
C'est probablement tout pour aujourd'hui. Cet article se réfère plus à l'introduction et a été consacré au problème du choix d'une solution et de son intégration dans l'environnement actuel.
Dans le prochain article, je parlerai plus en détail des pièges que nous avons rencontrés lors de l'utilisation de Haproxy, des mesures qu'il s'est avéré utile de surveiller et de ce qui devrait être optimisé exactement dans le système afin d'obtenir les performances maximales des serveurs.
Merci à tous pour votre attention, à bientôt!