【NGINX--3】流量管理

1、A/B 测试

在文件或应用的两个或多个版本之间分割客户端流量,以测试接受度或参与度。
使 用 split_clients 模块将一定比例的客户端流量定向到一个不同的上游(upstream)池:

split_clients "${remote_addr}AAA" $variant {
    20.0% "backendv2";
    * "backendv1";
}

split_clients 指令对作为第一个参数提供的字符串进行哈希处理,并用该哈希值除以提供的百分比,以映射作为第二个参数提供的变量的值。在第一个参数中添加“AAA”是为了证明这是一个可以包含多个变量的串联字符串,如通用哈希负载均衡算法中所述。第三个参数是一个包含键值(key-value)对的对象,其中键是百分比权重,值是要分配的值。键可以是百分比或星号。星号表示取完所有百分比后的剩余部分。对于 $variant 变量的值,backendv2 为客户端 IP 地址流量的 20%,backendv1 为其余80%。
在此示例中,backendv1 和 backendv2 表示上游服务器池,可以与 proxy_pass 指令一起使用,如下所示:

location / {
    proxy_pass http://$variant
}

使用变量 $variant 时,我们的流量将会分配给两个不同的应用服务器池。
为了理解 split_clients 的广泛用途,我们通过一个例子来看看两个静态站点版本之间的流量分割:

http {
    split_clients "${remote_addr}" $site_root_folder {
    33.3% "/var/www/sitev2/";
    * "/var/www/sitev1/";
   }
   server {
       listen 80 _;
       root $site_root_folder;
           location / {
               index index.html;
          }
      }
}

详解
在测试电子商务网站上不同类型的营销和前端功能的转化率时,这种类型的 A/B 测试非常有用。应用通常通过“灰度发布”进行部署。在这种类型的部署中,用户可以逐步增加路由到新版本的用户百分比,进而将流量慢慢切换到新版本。在推出新版本的代码时,在不同的应用版本之间分割客户端流量非常实用,这可以降低发生错误后带来的影响。更常见的部署模式是蓝绿部署,也就说在将用户切换到新版本的同时,仍然保持旧版本在验证部署期间的可用性。无论出于何种原因在两组不同的应用之间分割客户端流量,NGINX 的 split_client 模块都可以帮您轻松搞定。

参考资料
split_clients 模块文档

2、使用 GeoIP 模块和数据库

您需要安装 GeoIP 数据库并启用它在 NGINX 中的嵌入式变量,以利用 NGINX 日志、被代理请求或请求路由中客户端的物理位置。

安装 NGINX 时,官方 NGINX 开源版软件包仓库(配置方式见第 2 章)提供了一个名为 nginx-module-geoip 的软件包。当使用 NGINX Plus 软件包仓库时,该软件包被命名为 nginx-plus-module-geoip。以下示例展示了如何安装动态 NGINX GeoIP 模块软件包,以及如何下载 GeoIP 国家/地区和城市数据库:

RHEL/CentOS NGINX 开源版:
# yum install nginx-module-geoip
Debian/Ubuntu NGINX 开源版:
# apt-get install nginx-module-geoip
RHEL/CentOS NGINX Plus:
# yum install nginx-plus-module-geoip
Debian/Ubuntu NGINX Plus:
# apt-get install nginx-plus-module-geoip
下载 GeoIP 国家/地区和城市数据库并解压:
# mkdir /etc/nginx/geoip
# cd /etc/nginx/geoip
# wget "http://geolite.maxmind.com/\ 
download/geoip/database/GeoLiteCountry/GeoIP.dat.gz"
# gunzip GeoIP.dat.gz
# wget "http://geolite.maxmind.com/\ 
download/geoip/database/GeoLiteCity.dat.gz"
# gunzip GeoLiteCity.dat.gz

这组命令在 /etc/nginx 目录下创建了一个 geoip 目录,请移动到这个新目录,下载并解压软件包。

将 GeoIP 国家/地区和城市数据库保存到本地磁盘后,就可以指示 NGINX GeoIP 模块使用它们根据客户端 IP 地址公开嵌入式变量了:

