NGINX 的访问控制

Limiting Access to Proxied HTTP Resources

简介

NGINX 和 NGINX Plus 可以做如下控制:

  • 每个key value 的连接数 (比如,每个IP地址)
  • 每个key value 的请求速度 (在1s or 1m 内允许被处理的请求数量)
  • 每个连接的下载速度

注意: IP地址可能是在NAT设备后面共享的,所以根据IP地址进行限制要慎用。

限制连接数

步骤如下:

  1. 使用 limit_conn_zone 指令定义key,设置shared memory zone (工作进程将使用该zone作为key values的共享计数器)。第一个参数,指定表达式作为key。第二个参数 zone,指定zone的名字和它的大小:
limit_conn_zone $binary_remote_addr zone=addr:10m;
  1. 使用 limit_conn 指令将限制应用到 location {}, server {}, or http {} 上下文。指定shared memory zone的名字作为第一个参数,每个key允许的连接数作为第二个参数:
location /download/ {
     limit_conn addr 1;
}

使用$binary_remote_addr变量作为key,连接数的限制是基于IP地址的。

另外一种限制方式是基于服务器,使用 $server_name 变量:

http {
    limit_conn_zone $server_name zone=servers:10m;

    server {
        limit_conn servers 1000;
    }
}

限制请求速率

速度限制可以被用于防止DDoS攻击,或防止上游服务器被一段时间内大量的流量压垮。方法是基于 leaky bucket 算法:请求以各种速度到达桶,但是以修正的速度离开桶。

在使用速度限制前,需要配置“leaky bucket”的全局参数:

  • key - 区别客户端的参数,通常是一个变量
  • shared memory zone - zone的名字和大小,zone保存了这些keys的状态信息 (the “leaky bucket”)
  • rate - 速度限制,请求数/秒 (r/s) or 请求数/分钟 (r/m) (“leaky bucket draining”)

这些参数在 limit_req_zone 指令设置。该指令定义在http {} 层 - 这种方法允许对不同的上下文应用不同的区域和请求溢出参数:

http {
    #...
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}

如上配置,创建 shared memory zone 名称设置为one ,大小设置为10m。该 zone 保存了 $binary_remote_addr 变量所引用的客户端IP的状态。
注意:$remote_addr 也表现为客户端IP,但是$binary_remote_addr 表现为IP地址的二进制描述,更短。

shared memory zone 的空间调优可以参考如下数据:
$binary_remote_addr 的值是每个地址占用4 bytes ,在64位系统下状态信息的存储占用 128 bytes 。因此,16,000 IP地址的状态信息保存需要占用1m。

如果NGINX追加新条目的时候,存储空间满了,它将会溢出最老的条目。如果释放的空间让然不够容纳新记录,NGINX 会返回状态码 503 Service Unavailable。状态码可以在 limit_req_status 指令重新定义。

zone 设置好后,你就可以在NGINX配置文件的server {}, location {}, or http {}上下文应用该速度限制了:

