Detectando el efecto Slashdot en nginx

10

¿Hay alguna manera de hacer que Nginx me notifique si los hits de un referente exceden un umbral?

Por ejemplo, si mi sitio web aparece en Slashdot y, de repente, recibo 2K visitas en una hora, quiero recibir una notificación cuando supere 1K visitas por hora.

¿Será posible hacer esto en Nginx? Posiblemente sin lua? (ya que mi producto no está compilado por lua)

Quintin Par
fuente
44
¿Qué es "Slashdot"?
ewwhite
Hice algo como esto para detectar ddos ​​en ngix. Lo logré analizando el registro de acceso. Hice un trabajo cron para analizar el registro de acceso y contar conexiones IP únicas por hora.
Hex
8
¿Quieres decir que quieres que nginx pueda detectar si Dice te ha comprado?
MDMarra
1
@Hex Eso (y tal vez algunos fragmentos de su script) sería una excelente respuesta a esta pregunta :)
voretaq7
3
Probablemente ya no tenga que preocuparse por obtener Slashdotted. Su servidor web debería poder manejar 4 conexiones adicionales por hora. Sin embargo, podría querer preocuparme por ser Redditted ...
HopelessN00b

Respuestas:

3

La solución más eficiente podría ser escribir un demonio que sería tail -fel access.log, y realizar un seguimiento del $http_referercampo.

Sin embargo, una solución rápida y sucia sería agregar un access_logarchivo adicional , registrar solo la $http_referervariable con una personalizada log_formaty rotar automáticamente el registro cada X minutos.

  • Esto se puede lograr con la ayuda de scripts de logrotate estándar, que pueden necesitar reinicios elegantes de nginx para volver a abrir los archivos (por ejemplo, el procedimiento estándar, eche un vistazo a / a / 15183322 en SO por un tiempo simple) guión basado) ...

  • O, mediante el uso de variables internas access_log, posiblemente obteniendo la especificación de minutos $time_iso8601con la ayuda de la directiva mapo una if(dependiendo de dónde le gustaría poner su access_log).

Entonces, con lo anterior, puede tener 6 archivos de registro, cada uno de los cuales cubre un período de 10 minutos http_referer.Txx{0,1,2,3,4,5}x.log, por ejemplo, obteniendo el primer dígito del minuto para diferenciar cada archivo.

Ahora, todo lo que tiene que hacer es tener un script de shell simple que podría ejecutarse cada 10 minutos, cattodos los archivos anteriores juntos, canalizarlos sort, canalizarlos a uniq -c, a sort -rn, a head -16, y tiene una lista de las 16 Referervariaciones más comunes - libre de decidir si alguna combinación de números y campos excede sus criterios, y realizar una notificación.

Posteriormente, después de una sola notificación exitosa, puede eliminar todos estos 6 archivos y, en ejecuciones posteriores, no emitir ninguna notificación A MENOS QUE los seis archivos estén presentes (y / o un cierto otro número como mejor le parezca).

cnst
fuente
Esto se ve muy útil. Podría estar pidiendo demasiado, pero como la respuesta anterior, ¿le importaría ayudar con un guión?
Quintin Par
@QuintinPar ¡Eso suena extra curricular! ;-) Si lo desea, estoy disponible para contratar y consultar; mi correo electrónico es [email protected], también en Constantine.SU
cnst
Entiendo totalmente. Muchas gracias por toda la ayuda hasta ahora. Espero poder pagar algún día :-)
Quintin Par
1
@QuintinPar de nada! No se preocupe, debería ser un script bastante simple con la especificación anterior; solo es cuestión de probar, configurar y empaquetar, básicamente. :)
cnst
1
Eres un superhéroe!
Quintin Par
13

Creo que esto sería mucho mejor con logtail y grep. Incluso si es posible hacerlo con lua en línea, no desea esa sobrecarga para cada solicitud y, especialmente , no la quiere cuando se le ha asignado Slashdotted.

Aquí hay una versión de 5 segundos. Péguelo en un script y ponga un texto más legible a su alrededor y estará dorado.

5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"

Por supuesto, eso ignora por completo reddit.com y facebook.com y todos los millones de otros sitios que podrían enviarle mucho tráfico. Sin mencionar 100 sitios diferentes que le envían 20 visitantes cada uno. Probablemente debería tener un umbral de tráfico antiguo que hace que se le envíe un correo electrónico, independientemente del remitente.