load_module "/usr/lib64/nginx/modules/ngx_http_geoip_module.so";
http {
    geoip_country /etc/nginx/geoip/GeoIP.dat; 
    geoip_city /etc/nginx/geoip/GeoLiteCity.dat;
    # ...
}

load_module 指令从它在文件系统中的路径动态加载模块。load_module 指令仅在main 上下文中有效。geoip_country 指令获取了 GeoIP.dat 文件的路径,该文件包含将IP 地址映射到国家/地区代码的数据库,并且仅在 http 上下文中有效。
详解
要使用此功能,您必须先安装 NGINX GeoIP 模块以及本地 GeoIP 国家/地区和城市数据库,本节演示了相关安装和检索教程。

geoip_country 和 geoip_city 指令暴露了该模块中可用的多个嵌入式变量。geoip_country 指令允许使用一些变量来区分客户端的来源地/国。这些变量包括 $ geoip_country_code、$geoip_country_code3 和 $geoip_country_name。country code 变量返回由两个字母组成的国家/地区代码,以 3 结尾的变量返回由三个字母组成的国家/地区代码。country name 变量返回国家/地区的全名。

geoip_city 指令启用了相当一部分变量。geoip_city 指令启用了与 geoip_country 指令相同的所有变量,只不过名称不一样,例如 $ geoip_city_country_code、$ geoip_city_country_code3 和 $ geoip_city_country_name。其他变量包括 $ geoip_city、$ geoip_lati tude、$ geoip_longitude、$ geoip_city_continent_code 和 $ geoip_postal_code,所有这些都是它们返回的值的描述性变量。$geoip_region 和 $geoip_region_name 描

述了地区、领地、州、省、联邦土地等。Region 是由两个字母组成的代码,而 region name 是全名。$geoip_area_code(仅在美国有效)返回三位电话区号。您可以利用这些变量记录您的客户端信息。您可以选择性地将此信息作为请求头或变量传输给应用,也可以使用 NGINX 以特定的方式路由流量。

参考资料
geoip 模块文档
GeoIP Update GitHub

3、基于国家/地区的访问限制

根据合同或应用要求限制来自特定国家/地区的访问。

将您要阻止或允许的国家代码映射到变量:

load_module 
    "/usr/lib64/nginx/modules/ngx_http_geoip_module.so";
http {
    map $geoip_country_code $country_access { 
        "US" 0;
        default 1;
    }
    # ...
}

此映射会将新变量 $country_access 设置为 1 或 0。如果客户端 IP 地址来自美国,该变量将设置为 0;如果客户端 IP 地址来自其他国家/地区,该变量将设置为 1。
现在,在我们的 server 代码块中,我们将使用 if 语句来拒绝来自美国以外的任何人的访问:

server {
    if($country_access = '1') { 
    return 403;
    }
    # ...
}

如果 $country_access 变量设置为 1,则这个 if 语句的计算结果为 True。如果为 True,服务器将返回 403 unauthorized,否则服务器就正常运行。因此,这个 if 代码块只是用来拒绝非美国地区的用户。
详解
这个例子简明扼要地说明了如何仅允许几个国家/地区的访问,您可以根据自己的需求在此基础之上进行延伸。您可以借鉴这一做法,根据 GeoIP 模块提供的任何嵌入式变量允许或阻止访问。

4、查找原始客户端

NGINX 服务器前面设置了代理,您需要查找原始客户端 IP 地址。

使用 geoip_proxy 指令定义代理 IP 地址范围,并使用 geoip_proxy_recursive 指令查找原始 IP:

load_module "/usr/lib64/nginx/modules/ngx_http_geoip_module.so";
http {
    geoip_country /etc/nginx/geoip/GeoIP.dat; 
    geoip_city /etc/nginx/geoip/GeoLiteCity.dat; 
    geoip_proxy 10.0.16.0/26; 
    geoip_proxy_recursive on;
    # ...
}

