要问目前哪个Web服务器最流行,可能大家都会说是Nginx。这个老毛子开发的神器凭借高性能了、友好的配置、优秀的模块机制,反向代理支持迅速占领市场并走上Web服务器市场的No1. 本文我们就来谈谈对于开发人员来说Nginx的一些妙用方法。
从这个简单的配置,我们可以开始构建必要的复杂性。
如果服务需要以一台服务器为主的主动/被动配置 对于请求处理,将一个后端服务器做备份,配置方法如下:
...
upstream backend {
server job:10000;
server backup:10000 backup;
}
...
Backup选项指示,表示nginx将使用该服务器为备用服务器,只有当主服务器(job:10000)不可用时候,才会代理到backup:10000。
默认情况下,服务器在1次连接错误或超时后标记为不可用。但是该阈值可以通过max_fails来配置:
...
upstream backend {
# 主服务器失败尝试3次
server job:10000 max_fails=3;
# 备用服务器尝试失败次数10
server backup:10000 backup max_fails=10;
}
...
除了连接错误和超时,还可以使用HTTP 状态码,比如500,502等来定义不可用状态。该配置,由proxy_next_upstream语句定义。
...
upstream backend {
server job:10000;
server backup:10000 backup;
}
server {
server_name proxy;
listen 8000;
# 在连接错误,超时或者http 429(请求超出)的时候时候,跳转到下一个pstream。
proxy_next_upstream error timeout http_429;
location / {
proxy_pass http://backend;
}
}
...
如果要代理K8s集群服务,由于其自带负载均衡配置中应该设置max_fails=0,以免踢掉正常的服务器:
...
upstream backend {
server cck8s.svc max_fails=0;
}
...
这样nginx就不会将 K8s服务标记为不可用。即不会去尝试做被动健康检查,直接利用的K8s集群的主动健康检查。
有时需要根据某些HTTP标头值路由请求。比如查询范围、cookie值或、主机名或者他们的任意组合。这是Nginx最强大的地方,其他同类的代理基本上都做不到这一点。Nginx请求路由的关键是ngx_http_map_module。该模块允许从组合中定义变量带有正则表达式的变量。假设微服务架构中有3个后端服务来处理不同类型的数据:
返回刚刚收集的最新数据的实时数据服务。
返回旧数据的历史数据服务。
返回预先计算的数据的聚合数据服务。
这些服务通过相同的接口提供用户服务,接口形式如下
<date>.cc.api/?report=<report>
现在有几个用户用例:
2021-04-01.cc.api/?report=list_records 应该提供历史数据服务。
cc.api/?report=list_records 应该提供实时数据服务。
cc.api/?report=counters提供聚合数据服务。
2018-11-01.cc.api/?report=counters 提供历史聚合数据服务。
如果自己在程序中自己写路由则非常复杂,而用Nginx可以帮你通过很少配置就搞定:
首先,定义3个后端服务器:
upstream live {
server live1:8000;
server live2:8000;
server live3:8000;
}
upstream hist {
server hist1:9999;
server hist2:9999;
}
upstream agg {
server agg1:7100;
server agg2:7100;
server agg3:7100;
}
接下来,定义将侦听所有请求并以某种方式路由提供服务:
server {
server_name *.cc.api "";
listen 80;
location / {
proxy_pass http://???;
}
}
问题是我们应该写什么proxy_pass指示?
由于nginx配置是声明性的,可以编写 proxy_pass http://$destination/并使用map构建目标变量。
示例服务中,根据report请求变量和日期子域名,提取到变量中的内容为:
map $host $date {
"~^((?<subdomain>d{4}-d{2}-d{2}).)?cc.api$" $subdomain;
default "";
}
Map会通过正则解析$host变量,并将解析结果设置$date。
此处主要使用了有2条map规则 :主要的规则是正则表达式,另一个是后备表示为default关键词。
正则表示式子,我们不在详细介绍,如果有兴趣可以搜索虫虫以前的文章。此处规则为^开头,提取时间子域名:
d{4}-d{2}-d{2}解析日期格式 2021-06-28。
?<subdomain>被称为捕获组,它只是为匹配的部分命名正则表达式。然后在映射规则的右侧使用捕获组来分配。$date多变,而且子域是可选的,因此需要 用圆括号括起来(子域分隔符)并在整个组前添加?。
要提取report,无需使用map,nginx的arg_<param>查询参数的是预定义变量。 可以直接用$arg_report。
好的,有了日期report告。接着构建 $destination变量,用另一个map,诀窍是在后面map中可以利用上面的变量(包括自定义的)创建新变量的变量组合:
map "$arg_report:$date" $destination {
"~counters:.*" agg;
"~.*:.+" hist;
default live;
}
这里的组合是一个字符串,其中2个变量用冒号连接。冒号是个人选择,用于方便。实际上可以使用任何符号,只要确保正则表达式是明确的。
在map,有3个规则。
首先是设置$destination到gg时report查询参数是 counters。
二是设置 $destination到 hist时,$date变量不为空。
当没有其他匹配时设置的默认值是设置$destination至 live。
map中的正则表达式按顺序进行评估。
注意 $destinationvalue 是代理后端的名称。
完整的配置如下:
events {}
http {
upstream live {
server live1:8000;
server live2:8000;
server live3:8000;
}
upstream hist {
server hist1:9999;
server hist2:9999;
}
upstream agg {
server agg1:7100;
server agg2:7100;
server agg3:7100;
}
map $host $date {
"~^((?<subdomain>d{4}-d{2}-d{2}).)?cc.api$" $subdomain;
default "";
}
map "$arg_report:$date" $destination {
"~counters:.*" agg;
"~.*:.+" hist;
default live;
}
server {
server_name *.cc.api "";
listen 80;
location / {
proxy_pass http://$destination/;
}
}
}
如果使用Consul进行服务发现,则可以通过以下方式访DNS。 简单的
curl myApp.service.consul
非常方便,但没有人知道如何解析名称 .consul zone。
无论如何,要通过 Consul DNS 在 nginx 中路由请求很一件。nginx有一个 resolver模块nginx 中用于使用自定义 DNS 服务器的指令(注意就是最近爆严重漏洞的那个高危写入漏洞的CVE-2021-23017的模块,记得升级哦)。
以下下面是配置Nginx转发DNS请求给Consul配置:
...
server {
server_name *.cc.api "";
listen 80;
# 解析使用Consul DNS. 递归请求到114和谷歌DNS.
resolver 10.0.0.1:8600 10.0.0.2:8600 10.0.0.3:8600 114.114.114.114 8.8.8.8
location /v1/api {
proxy_pass http://prod.cc.api.consul/;
}
location /v1/rpc {
proxy_pass http://prod.rpc.service.consul/;
}
}
...
Nginx 是一个非常通用的工具。 它有丰富的配置语言为开发人员提供不错的功能。比如具有配置故障转移的主动/被动负载平衡、灵活的请求路由与Consul DNS 轻松集成等。虽然有些配置比较丑,甚至使用了可怕的正则,但是这也许就是他的魅力所在吧。