基于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
}
例:
如图所示当我们请求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 服务
[[email protected] local]#/usr/local/apache/bin/apachectl start
[[email protected] test]# echo‘<h1>10.0.10.83</h1>‘ > index.html
[[email protected]_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目录
}
检查语法是否正确并重新加载配置文件
[[email protected] 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
[[email protected] nginx]#/etc/init.d/nginx reload
使用curl访问测试http://10.0.10.61/test
[[email protected] nginx]#curl http://10.0.10.61/test/
<h1>10.0.10.83</h1>
我们再来查看后端apache的访问日志
[[email protected]_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,定义日志格式
[[email protected] 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
[[email protected] conf]#/usr/local/apache/bin/apachectl graceful
再次访问并查看日志
已看到目前已记录客户端的真实IP地址
[[email protected]_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;
}
保存退出并检查语法
[[email protected] 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,只是将其前端的参数传递过来
重新加载并访问测试:
[[email protected] 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;
}
访问测试:
[[email protected]_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
检查语法并重启
[[email protected] nginx]#!ngin
[[email protected] nginx]# !ser
然后在服务10.0.10.83服务器创建目录及文件
上传某张图片或创建某路径文件并访问测试
[[email protected] if]# pwd
/var/html/php_test/if
[[email protected] if]#echo "test 10.0.10.83" > 1.html
[[email protected] if]#curl http://10.0.10.61/if/1.html
1
先来测试一下访问静态内容
[[email protected] 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;
}
定义在本机,
那么直接访问测试:
可以发现,现在我们将2个请求分开了
如果请求的是静态内容则到10.0.10.83上去,如果请求的是动态内容则到本机,而如果动态请求在另外服务器上部署的也完全可以实现分割
基于匹配规则实现上传转发功能
随着我们业务量的越来越大,需求将上传服务器也对其进行分离,在站点上搭建一个专门的服务器用来接收上传
有时候web服务器可以实现上传,web也支持上传,但是需要将DAV功能打开
新建虚机10.0.10.62并开启apache上传功能
[[email protected]]# ./bin/apachectl -M | grep -i ‘dav‘
dav_module (static)
dav_fs_module (static)
这时只需要在DocumentRoot上对应的访问权限上启动对应的DAV即可
加入参数:
Dav on
Options Indexes FollowSymLinks
将网页目录给予写权限
[[email protected]]# 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;
}
}
保存退出检查语法并重新加载配置
[[email protected] nginx]#nginx -t
nginx: theconfiguration file /etc/nginx/nginx.conf syntax is ok
nginx:configuration file /etc/nginx/nginx.conf test is successful
[[email protected] nginx]#!ser
这时我们可以使用curl -T 上传文件
[[email protected] nginx]#curl -T koi-utf http://10.0.10.62/
查看文件是否存在
[[email protected] 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 先将其备份,方便之后恢复
[[email protected] nginx]#cp nginx.conf nginx.conf.bak
[[email protected] 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
[[email protected] nginx]#!nginx
[[email protected] nginx]#!ser
访问如下
[[email protected] nginx]#curl http://10.0.10.61
10.0.10.62
[[email protected] 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表示其主机始终不会生效除非组内所有主机全部故障
至此,实验结束,感谢各位。