geoip_proxy 指令定义了一个无类域间路由(CIDR)范围,我们的代理服务器就在这个范围内,并指示 NGINX 利用 X-Forwarded-For 请求头查找客户端 IP 地址。geoip_proxy_recursive 指令指示 NGINX 递归性地从 X-Forwarded-For 请求头中查找已知的最后一个客户端 IP。

Forwarded 请求头已经成为为被代理请求添加代理信息的标准请求头。NGINX GeoIP 模块使用的请求头是 X-Forwarded-For,否则就无法在写入时进行配置。虽然 X-Forwarded-For 不是官方标准,但仍然是被大多数代理广为使用、接受和设置的请求头。
详解
您可能会发现,如果您在 NGINX 前面使用代理,NGINX 将获取代理(而非客户端)的 IP 地址。对此,当在给定范围内打开连接时,您可以使用 geoip_proxy 指令指示NGINX 使用 X-Forwarded-For 请求头。geoip_proxy 指令使用一个地址或 CIDR 范围。当 NGINX 前面有多个传输流量的代理时,您可以使用 geoip_proxy_recursive 指令递归性地从 X-Forwarded-For 地址中搜索和查找源客户端。当在 NGINX 前面使用Amazon Web Services Elastic Load Balancing(AWS ELB)、谷歌负载均衡器或Microsoft Azure 负载均衡器等负载均衡器时,这种方法比较有用。

5、限制连接数

根据预定义的键(例如客户端的 IP 地址)限制连接数。

构造一个共享内存区来保存连接指标,并使用 limit_conn 指令限制打开的连接数:

http {
    limit_conn_zone $binary_remote_addr zone=limitbyaddr:10m; 
    limit_conn_status 429;
# ...
    server {
        # ...
        limit_conn limitbyaddr 40;
        # ...
    }
}

此配置创建了一个名为 limitbyaddr 的共享内存区。使用的预定义键是二进制形式的客户端 IP 地址。共享内存区的大小设置为 10 MB。limit_conn 指令使用两个参数:limit_conn_zone 名称和允许的连接数量。limit_conn_status 定义了连接状态被限制为 429 时的响应,此时表示响应过多。limit_conn 和 limit_conn_status 指令在 http、server 和 location 上下文中有效。
详解
通过使用键来限制连接数量,您不仅能够防止滥用,而且还能在所有客户端之间公平共享资源。请务必谨慎使用预定义键。正如我们在前面的示例中所示,如果许多用户位于源自同一 IP 地址的同一网络上,例如当在网络地址转换(NAT)后面时,使用 IP 地址是不合理的,这会使整个客户端组受到限制。limit_conn_zone 指令仅在 http 上下文中有效。您可以利用 NGINX 在 http 上下文中任意数量的可用变量来构建一个限制字符串。有一种更简洁的方法是,根据具体的用例利用一个变量(例如会话 cookie)识别应用层的用户。limit_conn_status 默认值为 503 时,代表服务不可用。由于服务可用,我们最好使用 429,而 500 级的响应码表示服务器错误,400 级的响应码表示客户端错误。

测试限制可能是个很棘手的问题,测试方案通常很难在替代环境中模拟实时流量。在这种情况下,您可以将 limit_req_dry_run 指令设置为 on,然后使用访问日志中的变量 $ limit_req_status。$limit_req_sta tus 变量将计算为 PASSED、DELAYED、REJECTED、DELAYED_DRY_RUN 或 REJECTED_DRY_RUN。启用 dry run 后,您将能够分析实时流量日志,并在真正实施限制前根据需要调整限制,从而确保您的限制配置正确。

6、限制速率

通过预定义的键(例如客户端的 IP 地址)来限制请求的速率。

利用限速模块限制请求速率:

http {
    limit_req_zone $binary_remote_addr
        zone=limitbyaddr:10m rate=3r/s;
    limit_req_status 429;
    # ...
    server {
        # ...
        limit_req zone=limitbyaddr;
        # ...
    }
}

