Historique des errances de la documentation Haproxy, ou ce qu'il faut rechercher lors de sa configuration

Bonjour encore!

La dernière fois, nous avons parlé de choisir un outil dans Ostrovok.ru pour résoudre le problème de proxy d'un grand nombre de demandes vers des services externes, sans mettre personne en même temps. L'article s'est terminé par une sélection de Haproxy . Aujourd'hui, je partagerai les nuances auxquelles j'ai dû faire face lors de l'utilisation de cette solution.



Configuration haproxy


La première difficulté était que l'option maxconn est différente selon le contexte:


Par habitude, je n'ai réglé que la première option ( performance tuning ). Voici ce que dit la documentation sur cette option:
Définit le nombre maximal de connexions simultanées par processus à <numéro>. Il
est équivalent à l'argument de ligne de commande "-n". Les procurations cesseront d'accepter
connexions lorsque cette limite est atteinte.

Il semblerait que ce qui est nécessaire. Cependant, lorsque je suis tombé sur le fait que les nouvelles connexions au proxy ne se sont pas immédiatement déroulées, j'ai commencé à lire la documentation plus attentivement, et là j'ai déjà trouvé le deuxième paramètre ( bind options ):
Limite les sockets à ce nombre de connexions simultanées. Étranger
les connexions resteront dans le carnet de commandes du système jusqu'à ce qu'une connexion soit établie.
libéré. Si non spécifié, la limite sera la même que le maxconn du frontend.

Alors, frontends maxconn y, puis recherchons les frontends maxconn :
Fixer le nombre maximum de connexions simultanées sur un frontend
...
Par défaut, cette valeur est définie sur 2000.

Super, ce dont vous avez besoin. Ajoutez à la configuration:

 global daemon maxconn 524288 ... defaults mode http maxconn 524288 

Le gag suivant était que Haproxy est un thread unique. Je suis très habitué au modèle de Nginx, donc cette nuance m'a toujours déprimé. Mais ne désespérez pas - Willy Tarreau ( développeur Haproxy ) a compris ce qu'il faisait, il a donc ajouté l'option - nbproc .

Cependant, directement dans la documentation, il est dit:
UTILISATION DE PLUSIEURS PROCESSUS
EST PLUS DIFFICILE À DÉBOGIR ET EST VRAIMENT DÉCOURAGÉ.
Cette option peut vraiment vous faire mal à la tête si vous avez besoin de:

  • limiter le nombre de requêtes / connexions aux serveurs (puisque vous aurez déjà non pas un processus avec un compteur, mais plusieurs processus, et chacun a son propre compteur);
  • Collecter des statistiques à partir du socket de gestion Haproxy
  • activer / désactiver les backends via la prise de contrôle;
  • ... peut-être autre chose. ¯ \ _ (ツ) _ / ¯

Néanmoins, les dieux nous ont donné des processeurs multicœurs, donc je voudrais les utiliser au maximum. Dans mon cas, il y avait quatre cœurs dans deux cœurs physiques. Pour Haproxy, j'ai sélectionné le premier noyau, et il ressemblait à ceci:

  nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 

En utilisant cpu-map, nous lions les processus Haproxy à un noyau spécifique. Le planificateur du système d'exploitation n'a plus besoin de penser à l'endroit où planifier Haproxy, ce qui maintient le content switch frais et le cache du processeur au chaud.

Il existe de nombreux tampons, mais pas dans notre cas


  • tune.bufsize - dans notre cas, il n'était pas nécessaire de l'exécuter, mais si vous avez des erreurs avec le code 400 (Bad Request) , alors c'est probablement votre cas.
  • tune.http.cookielen - si vous distribuez de gros cookies aux utilisateurs, alors, afin d'éviter des dommages lors de la transmission sur le réseau, il peut être judicieux d'augmenter également ce tampon.
  • tune.http.maxhdr est une autre source possible de 400 codes de réponse si vous avez trop d'en-têtes.

Considérez maintenant les choses de niveau inférieur


tune.rcvbuf.client / tune.rcvbuf.server , tune.sndbuf.client / tune.sndbuf.server - la documentation indique ce qui suit:
Il ne devrait normalement jamais être défini, et la taille par défaut (0) permet au noyau de régler automatiquement cette valeur en fonction de la quantité de mémoire disponible.

