首页 > 代码库 > 基于Nginx反向代理及负载均衡

基于Nginx反向代理及负载均衡

基于Nginx反向代理及负载均衡

参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

只要没有被启用,默认就是开启的,因为proxy属于nginx内置标准模块,通常实现代理的时候,最核心模块是proxy_pass,用于将用户请求的rui递交至上游服务器的某个URI但这个模块大部分用于location当中,因此要实现将某一URI的访问代理某个上游服务器大致的格式为:

location /name/ {

proxy_pass http://127.0.0.1/remote/;

}

参数解释:

location /name/ 指定当前服务器server的某一访问路径,本来这个location中定义的是root或其他相关参数,从此这个 location不在本地提供任何服务,而是通过proxy_pass模块传至远程其他主机http://127.0.0.1/remote/上去

其中/name/ 和 /remote/ 可以是不相匹配的, nginx可以自动处理这种映射关系。

但需要注意的是,当定义location的时候,其必须有一个转换关系,意为我们当前主机的路径uri要转换另外服务器的uri,这是其对应关系,事实上目标主机的uri可以省略掉,但是一旦省略掉就表示不将其转换

示例:

location/some/path/ {

proxy_pass http://127.0.0.1;#这里只有ip地址,后面没有任何uri

}

例:

wKiom1NrM6_ASWkyAAA7nKSSGe8765.jpg

如图所示当我们请求nginx某个uri的时候,nginx自动的代理至web服务器中,它并不真正提供用户请求的内容而仅仅的将用户的请求接进来,并且代理用户去web服务器上去取数据

通常web服务器会记录日志的,那么这时访问日志中远程ip则是nginx的ip地址

对于一个web服务器来讲记录的所有的ip地址都是代理服务器的,那么我们在某些分析的层面来讲是没有意义的,所以通常在这类场景下通常需要在nginx服务上做一些简单修改,并且在后端服务器上也做一些修改用于记录真实的客户端IP地址

将请求在nginx转发至后端主机的时候在头部加入header信息

参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_header

实现反向代理

IP地址

服务器角色

10.0.10.61

Nginx 实现反向代理

10.0.10.83

Apache上游服务器

10.0.10.82

Apache上游服务器

启动后端apache 服务

[root@mode local]#/usr/local/apache/bin/apachectl start

[root@mode test]# echo‘<h1>10.0.10.83</h1>‘ > index.html 

[root@modephp_test]# curl localhost
<h1>10.0.10.83</h1>

我们期望用户访问某个路径的时候,来源的路径是后端服务器的:

我们只要建一个新的location就可以了,如下所示

新建立location,明确说明使用proxy_pass模块代理后端主机10.0.10.83

       location /test {
            proxy_pass http://10.0.10.83/; #
注意一旦带有斜线就表示有url的如果没有url一定不能带斜线,这就表示访问的test没错,但是将test映射至后端主机,但后端并没有test目录
        }

检查语法是否正确并重新加载配置文件

[root@node1 nginx]#/usr/local/nginx/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

[root@node1 nginx]#/etc/init.d/nginx reload

使用curl访问测试http://10.0.10.61/test

[root@node1 nginx]#curl http://10.0.10.61/test/

<h1>10.0.10.83</h1>

我们再来查看后端apache的访问日志

[root@modetest_php]# tail access_w_20140508.log 

10.0.10.61 - -[08/May/2014:11:04:38 +0800] "GET / HTTP/1.0" 304 - "-""Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, likeGecko) Chrome/34.0.1847.131 Safari/537.36"

10.0.10.61 - - [08/May/2014:11:04:38+0800] "GET / HTTP/1.0" 304 - "-" "Mozilla/5.0(Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/34.0.1847.131 Safari/537.36"

可以看到来源地址都是nginx服务器的地址,对于我们之后分析日志没有任何意义所在,所以那这个时候我们想换成客户端地址必须借助于proxy_set_header模块

实现显示真实客户端IP

proxy_set_header模块使用示例

       location /test {
            proxy_passhttp://10.0.10.83/;
proxy_set_header X-Real-IP $remote_addr;
        }

proxy_set_header表示将发送至upsream server的报文的某首部进行重写;

X-Real-IP 表示 hader的名称为X-Real-IP

传递给X-Real-IP的地址是$remote_addr

再来查看后端日志的访问ip,无论怎么刷新它的记录ip依旧还是nginx

因为后端的服务器只记录了client值,并没有记录我们定义的header的值,所以我们还需要对apache的日志参数做修改

编辑httpd.conf,定义日志格式

[root@mode conf]#vim httpd.conf

将其注释并复制

#LogFormat "%h%l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""combined

将%h改为"%{X-Real-ip}i

LogFormat "%{X-Real-IP}i %l %u %t \"%r\"%>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