http {
    #...

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

    server {
        #...

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

如上配置,NGINX 在处理/search/ 的时候,处理速度不超过 1请求/秒。这些请求的处理会被延迟,以使得总体速度不超过指定值。如果请求的数量超过了指定速度,NGINX 将延迟处理这些请求直到 (shared memory zone one) 满。 对于在桶满时候到达的请求,NGINX 返回503 Service Unavailable 错误 。

流控测试

在真实配置速度限制前,可以试试 dry run 模式,该模式不会真的限制请求处理速度。但是,溢出的请求仍然会在shared memory zone被统计,并被日志记录。dry run模式通过 limit_req_dry_run 指令开启:

http {
    #...

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

    server {
        #...

        location /search/ {
            limit_req zone=one;
            limit_req_dry_run on;
        }
    }
}

溢出的请求被记录时候,被标记以 “dry run” :

2019/09/03 10:28:45 [error] 142#142: *13246 limiting requests, dry run, excess: 1.000 by zone "one", client: 172.19.0.1, server: www.example.com, request: "GET / HTTP/1.0", host: "www.example.com:80"

处理溢出请求

请求被限制到 limit_req_zone 指令配置的速度。如果请求数量超过了指定速度,并且shared memory zone满了,NGINX 就会返回错误。然而流量就是突发性的,返回错误并不是好的处理方式。

这些溢出的请求可以被缓冲起来处理。limit_req 指令的burst参数设置了可以在限定速度下被缓存处理的最大溢出请求数:

http {
    #...

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

    server {
        #...

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

如上配置,如果请求超过 1 请求/秒,超过速度的请求将被放到 zone one。当 zone 满之后,溢出的请求将会被排队 (burst),队列的长度为 5 个请求。队列内的请求会被延迟处理,这样总体速度不会超过限定速度。 超过burst限制的请求会被返回 503 错误。

如果流量突发不希望延迟处理,添加nodelay参数:

http {
    #...

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

    server {
        #...

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

如上配置,溢出请求不超过burst限制的部分会被立即处理,超过burst限制的部分会被返回 503。

延迟处理溢出请求

另一种处理溢出请求的方式是某个数值前的请求会被立即处理,当溢出请求即将被拒绝时应用流控。

这可以通过 delay and burst 参数实现。delay 参数定义了一个点,在该点后的溢出请求会被延迟处理以匹配速度限制 :

http {
    #...

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

    server {
        #...

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

如上配置,前面 3 个请求 (delay) 被没有延迟的处理,接下来的 2 个请求 (burst - delay) 被延迟处理,这样总体速度不超过限制,更多的溢出请求会被拒绝因为总的burst已经溢出了,后续请求被延迟。

Synchronizing Contents of Many Shared Memory Zones

If you have a computer cluster with several NGINX instances and these instances use the limit_req method, it is possible to sync the contents of their shared memory zones on conditions that:

  • the zone_sync functionality is configured for each instance
  • shared memory zones set in the limit_req_zone directive for each instance have the same name
  • the sync parameter of the limit_req_zone directive is specified for each instance:
http {
    #...
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s sync;
}

See Runtime State Sharing in a Cluster for details.

限制带宽

限制每个连接的带宽,使用 limit_rate 指令:

location /download/ {
    limit_rate 50k;
}

如上设置,一个客户端可以以单连接50k/s的速度下载内容。但是,客户端可以开启多个连接。那么,如果目的是限制下载速度,连接数也是需要被限制的。比如,每个IP可以开启一个连接:

location /download/ {
    limit_conn addr 1;
    limit_rate 50k;
}

要在客户端下载某个允许大小的数据后进行限速,使用 limit_rate_after 指令。客户端在到达某个限制前可以快速下载是合理需求 (比如,文件头 — 电影索引),然后对剩下的数据进行流控 (以确保在观影,而不是下载)。

limit_rate_after 500k;
limit_rate       20k;

如下范例展示了对连接数和带宽的联合限制。每个客户端的最大连接数限制为5 ,适合大部分场景,因为现代浏览器通常同时打开3个连接。同时,downloads的下载被限制为1个连接:

http {
    limit_conn_zone $binary_remote_address zone=addr:10m

    server {
        root /www/data;
        limit_conn addr 5;

        location / {
        }

        location /download/ {
            limit_conn       addr 1;
            limit_rate_after 1m;
            limit_rate       50k;
        }
    }
}

Dynamic Bandwidth Control

limit_rate 的值可以设备为变量 - 这就允许可以动态的调整带宽,比如,允许现代浏览器的带宽更高:

map $ssl_protocol $response_rate {
    "TLSv1.1" 10k;
    "TLSv1.2" 100k;
    "TLSv1.3" 1000k;
}

server {
    listen 443 ssl;
    ssl_protocols       TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;

    location / {
        limit_rate       $response_rate; # Limit bandwidth based on TLS version
        limit_rate_after 512;      # Apply limit after headers have been sent
        proxy_pass       http://my_backend;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容