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;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容