Ladadadada
fuente
1
El problema es ser proactivo. Necesito saber de cualquier sitio. Otra pregunta es ¿dónde pongo el umbral? ¿Quiso decir algún análisis de registro adicional? Tampoco encontré –o en fourmilab.ch/webtools/logtail
Quintin Par
El umbral dependerá de cuánto tráfico puedan manejar sus servidores. Solo tú puedes configurar eso. Si desea una notificación más rápida, ejecútela cada cinco minutos en lugar de cada hora y divida el umbral entre 12. La -o opción es para un archivo de compensación para que sepa dónde comenzar a leer la próxima vez.
Ladadadada
@Ladadadada, no estoy de acuerdo con que la sobrecarga sea sustancial, vea mi solución - serverfault.com/a/870537/110020 - Creo que la sobrecarga sería bastante mínima si se implementa correctamente, especialmente, (1), si su backend es realmente lento, entonces esta sobrecarga sería insignificante, o (2), si su backend ya está bastante recortado y / o almacenado en caché correctamente, entonces debería tener pequeños problemas con el manejo del tráfico en primer lugar, y un poco de carga extra ganada ' No haga mella. En general, parece que esta pregunta tiene dos casos de uso, (1), solo para ser informado, y (2), escala automática.
cnst
4

La directiva nginx limit_req_zone puede basar sus zonas en cualquier variable, incluyendo $ http_referrer.

http {
    limit_req_zone  $http_referrer  zone=one:10m   rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req   zone=one  burst=5;
        }

Sin embargo, también querrá hacer algo para limitar la cantidad de estado requerida en el servidor web, ya que los encabezados de referencia pueden ser bastante largos y variados y puede ver una variedad infinita. Puede usar la función nginx split_clients para establecer una variable para todas las solicitudes que se base en el hash del encabezado de referencia. El siguiente ejemplo usa solo 10 dólares, pero podría hacerlo con 1000 con la misma facilidad. Por lo tanto, si obtiene una barra oblicua, las personas cuyo referenciador se mezcló en el mismo cubo que la URL de barra oblicua también se bloquearían, pero podría limitar eso al 0.1% de los visitantes al usar 1000 cubos en clientes divididos.

Se vería algo así (totalmente no probado, pero direccionalmente correcto):