Mais pour moi, l'évidence est meilleure que l'implicite, j'ai donc forcé les valeurs de ces options pour être sûr de demain.

Et un autre paramètre qui n'est pas lié aux tampons, mais très important est tune.maxaccept .
Définit le nombre maximal de connexions consécutives qu'un processus peut accepter dans un
ligne avant de passer à un autre travail. En mode process unique, des nombres plus élevés
donner de meilleures performances à des taux de connexion élevés. Cependant en multi-processus
modes, il est généralement préférable de garder un peu d’équité entre les processus
augmenter les performances.

Dans notre cas, un grand nombre de demandes de proxy sont générées, j'ai donc augmenté cette valeur pour accepter plus de demandes à la fois. Néanmoins, comme le dit la documentation, il vaut la peine de tester qu'en mode multi-thread la charge est répartie aussi uniformément que possible entre les processus.

Tous les paramètres ensemble:

  tune.bufsize 16384 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432 

Ce qui n'arrive jamais, ce sont les temps morts. Que ferions-nous sans eux?


  • timeout connect - temps pour établir une connexion avec le backend. Si la connexion avec le backend n'est pas très bonne, il est préférable de la désactiver d'ici ce délai jusqu'à ce que le réseau revienne à la normale.
  • timeout client - timeout pour la transmission des premiers octets de données. Cela permet de déconnecter ceux qui font des demandes «en réserve».

Kulstory sur le client HTTP dans Go
Go a un client HTTP régulier qui a la capacité de conserver un pool de connexions aux serveurs. Une histoire intéressante s'est donc produite, à laquelle le délai d'attente et le pool de connexions décrits ci-dessus dans le client HTTP ont pris part. Une fois qu'un développeur s'est plaint qu'il avait périodiquement 408 erreurs d'un proxy. Nous avons examiné le code client et y avons vu la logique suivante:

  • Nous essayons de prendre une connexion établie gratuitement à partir de la piscine;
  • si cela ne fonctionne pas, lancez l'installation d'une nouvelle connexion dans goroutine;
  • vérifiez à nouveau la piscine;
  • s'il y en a dans la piscine - nous le prenons et mettons le nouveau dans la piscine, sinon - utilisez le nouveau.

Vous avez déjà compris ce qu'est le sel?

Si le client a établi une nouvelle connexion mais ne l'a pas utilisée, le serveur la ferme au bout de cinq secondes et l'affaire est terminée. Le client intercepte cela uniquement lorsqu'il obtient déjà la connexion du pool et essaie de l'utiliser. Il convient de garder cela à l'esprit.

  • timeout server - le temps maximum pour attendre une réponse du serveur.
  • timeout client-fin / timeout server-fin - nous nous protégeons ici des connexions semi-fermées afin de ne pas les accumuler dans la table du système d'exploitation.
  • timeout http-request est l'un des délais d' expiration les plus appropriés. Vous permet de couper les clients lents qui ne peuvent pas faire de demande HTTP dans le temps imparti pour eux.
  • timeout http-keep-alive - spécifiquement dans notre cas, si une connexion keep-alive se bloque sans demandes pendant plus de 50 secondes, alors très probablement quelque chose s'est mal passé et la connexion peut être fermée, libérant ainsi de la mémoire pour quelque chose de nouveau la lumière.

Tous les temps morts ensemble:

 defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s 

Journalisation Pourquoi est-ce si difficile?


Comme je l'ai écrit plus tôt, le plus souvent dans mes décisions j'utilise Nginx, donc je suis gâté par sa syntaxe et la simplicité de modification des formats de journaux. J'ai particulièrement aimé la fonctionnalité killer - formater les journaux sous forme de json, puis les analyser avec n'importe quelle bibliothèque standard.

Qu'avons-nous chez Haproxy? Il existe une telle opportunité, vous seul pouvez écrire exclusivement dans syslog, et la syntaxe de configuration est légèrement plus enveloppante.
Je vais vous donner un exemple de configuration avec des commentaires:

 #  ,     ,    (   # error.log  nginx) log 127.0.0.1:2514 len 8192 local1 notice emerg #    -  access.log log 127.0.0.1:2514 len 8192 local7 info 