此示例配置创建了一个名为 limitbyaddr 的共享内存区。使用的预定义键是二进制形式的客户端 IP 地址。共享内存区的大小设置为 10 MB。该区域使用关键字参数设置速率。limit_req 指令使用了一个必不可少的关键字参数:zone。zone 指示了要使用哪个共享内存请求限制区的指令。根据 limit_req_status 指令的定义,超过明示速率的请求将返回 429 HTTP 代码。建议设置一个 400 级范围的状态码,因为默认值是 503,这代表服务器有问题,而实际问题是出在客户端方面。

使用 limit_req 指令的可选关键字参数来启用两级速率限制:

server {
    location / {
        limit_req zone=limitbyaddr burst=12 delay=9;
    }
}

在某些情况下,客户端需要同时发出许多请求,此后先在一段时间内降低速率,然后再发出更多请求。您可以使用关键字参数 burst 允许客户端超过其速率限制但不拒绝其请求。超出速率的请求将延迟处理,以将速率限制匹配到配置的值。有一组关键字参数可以改变这种行为,即 delay 和 nodelay。nodelay 参数不带值,只允许客户端一次性消耗所有流量突发值;但是必须要先等待足够的时间,直到满足速率限制要求为止,否则所有请求都会被拒绝。在此示例中,如果我们使用 nodelay,客户端可以在第一秒消耗 12 个请求,但是必须要在初始请求之后等待 4 秒才能发出另一个请求。delay 关键字参数定义了在不限流的情况下可以预先发出多少请求。在这种情况下,客户端可以毫无延迟地预先发出 9 个请求,接下来的 3 个将受到限制,此后 4 秒内的任何请求都将被拒绝。
详解
限速模块非常强大,可以防止滥用快速请求,同时仍然为每个人提供优质服务。限制请求速率的原因有很多,安全性就是其中之一。您可以通过严格限制登录页面的速率来防御暴力破解攻击。您可以对所有请求设置合理的限制,从而防止恶意用户试图对您的应用拒绝服务或浪费资源的不轨行为。限速模块的配置很像实操指南 3.5 中描述的连接限制模块,并且存在许多相同的问题。您可以按照每秒限速,也可以按照每分钟限速。当达到速率限制时,日志就会记录事件。此外,还有一条指令没有在示例中给出:limit_req_log_level,它的默认值为 error,您也可以把它设置为 info、notice 或warn。

测试限制可能是个很棘手的问题,测试方案通常很难在替代环境中模拟实时流量。在这种情况下,您可以将 limit_req_dry_run 指令设置为 on,然后使用访问日志中的变量 $ limit_req_status。$limit_req_status 变量将计算为 PASSED、REJECTED 或REJECTED_DRY_RUN。启用 dry run 后,您将能够分析实时流量日志,并在真正实施限制前根据需要调整限制,从而确保您的限制配置正确。

7、限制带宽

按客户端限制资产的下载带宽。

使用 NGINX 的 limit_rate 和 limit_rate_after 指令限制响应客户端的带宽:

location /download/ { 
    limit_rate_after 10m; 
    limit_rate 1m;
}

这个 location 代码块的配置指定,对于前缀为 download 的 URI,向客户端提供响应的速率将在 10 MB 之后被限制为每秒 1 MB。带宽限制是针对每个连接的,因此您可能希望在适用的情况下配合使用连接限制和带宽限制。
详解
通过限制特定连接的带宽,NGINX 能够以您指定的方式在所有客户端上共享其上传带宽,并且只需 limit_rate_after 和 limit_rate 这两个指令就可以做到这一点。limit_rate_after 指令几乎可以在任何上下文中进行设置,包括 http、server、location 以及location 代码块内的 if。limit_rate 指令的适用上下文与 limit_rate_after 相同,但是它还可以通过一个名为 $limit_rate 的变量进行设置。

limit_rate_after 指令规定,在传输指定数量的数据之前不得限制连接的速率。limit_rate 指令指定了给定上下文中的速率限制,默认单位是每秒字节数,但是您也可以设置为 m(兆字节)或 g(千兆字节)。这两条指令的默认值都是 0,表示不对下载速率进行任何限制。此模块允许您以编程方式更改客户端的速率限制。

你可能感兴趣的:(NGINX,nginx,运维,linux)