5. Limites des descripteurs de fichiers
Afin de garantir que toutes les connexions entrantes soient correctement servies, HAProxy calcule au moment du chargement le nombre total de descripteurs de fichiers nécessaires durant la durée de vie du processus. Un processus Unix classique reçoit généralement 1024 descripteurs de fichiers par défaut, et un processus privilégié peut augmenter lui-même cette limite. C’est une des raisons de lancer HAProxy en tant que root et de lui permettre d’ajuster cette limite. La limite par défaut de 1024 descripteurs de fichiers permet approximativement 500 connexions simultanées. Ce calcul repose sur le paramètre global maxconn, qui limite le nombre total de connexions par processus, le nombre d’écouteurs, le nombre de serveurs ayant un contrôle d’état activé, les vérifications d’agent, les pairs, les enregistreurs et éventuellement quelques autres exigences techniques. Une estimation simple de ce nombre consiste à doubler la valeur de maxconn et à ajouter quelques dizaines pour obtenir une approximation du nombre de descripteurs de fichiers nécessaires.
HAProxy ne savait initialement pas calculer cette valeur, et il était nécessaire de la spécifier à l’aide de l’option « ulimit-n » dans la section globale. C’est pourquoi, même aujourd’hui, de nombreuses configurations incluent encore cette option. Malheureusement, cette valeur était souvent mal calculée, entraînant des échecs de connexion lorsque la limite maxconn était atteinte, au lieu de limiter les connexions entrantes en attendant la disponibilité des ressources nécessaires. Pour cette raison, il est important de supprimer toute option « ulimit-n » résiduelle provenant de versions très anciennes.
Augmenter le nombre de descripteurs de fichiers pour accepter des charges modérées est obligatoire, mais nécessite des ajustements spécifiques au système d’exploitation. Tout d’abord, le système de sondage select() est limité à 1024 descripteurs de fichiers. En réalité, sur Linux, il était autrefois capable de gérer davantage, mais certains systèmes d’exploitation livrent des politiques SELinux excessivement restrictives interdisant l’utilisation de select() avec plus de 1024 descripteurs de fichiers. HAProxy refuse désormais de démarrer dans ce cas afin d’éviter tout problème à l’exécution. Sur tous les systèmes d’exploitation pris en charge, poll() est disponible et ne souffre pas de cette limitation. Il est automatiquement sélectionné, donc aucune action n’est requise pour obtenir une configuration fonctionnelle. Toutefois, poll() devient très lent lorsque le nombre de descripteurs de fichiers augmente. Bien que HAProxy fasse tout son possible pour limiter cet impact sur les performances (par exemple grâce à la mise en cache interne des descripteurs de fichiers et au traitement par lots), une règle empirique consiste à dire qu’utiliser poll() avec plus d’un millier de connexions simultanées consommera beaucoup de CPU.
Pour les systèmes Linux basés sur les noyaux 2.6 et ultérieurs, l’appel système epoll() sera utilisé. Il s’agit d’un mécanisme bien plus évolutif, fondé sur des rappels dans le noyau, garantissant un temps de réveil constant, quel que soit le nombre de descripteurs de fichiers surveillés. Il est utilisé automatiquement lorsqu’il est détecté, à condition que HAProxy ait été compilé pour l’une des variantes Linux. Sa présence et son support peuvent être vérifiés à l’aide de la commande « HAProxy -vv ».
Pour les systèmes BSD qui le supportent, kqueue() est disponible en tant qu’alternative. Il est bien plus rapide que poll() et légèrement plus rapide que epoll(), grâce à sa gestion par lots des modifications. Au moins FreeBSD et OpenBSD le supportent. Tout comme pour epoll() sous Linux, son support et sa disponibilité sont indiqués dans la sortie de la commande « HAProxy -vv ».
Disposer d’un bon poller est une chose, mais il est obligatoire que le processus puisse atteindre les limites. Lorsque HAProxy démarre, il définit immédiatement les limites des descripteurs de fichiers du nouveau processus et vérifie si cette opération réussit. En cas d’échec, il le signale avant le fork afin que l’administrateur puisse détecter le problème. Tant que le processus est lancé en tant que root, il ne devrait pas y avoir de raison que ce paramétrage échoue. Toutefois, il peut échouer si le processus est lancé par un utilisateur non privilégié. Si une raison impérieuse existe pour ne pas lancer HAProxy en tant que root (par exemple : lancé par des utilisateurs finaux ou par un compte dédié à une application), alors l’administrateur système peut augmenter la limite des descripteurs de fichiers pour cet utilisateur spécifique. L’efficacité de ce paramétrage peut être vérifiée en exécutant « ulimit -n » depuis la ligne de commande de l’utilisateur. Elle doit refléter la nouvelle limite.
Avertissement : lorsque les limites d’un utilisateur non privilégié sont modifiées dans son compte, il est fréquent que ces valeurs ne soient prises en compte que lors de la connexion de l’utilisateur, et non du tout dans certains scripts exécutés au démarrage du système ou dans des crontabs. Cela dépend entièrement du système d’exploitation ; veillez à vérifier la commande « ulimit -n » avant de lancer haproxy dans ce cas. Il est généralement conseillé de ne jamais lancer haproxy en tant qu’utilisateur non privilégié dans un environnement de production. Une autre bonne raison est qu’il empêche haproxy d’activer certaines protections de sécurité.
Une fois certain que le système autorise le processus HAProxy à utiliser le nombre demandé de descripteurs de fichiers, deux nouvelles limites propres au système peuvent apparaître. La première est la limite globale du nombre de descripteurs ouverts sur le système, tous processus confondus. Lorsqu’elle est atteinte, accept() ou socket() renvoie généralement ENFILE. La seconde est la limite stricte par processus, qui empêche setrlimit() de fixer une valeur supérieure. Ces limites dépendent fortement du système d’exploitation. Sous Linux, la limite globale est fixée au démarrage en fonction de la mémoire et peut être modifiée avec le sysctl “fs.file-max”. La limite stricte par processus vaut 1048576 par défaut et peut être modifiée avec le sysctl “fs.nr_open”.
Les limites des descripteurs de fichiers peuvent être observées sur un processus en cours d’exécution lorsque celles-ci sont trop faibles. L’outil strace signalera que les appels système accept() et socket() retournent “-1 EMFILE” lorsque les limites du processus ont été atteintes. Dans ce cas, il suffit de relever la valeur de “ulimit-n” (ou de la supprimer) pour résoudre le problème. Si ces appels système retournent “-1 ENFILE”, cela signifie que les limites du noyau ont été atteintes et qu’une action doit être entreprise sur un paramètre système global. Ces problèmes doivent absolument être traités, car ils entraînent une utilisation élevée du CPU (lorsque accept() échoue) et des échecs de connexion généralement perceptibles par l’utilisateur. Une solution consiste également à réduire la valeur maxconn globale afin d’imposer une sérialisation, et éventuellement à désactiver la réutilisation persistante HTTP afin de forcer la libération et la réutilisation plus rapide des connexions.