nginx之proxy

最近项目中使用到nginx的反向代理,之前也一直听过nginx的反向代理,但一直没有去详细了解,机会来啦就要抓住,本篇文章将从何为代理到nginx反向代理到最后主要配置指令的详细说明为顺序展开讲解~

为何代理

正向代理(forward proxy):是一个位于客户端和目标服务器之间的服务器(代理服务器),为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的内容返回给客户端。

有时候,用户想要访问某国外网站,该网站无法在国内直接访问,但是我们可以访问到一个代理服务器,这个代理服务器可以访问到这个国外网站。这样呢,用户对该国外网站的访问就需要通过代理服务器来转发请求,并且该代理服务器也会将请求的响应再返回给用户。这个上网的过程就是用到了正向代理。

正向代理

正向代理,其实是”代理服务器”代理了”客户端”,去和”目标服务器”进行交互。

正向代理的用途:

1)突破访问限制

2)提高访问速度

3)隐藏客户端真实IP

反向代理(reverse proxy):是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。通过反向代理服务器访问目标服务器时,客户端是不知道真正的目标服务器是谁的,甚至不知道自己访问的是一个代。

用的最多的就是负载均衡,提供web服务的服务器不止一台,一般是一个集群,此时就需要一台统一对外的代理服务器来接收转发相应请求到后端真正处理请求的web服务器上。这个代理服务器一般也叫负载均衡服务器。

反向代理

反向代理,其实是”代理服务器”代理了”目标服务器”,去和”客户端”进行交互。

反向代理用途:

1)隐藏真实服务器IP(一般负载均衡会提供VIP)

2)负载均衡(平均真实服务器的负载)

3)提高访问速度(主要是可以缓存部分短时间内重复访问的静态资源)

4)提供安全保障(可以作为后端服务的防火墙,阻挡部分web攻击,提供访问认证等)

正向代理和反向代理的区别

虽然正向代理服务器和反向代理服务器所处的位置都是客户端和真实服务器之间,所做的事情也都是把客户端的请求转发给服务器,再把服务器的响应转发给客户端,但是二者之间还是有一定的差异的。

1、正向代理其实是客户端的代理,帮助客户端访问其无法访问的服务器资源。反向代理则是服务器的代理,帮助服务器做负载均衡,安全防护等。

2、正向代理一般是客户端架设的,比如在自己的机器上安装一个代理软件。而反向代理一般是服务器架设的,比如在自己的机器集群中部署一个反向代理服务器。

3、正向代理中,服务器不知道真正的客户端到底是谁,以为访问自己的就是真实的客户端。而在反向代理中,客户端不知道真正的服务器是谁,以为自己访问的就是真实的服务器。

4、正向代理和反向代理的作用和目的不同。正向代理主要是用来解决访问限制问题。而反向代理则是提供负载均衡、安全防护等作用。二者均能提高访问速度。

Nginx反向代理

nginx反向代理主要涉及到ngx_http_proxy_module,大部分情况可能会借助ngx_http_upstream_module模块。今天主要讲解这两个模块。

ngx_http_proxy_module模块将请求转发到其他服务器。举个官方🌰:

1
2
3
4
5
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

ngx_http_upstream_module模块主要用于定义一组可用于各种pass流向的服务。举个官方🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;

server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}

server {
location / {
proxy_pass http://backend;
}
}

upstream还可以为每个设备设置状态值,这些状态值的含义分别如下:

  • down:表示当前的server暂时不参与负载.
  • weight:默认为1。weight越大,负载的权重就越大。
  • max_fails:允许请求失败的次数,默认为1。当超过最大次数时,返回proxy_next_upstream 模块定义的错误.
  • fail_timeout : max_fails次失败后,暂停的时间。
  • backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。

配置指令详解

proxy_pass

1
2
3
Syntax:   proxy_pass URL;
Default: —
Context: location, if in location, limit_except

此指令用于指定web后端服务地址,设置协议、地址和可选URI,格式为:协议(http|https)://+地址+(/URI),其中地址可以为域名、IP、IP:PORT、Unix Domain Socket以及server group,举个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#示例1:域名+端口
location / {
proxy_pass http://localhost:8000;
}

#示例2: Unix Domain Socket,这里的domain需要以 "unix:" 开头,以 ":" 结束
location / {
proxy_pass http://unix:/tmp/backend.socket:/uri/;
}

