Detectando o efeito Slashdot no nginx

10

Existe uma maneira de fazer com que o Nginx me notifique se os hits de um referenciador ultrapassam um limite?

Por exemplo, se meu site é apresentado no Slashdot e, de repente, tenho 2 mil acessos em uma hora, quero ser notificado quando ultrapassar 1 mil acessos por hora.

Será possível fazer isso no Nginx? Possivelmente sem lua? (desde que meu prod não é lua compilado)

Quintin Par
fonte
4
O que é "Slashdot" ??
ewwhite
Eu fiz algo assim para detectar ddos ​​no ngix. Eu consegui isso analisando o log de acesso. Fiz um trabalho cron para analisar o log de acesso e contar conexões ip exclusivas por hora.
Hex
8
Quer dizer que você deseja que o nginx seja capaz de detectar se você foi comprado pela Dice?
MDMarra 27/09/12
1
@Hex Isso (e talvez alguns trechos de seu script) seria uma excelente resposta a esta pergunta :)
voretaq7
3
Provavelmente não precisa mais se preocupar em obter Slashdotted. Seu servidor da web deve ser capaz de lidar com 4 conexões extras por hora. Pode querer se preocupar em ficar Redditted, embora ...
HopelessN00b

Respostas:

3

A solução mais eficaz poderia ser a de escrever um daemon que tail -fa access.log, e manter o controle do $http_referercampo.

No entanto, uma solução rápida e suja seria adicionar um access_logarquivo extra , registrar apenas a $http_referervariável com um costume log_formate girar o log automaticamente a cada X minutos.

  • Isso pode ser realizado com a ajuda de scripts de logrotate padrão, que podem ser necessários para reiniciar normalmente o nginx para que os arquivos sejam reabertos (por exemplo, o procedimento padrão, consulte / a / 15183322 no SO por um simples período de tempo). script baseado)…

  • Ou, usando variáveis ​​dentro access_log, possivelmente tirando a especificação minuciosa $time_iso8601com a ajuda da diretiva mapou if(dependendo de onde você gostaria de colocar sua access_log).

Portanto, com o exposto, você pode ter 6 arquivos de log, cada um cobrindo um período de 10 minutos http_referer.Txx{0,1,2,3,4,5}x.log, por exemplo, obtendo o primeiro dígito do minuto para diferenciar cada arquivo.

Agora, tudo o que você precisa fazer é ter um script de shell simples que possa ser executado a cada 10 minutos, cattodos os arquivos acima juntos, canalizá-lo sort, canalizá-lo uniq -cpara sort -rn, para head -16, e você tem uma lista das 16 Referervariações mais comuns - livre para decidir se alguma combinação de números e campos excede seus critérios e executar uma notificação.

Posteriormente, após uma única notificação bem-sucedida, você pode remover todos esses 6 arquivos e, nas execuções subseqüentes, não emitir nenhuma notificação, A menos que todos os seis arquivos estejam presentes (e / ou outro número que desejar).

cnst
fonte
Isso parece super útil. Eu posso estar pedindo demais, mas, como a resposta anterior, você se importaria de ajudar com um script?
Quintin Par
@QuintinPar Isso soa extra-curricular! ;-) Se você quiser, estou disponível para contratação e consultoria; meu email é [email protected], também em Constantine.SU
cnst
Compreendo totalmente. Muito obrigado por toda a ajuda até agora. Espero que eu possa pagar um dia para você :-)
Quintin Par
1
@QuintinPar de nada! Não se preocupe, ele deve ser um script bastante simples com as especificações acima; apenas uma questão de testar, configurar e empacotar, basicamente. :)
cnst
1
Você é um super herói!
Quintin Par 2/17
13

Eu acho que isso seria muito melhor feito com logtail e grep. Mesmo que seja possível fazer isso com lua inline, você não deseja essa sobrecarga para todas as solicitações e, principalmente , não deseja quando tiver um Slashdotted.

Aqui está uma versão de 5 segundos. Coloque-o em um script e coloque um texto mais legível em torno dele e você estará dourado.

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

Obviamente, isso ignora completamente o reddit.com e o facebook.com e todos os milhões de outros sites que podem enviar muito tráfego. Sem mencionar 100 sites diferentes, enviando 20 visitantes cada. Provavelmente, você deve ter apenas um limite de tráfego antigo simples que faça com que um email seja enviado a você, independentemente do referenciador.

Ladadadada
fonte
1
O problema é ser proativo. Eu preciso saber de qualquer site. Outra pergunta é onde coloco o limite? Você quis dizer alguma análise de log adicional? Também não encontrei –o em fourmilab.ch/webtools/logtail
Quintin Par
O limite dependerá da quantidade de tráfego que seus servidores podem gerenciar. Somente você pode definir isso. Se você deseja uma notificação mais rápida, execute-a a cada cinco minutos em vez de a cada hora e divida o limite por 12. A -o opção é para um arquivo offset, para que ele saiba por onde começar a ler da próxima vez.
Ladadadada 28/09/12
@Ladadadada, eu discordo que a sobrecarga seria substancial, veja minha solução - serverfault.com/a/870537/110020 - Acredito que a sobrecarga seria bastante mínima se isso fosse implementado adequadamente, especialmente (1), se o seu back-end for muito lento, essa sobrecarga seria insignificante, ou, (2), se seu back-end já estiver bem cheio e / ou armazenado em cache corretamente, você deve ter pequenos problemas com a manipulação de tráfego e, em primeiro lugar, ganhar um pouco de carga extra ' Não faça um dente. No geral, parece que essa pergunta tem dois casos de uso, (1), acabando de ser informado e (2), dimensionamento automático.
CNST
4