%h是表示远端客户的ip地址,这里我们将其改成某个特定首部的值也就是刚才我定义的X-Real-IP,而引用特定首部的值的格式是%{xxx}i这样就表示引用这个首部的值

保存退出并重新加载apache

[root@mode conf]#/usr/local/apache/bin/apachectl graceful

再次访问并查看日志

已看到目前已记录客户端的真实IP地址

[root@modetest_php]# tail -1 access_w_20140508.log 

10.0.10.1- - [08/May/2014:12:44:40 +0800] "GET / HTTP/1.0" 304 -"-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/34.0.1847.131 Safari/537.36"

使用模式匹配

示例:

  location ~/test {
   proxy_pass http://10.0.10.83/hello;
    proxy_set_header X-real-ip $remote_addr;
  }

只要客户端访问的uri中只要包含test的路径则进行跳转至于10.0.10.83的/test的物理路径;

一旦使用了模式匹配,那么后面跟上test之后里面代理的时候,后面一定不能带有路径hello,一定什么路径都不要加,它会将test的内容填充至于后面

测试:

  location ~/test {
   proxy_pass http://10.0.10.83/
hello;#
一定不能带有任何字符
    proxy_set_header X-real-ip $remote_addr;
  }

保存退出并检查语法

[root@node1 nginx]#nginx -t

nginx:[emerg] "proxy_pass" cannot have URI part in location given byregular expression, or inside named location, or inside "if"statement, or inside "limit_except" block in /etc/nginx/nginx.conf:49

nginx:configuration file /etc/nginx/nginx.conf test failed

首先语法检测根本不通过,提示路径中不能加入uri,所以如果出现此类配置方式是连服务都不能启动的

将后面的uri去掉再测试:

       location   /test {
          proxy_passhttp://10.0.10.83;          #
注意没有斜线
          proxy_set_headerX-real-ip $remote_addr;
        }

总结:使用匹配模式,一定不能加uri,只是将其前端的参数传递过来

重新加载并访问测试:

[root@mode test_php]#curl http://10.0.10.61/test
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>
404 Not Found</title>

加入斜线

       location  /test {
            proxy_passhttp://10.0.10.83
/;
            proxy_set_headerX-real-ip $remote_addr;
        }

访问测试:

[root@modetest_php]# curl http://10.0.10.61/test
<h1>10.0.10.83</h1>

如果location 给了uri 而proxy_pass没有跟任何uri 意味着将location的uri附加至proxy_pass 当做请求的路径,哪怕加一个斜线也表示对应的uri,所以请求的test路径相对于http://10.0.10.83/目录下的文件

基于匹配规则实现动静分离

新建location,内容如下

location  ~* \.(jpg|bng|html|css|png|gif|ico|)${
   proxy_pass http://10.0.10.83;
proxy_set_header X-real-ip $remote_addr;
  }

如果访问的内容是不区分大小写的方式:

如果访问的uri后缀是以静态内容或图片格式结尾的,那么直接将请求转向10.0.10.83

检查语法并重启

[root@node1 nginx]#!ngin

[root@node1 nginx]# !ser

然后在服务10.0.10.83服务器创建目录及文件

上传某张图片或创建某路径文件并访问测试

[root@mode if]# pwd
/var/html/php_test/if

[root@mode if]#echo "test 10.0.10.83" > 1.html 

[root@mode if]#curl http://10.0.10.61/if/1.html
1

先来测试一下访问静态内容

[root@node1 nginx]#curl http://10.0.10.61/if/1.html#指定其uri

test 10.0.10.83#可以看到,已经成功跳转至10.0.10.83

所以就算前端没有这样的路径但能映射到后端服务器相关路径都能访问的到

那么假如说有另外服务器可以访问动态内容的话,则可以将所有的动态请求直接转发至动态内容服务器

如下所示

我们本机已经存在fastcgi,所以将nginx的fastcgi功能开启,由于9000端口在本地监听,所以这里默认即可

location ~ \.php$ {#已经默认帮我们定义了location匹配规则,如果后缀是php的uri,那么全部转发至这个主机

root/web/htdocs;#指定其php目录

fastcgi_pass127.0.0.1:9000;#fastcgi在我们本机已监听

fastcgi_indexindex.php;

fastcgi_paramSCRIPT_FILENAME/scripts$fastcgi_script_name;

includefastcgi_params;

}

定义在本机,

那么直接访问测试:

wKiom1NrM8vTPCdOAADCbdtRKmk245.jpg

可以发现,现在我们将2个请求分开了

如果请求的是静态内容则到10.0.10.83上去,如果请求的是动态内容则到本机,而如果动态请求在另外服务器上部署的也完全可以实现分割

基于匹配规则实现上传转发功能

随着我们业务量的越来越大,需求将上传服务器也对其进行分离,在站点上搭建一个专门的服务器用来接收上传

有时候web服务器可以实现上传,web也支持上传,但是需要将DAV功能打开

新建虚机10.0.10.62并开启apache上传功能