#示例3: 地址为server group
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
server backup1.example.com:8080 backup;
}

server {
location / {
proxy_pass http://backend;
}
}

如果domain被解析为多个地址,这些地址将被循环使用,这可以实现负载均衡了。

关于URI

针对设置了URI的情况,原始请求的URI中匹配上location的部分,将被设置中的URI替换,举个🌰:原始URL为 http://domain/proxy/index.html 代理设置的URI为/aaa/,则原URI中的/proxy/将被替换为/aaa/

1
2
3
4
5
6
7
8
9
10
11
# 匹配/proxy/ "/proxy/" 将被 "/aaa/"替换
location /proxy/ {
proxy_pass http://127.0.0.1/aaa/;
}
# 最终代理到URL:http://127.0.0.1/aaa/test.html

#相对于前一种,最后少一个 / )
location /proxy/ {
proxy_pass http://127.0.0.1/aaa;
}
#代理到URL:http://127.0.0.1/aaatest.html

所以,只要设置了uri,则会将匹配上的部分进行字符串替换。

如果只设置了domain,没有设置URI,则location匹配到请求中的原始URI会附加在domain后面,作为URI

1
2
3
4
5
#假设下面四种情况分别用 http://192.168.1.1/proxy/test.html 进行访问。
location /proxy/ {
proxy_pass http://127.0.0.1;
}
#代理到URL:http://127.0.0.1/proxy/test.html

proxy_set_header

1
2
3
4
Syntax:   proxy_set_header field value;
Default: proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
Context: http, server, location

允许重新定义或者添加发往后端服务器的请求头。其值可以包含文本、变量或者二者的组合,当且仅当当前配置级别中没有定义proxy_set_header指令时,会从上面的级别继承配置。 默认情况下,只有两个请求头会被重新定义。

关于proxy_set_header功能可以设置反向代理后的http header中的$host,$http_host,$proxy_host,那么这几个有什么区别呢?

proxy_host:是proxy_pass后面指定的host

http_host和host:都是原始的host字段,比如请求的地址为www.demo.com, 那么反向代理之后还是www.demo.com ,但是如果客户端发送过来的header中没有host字段时,则采用默认的。

举个🌰:

客户端(请求web服务) : 192.168.1.1

nginx作为反向代理服务器: 192.168.1.136

nginx作为后端web服务器: 192.168.1.137

将左侧匹配到的/proxy_path/开头的url全部转发到后端服务器192.168.223.137。

将136代理服务器,137后端服务器的log_format修改为如下:

1
log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';

客户端访问http://192.168.1.136:8080/proxy_path/index.html

1) proxy_set_header Host $host;

1
2
3
4
5
6
7
8
9
10
11
server {
listen 8080;
server_name 192.168.1.136;

location ^~/proxy_path/ {
root "/www/html";
index index.html;
proxy_pass http://192.168.1.137/;
proxy_set_header Host $host;
}
}

这里的Host就是对应日志中的http_host。

查看代理服务器、后端服务器均能在日志中可发现http_host均为192.168.1.136:8080。

1
192.168.1.1 - - [18/Jul/2017:10:21:25 +0800] "GET /favicon.ico HTTP/1.1" 192.168.1.136:8080 404 24 "http://192.168.1.136:8080/proxy_path/index.html" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"

2) proxy_set_header Host $proxy_host;

1
2
3
4
5
6
7
8
9
10
11
server {
listen 8080;
server_name 192.168.1.136;

location ^~/proxy_path/ {
root "/www/html";
index index.html;
proxy_pass http://192.168.1.137/;
proxy_set_header Host $proxy_host;
}
}

首先查看136代理服务器的日志:

1
192.168.1.1 - - [18/Jul/2017:10:30:12 +0800] "GET /proxy_path/index.html HTTP/1.1" 192.168.1.136:8080 304 0 "-" "Mozilla/5.0(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "-"

客户端请求的host为192.168.223.136:8080,而nginx代理服务器作为137后端服务器的客户端,将请求的报文首部重新封装,将proxy_host封装为请求的host。

那么137上面日志请求的host就是其自身,proxy_host就是代理服务器请求的host,也就是后端服务器137

1
192.168.1.136 "192.168.1.1" - - [18/Jul/2017:10:30:12 +0800] "GET /index.html HTTP/1.0" "192.168.1.137" 304 0 "-" "Mozilla/5.0(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36" "192.168.1.1"