Une douleur particulière est causée par de tels moments:
  • noms de variables courts, et en particulier leurs combinaisons comme% HU ou% fp
  • le format ne peut pas être divisé en plusieurs lignes, vous devez donc écrire footcloth sur une seule ligne. difficile d'ajouter / supprimer des éléments nouveaux / inutiles
  • pour que certaines variables fonctionnent, elles doivent être explicitement déclarées via l'en-tête de demande de capture

Par conséquent, pour obtenir quelque chose d'intéressant, vous devez avoir un tel couvre-pied:

 log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}' 

Eh bien, il semblerait, de petites choses, mais sympa


J'ai décrit le format du journal ci-dessus, mais pas si simple. Pour y déposer certains éléments, tels que:

  • http_host
  • http_referer,
  • http_user_agent,

vous devez d'abord capturer ces données à partir de la demande ( capture ) et les placer dans un tableau de valeurs capturées.

Voici un exemple:

 capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 

En conséquence, nous pouvons désormais accéder aux éléments dont nous avons besoin de cette façon:
%[capture.req.hdr(N)] , où N est le numéro de séquence de la définition du groupe de capture.
Dans l'exemple ci-dessus, l'en-tête Host sera au numéro 0 et le User-Agent sera au numéro 2.

Haproxy a une particularité: il résout les adresses DNS des backends au démarrage et, s'il ne peut résoudre aucune des adresses, tombe la mort du brave.

Dans notre cas, ce n'est pas très pratique, car il y a beaucoup de backends, nous ne les gérons pas, et il vaut mieux obtenir 503 de Haproxy que l'ensemble du serveur proxy refusera de démarrer à cause d'un seul fournisseur. L'option suivante nous aide avec ceci: init-addr .

Une ligne tirée directement de la documentation nous permet de passer en revue toutes les méthodes disponibles pour résoudre une adresse et, dans le cas d'un fichier, de simplement reporter cette question à plus tard et de continuer:

 default-server init-addr last,libc,none 

Et enfin, mon préféré: la sélection du backend.
La syntaxe de la configuration de sélection du backend Haproxy est connue de tous:

 use_backend <backend1_name> if <condition1> use_backend <backend2_name> if <condition2> default-backend <backend3> 

Mais bon mot, ce n'est pas très bien. J'ai déjà décrit tous les backends de manière automatisée (voir l' article précédent ), il serait possible de générer use_backend ici use_backend , la mauvaise affaire n'est pas délicate, mais je ne le voulais pas. En conséquence, une autre méthode a été trouvée:

  capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 #   host_present      Host acl host_present hdr(host) -m len gt 0 #    ,     use_backend %[req.hdr(host),lower,field(1,'.')] if host_present #      ,    default_backend default backend default mode http server no_server 127.0.0.1:65535 

Ainsi, nous avons normalisé les noms des backends et des URL par lesquels vous pouvez y accéder.

Eh bien, compilons maintenant à partir des exemples ci-dessus dans un fichier:

Version complète de la configuration
  global daemon maxconn 524288 nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 tune.bufsize 16384 tune.comp.maxlevel 1 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432 stats socket /run/haproxy.sock mode 600 level admin log /dev/stdout local0 debug defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s default-server init-addr last,libc,none log 127.0.0.1:2514 len 8192 local1 notice emerg log 127.0.0.1:2514 len 8192 local7 info log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}' 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 backend default mode http server no_server 127.0.0.1:65535 resolvers dns hold valid 1s timeout retry 100ms nameserver dns1 127.0.0.1:53 


Merci à ceux qui ont lu jusqu'au bout. Mais ce n'est pas tout. La prochaine fois, nous examinerons les éléments de niveau inférieur liés à l'optimisation du système lui-même, dans lequel Haproxy fonctionne, afin que lui et son système d'exploitation soient à l'aise ensemble, et qu'il y ait suffisamment de fer pour tout le monde.

A très bientôt!

Source: https://habr.com/ru/post/fr438966/


All Articles