http {

split_clients $http_referrer $refhash {
               10%               x01;
               10%               x02;
               10%               x03;
               10%               x04;
               10%               x05;
               10%               x06;
               10%               x07;
               10%               x08;
               10%               x09;
               *                 x10;
               }

limit_req_zone  $refhash  zone=one:10m   rate=1r/s;

...

server {

    ...

    location /search/ {
        limit_req   zone=one  burst=5;
    }
rmalayter
fuente
Este es un enfoque interesante; sin embargo, creo que la pregunta es acerca de una alerta automática cuando se produce el efecto Slashdot; su solución parece resolverse en torno al bloqueo aleatorio del 10% de los usuarios. Además, creo que su razonamiento para usar split_clientspuede estar mal informado: limit_reqse basa en un "cubo con fugas", lo que significa que el estado general nunca debe exceder el tamaño de la zona especificada.
cnst
2

¡Sí, por supuesto que es posible en NGINX!

Lo que podría hacer es implementar el siguiente DFA :

  1. Implemente la limitación de velocidad, basada en $http_referer, posiblemente, utilizando algunas expresiones regulares a través de a mappara normalizar los valores. Cuando se excede el límite, se abre una página de error interno, que puede atrapar a través de un error_pagecontrolador según una pregunta relacionada , yendo a una nueva ubicación interna como una redirección interna (no visible para el cliente).

  2. En la ubicación anterior para los límites excedidos, realiza una solicitud de alerta, permitiendo que la lógica externa realice la notificación; Esta solicitud se almacena en caché posteriormente, lo que garantiza que solo obtendrá 1 solicitud única por una ventana de tiempo determinada.

  3. Capture el código de estado HTTP de la solicitud anterior (devolviendo un código de estado ≥ 300 y usando proxy_intercept_errors on, o, alternativamente, use el no construido por defecto auth_requesto add_after_bodypara hacer una subrequest "libre"), y complete la solicitud original como si el paso anterior no estuvo involucrado. Tenga en cuenta que debemos habilitar el error_pagemanejo recursivo para que esto funcione.

Aquí está mi PoC y un MVP, también en https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf :

limit_req_zone $http_referer zone=slash:10m rate=1r/m;  # XXX: how many req/minute?
server {
    listen 2636;
    location / {
        limit_req zone=slash nodelay;
        #limit_req_status 429;  #nginx 1.3.15
        #error_page 429 = @dot;
        error_page 503 = @dot;
        proxy_pass http://localhost:2635;
        # an outright `return 200` has a higher precedence over the limit
    }
    recursive_error_pages on;
    location @dot {
        proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
        # if you don't have `resolver`, no URI modification is allowed:
        #proxy_pass http://localhost:2637;
        proxy_intercept_errors on;
        error_page 429 = @slash;
    }
    location @slash {
        # XXX: placeholder for your content:
        return 200 "$uri: we're too fast!\n";
    }
}
server {
    listen 2635;
    # XXX: placeholder for your content:
    return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
        max_size=64m keys_zone=slashdotted:10m;
server {
    # we need to flip the 200 status into the one >=300, so that
    # we can then catch it through proxy_intercept_errors above
    listen 2637;
    error_page 429 @/.;
    return 429;
    location @/. {
        proxy_cache slashdotted;
        proxy_cache_valid 200 60s;  # XXX: how often to get notifications?
        proxy_pass http://localhost:2638;
    }
}
server {
    # IRL this would be an actual script, or
    # a proxy_pass redirect to an HTTP to SMS or SMTP gateway
    listen 2638;
    return 200 authorities_alerted\n;
}

Tenga en cuenta que esto funciona como se esperaba:

% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!

127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%

Puede ver que la primera solicitud da como resultado un hit de front-end y un backend, como se esperaba (tuve que agregar un backend ficticio a la ubicación que tiene limit_req, porque return 200tendría prioridad sobre los límites, un backend real no es necesario para el resto del manejo).

La segunda solicitud está por encima del límite, por lo tanto, enviamos la alerta (obteniendo 200) y la almacenamos en caché, regresando 429(esto es necesario debido a la limitación mencionada anteriormente de que las solicitudes por debajo de 300 no pueden ser capturadas), que posteriormente es capturado por el front-end , que ahora es libre de hacer lo que quiera.

La tercera solicitud aún excede el límite, pero ya hemos enviado la alerta, por lo que no se envía ninguna nueva.

¡Hecho! ¡No olvides bifurcarlo en GitHub!

cnst
fuente
¿Pueden dos condiciones limitantes de velocidad trabajar juntas? Estoy usando esto ahora: serverfault.com/a/869793/26763
Quintin Par
@QuintinPar :-) Creo que dependerá de cómo lo use; el problema obvio sería distinguir en una única ubicación cuyo límite introdujo la condición; pero si este es un limit_req, y el otro es un limit_conn, entonces solo usa el limit_req_status 429anterior (requiere un nginx muy nuevo), y creo que deberías ser dorado; puede haber otras opciones (una de las cuales debe estar seguro es encadenar nginx w / set_real_ip_from, pero, dependiendo de lo que quiera hacer exactamente, puede haber opciones más eficientes).
cnst
@QuintinPar si hay algo que falta en mi respuesta, avíseme. Por cierto, tenga en cuenta que una vez que se alcanza el límite, y se debe llamar a su secuencia de comandos, hasta que nginx guarde en caché dicha secuencia de comandos, entonces su contenido puede retrasarse; por ejemplo, es posible que desee implementar el script de forma asincrónica con algo como golang, o buscar en las opciones de tiempo de espera para las secuencias ascendentes; Además, es posible que desee usar proxy_cache_lock ontambién, y posiblemente agregue algún manejo de errores sobre qué hacer si el script falla (por ejemplo, usarlo error_pagetan bien como de proxy_intercept_errorsnuevo). Confío en que mi POC es un buen comienzo. :)
cnst
Gracias por intentar esto. Un problema importante para mí todavía es que estoy usando limit_req y limit_conn a nivel http y se aplica a todos los sitios web que tengo. No puedo anularlo será esto. Entonces, esta solución está utilizando una funcionalidad destinada a otra cosa. ¿Algo más acerca de esta solución?
Quintin Par
@QuintinPar ¿Qué pasa con tener instancias nginx anidadas, donde cada una usará un conjunto único de limit_req/ limit_conn? Por ejemplo, simplemente coloque la configuración anterior frente a su servidor front-end actual. Puede usar set_real_ip_fromnginx ascendente para garantizar que las IP se contabilicen correctamente en el futuro. De lo contrario, si aún no encaja, creo que debe articular sus restricciones exactas y las especificaciones de manera más vívida: ¿de qué niveles de tráfico estamos hablando? ¿Con qué frecuencia debe ejecutarse la estadística (1 min / 5 min / 1 h)? ¿Qué hay de malo con la vieja logtailsolución?
cnst