同理,proxy_port也即为后端服务器137的web端口80。

3) X-Real-IP $remote_addr;

$remote_addr的值放进变量X-Real-IP中,此变量名可变,$remote_addr的值为客户端的ip

nginx转发136服务器日志格式为:

1
2
3
log_format main '$remote_addr - $remote_user [$time_local] "$request" $http_host '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

nginx后端137服务器的日志格式:

1
2
3
log_format main '$remote_addr "$http_x_real_ip" - $remote_user [$time_local] "$request" "$http_host" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

两者区别在于$http_x_real_ip,添加了这个变量的值,通过设置将真实客户端IP传递到了服务端。

4)X-Forwarded-For 中的 $remote_addr VS $proxy_add_x_forwarded_for

$proxy_add_x_forwarded_for变量包含客户端请求头中的”X-Forwarded-For”,与$remote_addr两部分,他们之间用逗号分开。

举个🌰,135、136、137三台机器均设置为:

1
2
3
4
5
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#日志格式
log_format main '$remote_addr "$http_x_real_ip" - $remote_user [$time_local] "$request" "$http_host" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

代理情况如图所示:

二级代理

135机器上看到的日志中http_x_forwarded_for为真实客户端192.168.1.1,因为此时他的”X-Forwarded-For”部分是空的,所以只有$remote_addr

136机器上看到的日志中http_x_forwarded_for为”192.168.1.1, 192.168.1.135”

137机器上看到的日志中http_x_forwarded_for为”192.168.1.1, 192.168.1.135, 192.168.1.136”

proxy_send_timeout

1
2
3
Syntax:  proxy_send_timeout time;
Default: proxy_send_timeout 60s;
Context: http, server, location

设置代理服务器与后端服务器的请求发送超时时间,这个时间只会在连续的两次写操作中生效,如果在此期间后端没有再接收数据,则连接会被断开。

proxy_read_timeout

1
2
3
Syntax:  proxy_read_timeout time;
Default: proxy_read_timeout 60s;
Context: http, server, location

定义从后端读取数据的超时时间。默认60s

proxy_set_body

1
2
3
Syntax:  proxy_set_body value;
Default: —
Context: http, server, location

允许重新定义请求体,这里的value可以是文本、变量、或者二者的组合。

proxy_bind

1
2
3
Syntax:  proxy_bind address [transparent] | off;
Default: —
Context: http, server, location

此指令用于绑定代理后端web服务器收到的请求报文里源ip端口:

1)如果设定为固定的IP,那么后端真正看到的就是这个固定的IP

2)如果填写的$remote_addr则看到的是真实client的IP

3)如果设置为off,则系统会自动设置本机IP和随机指定一个端口

transparent:参数的作用是透传客户端IP,使用此参数,在linux上,在1.13.1以下版本需要超级用户权限。

如:

1
2
3
4
location / {
proxy_pass http://localhost:8000;
proxy_bind $remote_addr transparent;
}

proxy_next_upstream

1
2
3
Syntax:  proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
Default: proxy_next_upstream error timeout;
Context: http, server, location

指定在后端返回相应情况下转发到下一台服务器。

error : 和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现错误
timeout :和后端服务器建立连接时,或者向后端服务器发送请求时,或者从后端服务器接收响应头时,出现超时
invalid_header : 后端服务器返回空响应或者非法响应头
http_500 : 后端服务器返回的响应状态码为500
http_502 : 后端服务器返回的响应状态码为502
http_503 : 后端服务器返回的响应状态码为503
http_504 : 后端服务器返回的响应状态码为504
http_404 : 后端服务器返回的响应状态码为404
off : 停止将请求发送给下一台后端服务器

举个🌰:

1
proxy_next_upstream http_500 | http_502 | http_503 | http_504 |http_404

当其中一台服务器返回错误码404,500…等错误时,可以分配到下一台服务器继续处理,提高平台访问成功率,多用于负载均衡。

针对服务端返回error timeout时,如果采用默认设置,则会继续将请求转发给下一个服务器继续处理。如果请求涉及到充值等情况,有可能被充值多次,此时可以将proxy_next_upstream设置为off。当然后端服务自己做幂等处理非常有必要。

  • 本文作者: 风月
  • 本文链接: /nginx-proxy/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!