[root@node2apache]# ./bin/apachectl  -M  | grep -i ‘dav‘
dav_module (static)
dav_fs_module (static)

这时只需要在DocumentRoot上对应的访问权限上启动对应的DAV即可

加入参数:

Dav on

Options Indexes FollowSymLinks

将网页目录给予写权限

[root@node2apache]# setfacl -m u:apache:rwx /var/www/html/

编辑nginx配置文件,加入if判断语句并引用$request_method模块,判断如果用户操作是put,那么将其转发至10.0.10.62

location / {

root/web/htdocs/;

indexindex.php index.html index.htm;

if ($request_method ~*"PUT"){

proxy_pass http://10.0.10.62;

break;

}

}

保存退出检查语法并重新加载配置

[root@node1 nginx]#nginx -t

nginx: theconfiguration file /etc/nginx/nginx.conf syntax is ok

nginx:configuration file /etc/nginx/nginx.conf test is successful

[root@node1 nginx]#!ser

这时我们可以使用curl -T 上传文件

[root@node1 nginx]#curl -T koi-utf http://10.0.10.62/

查看文件是否存在

[root@node2 conf]#ll /var/www/html/

total 16

-rw-r--r--. 1rootroot11 May6 18:31 index.html

-rw-r--r--. 1rootroot6 Apr 28 21:15 index.html.bak

-rw-r--r--. 1apache apache 2837 May6 00:08 koi-utf

如上,说明支持put的方法,已经可以上传

假设我们现在代理如果用户请求的内容是上传操作则专至62这台服务器,这样就一个静态动态 一个负责上传的角色,完全将服务角色分割开来

由此可见,我们的服务扮演了三种角色,将上传、动态、静态内容分别分发至不同的服务器

nginx实现负载均衡

nginx实现负载均衡有单独一模块来实现需求,叫做upstream

参考:http://nginx.org/en/docs/http/ngx_http_upstream_module.html

示例:

#首先在全局配置中定义upstream name

upstream backend {

server backend1.example.comweight=5;

server backend2.example.com:8080;

server unix:/tmp/backend3;

server backup1.example.com:8080backup;

server backup2.example.com:8080backup;

}

#定义完成后再从location中引用

server {

location / {

proxy_pass http://backend;

}

}

一旦启动了此模块,将引入一个新的上下文(只要加大括号就表示新引入一个新的上下文)因此upstream也引入了一个新的上下文

每个server后面跟了一个主机名或ip 可以加端口,但注意的是前面一定不能加http://

而upstream为一个关键字,后面的backend为名称,说明这是一组服务器可以被轮流访问的

之后在location定义反向代理的时候,定义的再也不是某个指定服务器了而是upstream,因此用户的请求发往这个upstream以后,这个upstream模块会自动从定义的规则中每一次选择一个server 进行分发

为了演示效果,我们将后端的apache服务器分别创建不同的页面,步骤略

配置nginx 先将其备份,方便之后恢复

[root@node1 nginx]#cp nginx.conf nginx.conf.bak
[root@node1 nginx]# vim nginx.conf

将之前定义的location全都删掉,步骤略

在httpd{}上下文中定义upstream 

upstream webservers {

server 10.0.10.62;

server 10.0.10.83;

}

定义upstream名为webservers,其组内定义2个web主机 分别是10.0.10.62和10.0.10.83 注意的是ip前面不能加http://

定义location,如下所示

location / {

#root/web/htdocs/;

#indexindex.php index.html index.htm;

proxy_pass http://webservers;#webservers为定义的upstream的名称

}

假如用户访问的location的根目录,当访问的是根的时候直接使用proxy_pass直接映射至upstream组内的主机

保存退出检测语法并重启nginx

[root@node1 nginx]#!nginx

[root@node1 nginx]#!ser

访问如下

[root@node1 nginx]#curl http://10.0.10.61

10.0.10.62

[root@node1 nginx]#curl http://10.0.10.61

<h1>10.0.10.83</h1>

实现后端realserver健康状态检查

使其一旦出现故障不再将其加进来

定义upstream,如下所示

upstream webservers {

#server 10.0.10.62 ;

server 10.0.10.61 max_fails=3fail_timeout=1s backup;

server 10.0.10.83 weight=1max_fails=3 fail_timeout=2s;

weight=1 设置其权重;

max_fails=3最大允许3次失败;

fail_timeout=2s如果超过2秒则超时;

在第三行可以看到,我们在本机也启动一个web服务器,但是这个服务器不是专门提供工作的,一旦后端服务器出现故障,那么则转至backup服务器,使其服务器专门为用户提供错误页面

backup表示其主机始终不会生效除非组内所有主机全部故障

至此,实验结束,感谢各位。

 

 

 

本文出自 “心情依旧” 博客,请务必保留此出处http://yijiu.blog.51cto.com/433846/1408443