Existe-t-il un moyen pour que Nginx me prévienne si les hits d'un référent dépassent un seuil?
Par exemple, si mon site Web est présenté à Slashdot et que tout à coup, j'ai 2K hits à venir dans une heure, je veux être averti quand va au-delà de 1K hits par heure.
Sera-t-il possible de le faire dans Nginx? Peut-être sans lua? (puisque ma prod n'est pas lua compilée)
Réponses:
La solution la plus efficace pourrait être d'écrire un démon qui
tail -f
leaccess.log
, et garder une trace du$http_referer
champ.Cependant, une solution rapide et sale serait d'ajouter un
access_log
fichier supplémentaire , de ne journaliser que la$http_referer
variable avec une coutumelog_format
et de faire pivoter automatiquement le journal toutes les X minutes.Cela peut être accompli à l'aide de scripts logrotate standard, qui pourraient avoir besoin de faire des redémarrages gracieux de nginx pour que les fichiers soient rouverts (par exemple, la procédure standard, jetez un œil à / a / 15183322 sur SO pour un temps simple- basé sur un script)…
Ou, en utilisant des variables à l'intérieur
access_log
, éventuellement en extrayant la spécification minute$time_iso8601
à l'aide de lamap
ou d'uneif
directive (selon l'endroit où vous souhaitez mettre votreaccess_log
).Ainsi, avec ce qui précède, vous pouvez avoir 6 fichiers journaux, chacun couvrant une période de 10 minutes
http_referer.Txx{0,1,2,3,4,5}x.log
, par exemple, en obtenant le premier chiffre de la minute pour différencier chaque fichier.Maintenant, tout ce que vous avez à faire est d'avoir un script shell simple qui pourrait s'exécuter toutes les 10 minutes,
cat
tous les fichiers ci-dessus ensemble, le diriger vers, le dirigersort
versuniq -c
, verssort -rn
, vershead -16
, et vous avez une liste des 16Referer
variantes les plus courantes - libre de décider si des combinaisons de nombres et de champs dépassent vos critères et d'effectuer une notification.Par la suite, après une seule notification réussie, vous pouvez supprimer tous ces 6 fichiers et, dans les exécutions suivantes, ne pas émettre de notification SAUF si les six fichiers sont présents (et / ou un certain autre numéro comme bon vous semble).
la source
Je pense que ce serait beaucoup mieux fait avec logtail et grep. Même s'il est possible de le faire avec lua inline, vous ne voulez pas cette surcharge pour chaque demande et surtout vous ne le voulez pas quand vous avez été Slashdotted.
Voici une version de 5 secondes. Collez-le dans un script et mettez-y du texte plus lisible et vous êtes en or.
Bien sûr, cela ignore complètement reddit.com et facebook.com et tous les millions d'autres sites qui pourraient vous envoyer beaucoup de trafic. Sans oublier 100 sites différents vous envoyant chacun 20 visiteurs. Vous devriez probablement avoir simplement un ancien seuil de trafic qui provoque l'envoi d'un e-mail, quel que soit le référent.
la source
-o
option est pour un fichier de décalage afin qu'il sache où commencer la lecture la prochaine fois.La directive nginx limit_req_zone peut baser ses zones sur n'importe quelle variable, y compris $ http_referrer.
Vous voudrez également faire quelque chose pour limiter la quantité d'état requise sur le serveur Web, car les en-têtes de référence peuvent être assez longs et variés et vous pouvez voir un variet infini. Vous pouvez utiliser la fonction nginx split_clients pour définir une variable pour toutes les demandes qui est basée sur le hachage de l'en-tête du référent. L'exemple ci-dessous utilise seulement 10 buckes, mais vous pouvez le faire avec 1000 tout aussi facilement. Donc, si vous deviez slashdotted, les personnes dont le référent s'est avéré être haché dans le même compartiment que l'URL slashdot seraient également bloquées, mais vous pouvez limiter cela à 0,1% des visiteurs en utilisant 1000 compartiments dans split_clients.
Cela ressemblerait à quelque chose comme ça (totalement non testé, mais directionnellement correct):
la source
split_clients
peut être mal informé -limit_req
est basé sur un "seau qui fuit", ce qui signifie que l'état global ne doit jamais dépasser la taille de la zone spécifiée.Oui, bien sûr, c'est possible dans NGINX!
Vous pouvez implémenter le DFA suivant :
Implémentez la limitation de débit, basée sur
$http_referer
, éventuellement en utilisant une expression régulière via amap
pour normaliser les valeurs. Lorsque la limite est dépassée, une page d'erreur interne s'affiche, que vous pouvez intercepter via unerror_page
gestionnaire selon une question connexe , en allant vers un nouvel emplacement interne en tant que redirection interne (non visible pour le client).À l'emplacement ci-dessus pour les limites dépassées, vous effectuez une demande d'alerte, laissant la logique externe effectuer la notification; cette demande est ensuite mise en cache, ce qui garantit que vous n'obtiendrez qu'une seule demande par fenêtre de temps donnée.
Attrapez le code d'état HTTP de la demande précédente (en renvoyant un code d'état ≥ 300 et en utilisant
proxy_intercept_errors on
, ou, alternativement, utilisez le non-construit par défautauth_request
ouadd_after_body
pour faire une sous-demande "gratuite"), et complétez la demande d'origine comme si l'étape précédente n'était pas impliquée. Notez que nous devons activer laerror_page
gestion récursive pour que cela fonctionne.Voici mon PoC et un MVP, également sur https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf :
Notez que cela fonctionne comme prévu:
Vous pouvez voir que la première demande aboutit à un hit frontal et à un backend, comme prévu (j'ai dû ajouter un backend factice à l'emplacement qui a
limit_req
, car unreturn 200
aurait priorité sur les limites, un vrai backend n'est pas nécessaire pour le reste de la manipulation).La deuxième demande est au-dessus de la limite, donc, nous envoyons l'alerte (obtention
200
), et la mettons en cache, en retournant429
(cela est nécessaire en raison de la limitation susmentionnée selon laquelle les demandes inférieures à 300 ne peuvent pas être interceptées), qui est ensuite interceptée par le frontal , qui est maintenant libre de faire ce qu'il veut.La troisième demande dépasse toujours la limite, mais nous avons déjà envoyé l'alerte, donc aucune nouvelle alerte n'est envoyée.
Terminé! N'oubliez pas de le bifurquer sur GitHub!
la source
limit_req
, et l'autre est unlimit_conn
, alors utilisez simplement ce quilimit_req_status 429
précède (nécessite un tout nouveau nginx), et je pense que vous devriez être en or; il peut y avoir d'autres options (une pour travailler à coup sûr est de chaîner nginx avecset_real_ip_from
, mais, selon ce que vous voulez exactement faire, il peut y avoir des choix plus efficaces).golang
, ou examiner les options de délai d'attente pour les amonts; peut également vouloir utiliserproxy_cache_lock on
également, et éventuellement ajouter une gestion des erreurs pour savoir quoi faire en cas d'échec du script (par exemple, utilisererror_page
aussi bien qu'àproxy_intercept_errors
nouveau). J'espère que mon POC est un bon début. :)limit_req
/limit_conn
? Par exemple, placez simplement la configuration ci-dessus devant votre serveur frontal actuel. Vous pouvez utiliserset_real_ip_from
dans nginx en amont pour vous assurer que les adresses IP sont correctement comptabilisées sur toute la ligne. Sinon, si cela ne correspond toujours pas, je pense que vous devez exprimer vos contraintes exactes et les spécifications de manière plus vivante - de quels niveaux de trafic parlons-nous? À quelle fréquence la statistique doit-elle s'exécuter (1 min / 5 min / 1 h)? Quel est le problème avec l'anciennelogtail
solution?