A diretiva nginx limit_req_zone pode basear suas zonas em qualquer variável, incluindo $ http_referrer.

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

    ...

    server {

        ...

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

Você também deseja fazer algo para limitar a quantidade de estado necessária no servidor da web, pois os cabeçalhos do referenciador podem ser bastante longos e variados e você pode ver uma variedade de informações. Você pode usar o recurso nginx split_clients para definir uma variável para todas as solicitações com base no hash do cabeçalho do referenciador. O exemplo abaixo usa apenas 10 buckes, mas você pode fazer isso com 1000 com a mesma facilidade. Portanto, se você receber um slashdotted, as pessoas cujo referenciador tiver hash no mesmo bloco que o URL do slashdot também serão bloqueadas, mas você poderá limitar isso a 0,1% dos visitantes usando 1000 buckets em split_clients.

Seria algo assim (totalmente não testado, mas direcionalmente correto):

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
fonte
Esta é uma abordagem interessante; no entanto, acredito que a pergunta é sobre um alerta automático quando o efeito Slashdot está ocorrendo; sua solução parece resolver bloqueando aleatoriamente cerca de 10% dos usuários. Além disso, acredito que seu raciocínio sobre o uso split_clientspode estar mal informado - limit_reqé baseado em um "depósito com vazamento", o que significa que o estado geral nunca deve exceder o tamanho da zona especificada.
CNST
2

Sim, é claro que é possível no NGINX!

O que você pode fazer é implementar o seguinte DFA :

  1. Implemente a limitação de taxa, com base em $http_referer, possivelmente usando alguma regex por meio de a mappara normalizar os valores. Quando o limite é excedido, uma página de erro interna é exibida, que você pode capturar através de um error_pagemanipulador de acordo com uma pergunta relacionada , indo para um novo local interno como redirecionamento interno (não visível para o cliente).

  2. No local acima, para limites excedidos, você executa uma solicitação de alerta, permitindo que a lógica externa execute a notificação; essa solicitação é armazenada em cache posteriormente, garantindo que você receba apenas uma solicitação única por um determinado período de tempo.

  3. Capture o código de status HTTP da solicitação anterior (retornando um código de status ≥ 300 e usando proxy_intercept_errors on, ou, alternativamente, use o não construído por padrão auth_requestou add_after_bodypara fazer uma sub-solicitação "gratuita") e conclua a solicitação original como se o passo anterior não estava envolvido. Observe que precisamos habilitar o error_pagetratamento recursivo para que isso funcione.

Aqui está meu PoC e um MVP, também em 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;
}

Observe que isso funciona conforme o esperado:

% 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"
%

Você pode ver que a primeira solicitação resulta em um resultado de front-end e de back-end, conforme o esperado (eu tive que adicionar um back-end fictício ao local que possui limit_req, porque um return 200teria precedência sobre os limites, um back-end real não é necessário para o restante do manuseio).

A segunda solicitação está acima do limite, portanto, enviamos o alerta (obtendo 200) e o armazenamos em cache, retornando 429(isso é necessário devido à limitação acima mencionada de que solicitações abaixo de 300 não podem ser capturadas), que são capturadas posteriormente pelo front-end , que agora é gratuito para fazer o que quiser.

A terceira solicitação ainda está excedendo o limite, mas já enviamos o alerta, portanto, nenhum novo alerta é enviado.

Feito! Não se esqueça de bifurcar no GitHub!

cnst
fonte
Duas condições limitadoras de taxa podem trabalhar juntas? Estou usando isso agora: serverfault.com/a/869793/26763
Quintin Par
@QuintinPar :-) Acho que vai depender de como você o usa - o problema óbvio seria distinguir em um único local o limite que introduziu a condição; mas se este é um limit_req, e o outro é um limit_conn, basta usar o limit_req_status 429acima (requer um nginx muito novo), e acho que você deveria ser de ouro; pode haver outras opções (uma a trabalhar com certeza é encadear o nginx com set_real_ip_from, mas, dependendo do que exatamente você deseja fazer, pode haver opções mais eficientes).
cnst 27/08/17
@QuintinPar se houver algo que está faltando na minha resposta, me avise. BTW, observe que quando o limite for atingido e seu script for chamado, até que esse script seja armazenado em cache corretamente pelo nginx, seu conteúdo poderá sofrer um atraso; por exemplo, convém implementar o script de forma assíncrona com algo como golang, ou procurar nas opções de tempo limite para upstreams; Além disso, convém usar proxy_cache_lock ontambém e, possivelmente, adicionar algum tratamento de erro para o que fazer se o script falhar (por exemplo, o uso error_pagetambém proxy_intercept_errors). Confio que meu POC é um bom começo. :)
cnst 28/08/17
Obrigado por tentar isso. Uma questão importante para mim ainda é: eu estou usando limit_req e limit_conn já no nível http e se aplica a todos os sites que tenho. Eu não posso substituir isso será isso. Portanto, esta solução está usando uma funcionalidade destinada a outra coisa. Alguma outra abordagem para esta solução?
Quintin Par
@QuintinPar Que tal ter instâncias nginx aninhadas, em que cada uma usará um único conjunto de limit_req/ limit_conn? Por exemplo, basta colocar a configuração acima na frente do seu servidor front-end atual. Você pode usar set_real_ip_fromno nginx upstream para garantir que os IPs sejam contabilizados corretamente na linha. Senão, se ainda não se encaixar, acho que você precisa articular suas restrições exatas e as especificações de forma mais vívida - de que níveis de tráfego estamos falando? Com que frequência o stat precisa ser executado (1min / 5min / 1h)? O que há de errado com a logtailsolução antiga ?
cnst 29/08/17