目录
前言
本文主要为总结nginx学习。
一、Nginx安装
1.yum安装
http://www.nginx.org官方网站:http://www.nginx.org
Nginx版本类型
Mainline version: 主线版,即开发版
Stable version: 最新稳定版,生产环境上建议使用的版本
Legacy versions: 遗留的老版本的稳定版
配置Yum源的官网:http://nginx.org/en/linux_packages.html
#安装yum工具包
yum install yum-utils -y
要设置yum存储库,请创建名为/etc/yum.repos.d/nginx.repo的文件,其中包含以下内容
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
安装
[root@nginx-server yum.repos.d]# yum install -y nginx
[root@localhost ~]# nginx -V #查看nginx的详细信息,包括模块,安装目录、用户
启动并设置开机启动
[root@nginx-server ~]# systemctl start nginx
[root@nginx-server ~]# systemctl enable nginx
浏览器输入ip访问:
2.源码安装
安装环境
yum -y install gcc gcc-c++ pcre pcre-devel gd-devel openssl openssl-devel zlib zlib-devel
添加普通用户账号来运行nginx:
useradd -M -s /sbin/nologin nginx
安装nginx
[root@localhost ~]# rpm -qa|grep wget && wget http://nginx.org/download/nginx-1.24.0.tar.gz
[root@localhost ~]# mkdir -p /tmp/nginx/
[root@localhost ~]# tar -xzvf nginx-1.24.0.tar.gz -C /usr/local/
[root@localhost ~]# cd /usr/local/nginx-1.24.0/
[root@localhost nginx-1.24.0]# ./configure --prefix=/usr/local/nginx --user=nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/tmp/nginx/client_body --http-proxy-temp-path=/tmp/nginx/proxy --http-fastcgi-temp-path=/tmp/nginx/fastcgi --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-pcre --with-http_realip_module --with-stream
#这里的模块安装时可以不用添加
[root@localhost nginx-1.24.0]# make && make install
启动
[root@localhost ~]# /usr/local/nginx/sbin/nginx #或者直接 nginx
通过 nginx 命令控制 nginx 服务
nginx -c /path/nginx.conf - # 以特定目录下的配置文件启动nginx
nginx -s reload # 修改配置后重新加载生效
nginx -s stop # 快速停止nginx
nginx -s quit # 正常停止nginx
nginx -t # 测试当前配置文件是否正确
nginx -t -c /path/to/nginx.conf # 测试特定的nginx配置文件是否正确
#注意:
nginx -s reload 命令加载修改后的配置文件,命令下达后发生如下事件:
1. Nginx的master进程检查配置文件的正确性,若是错误则返回错误信息,nginx继续采用原配置文件进行工作(因为worker未受到影响)
2. 如果配置文件没问题,则Nginx会启动新的worker进程,并且采用新的配置文件
3. Nginx将新的请求分配新的worker进程
4. Nginx等待以前的worker进程的全部请求已经都返回后,关闭相关worker进程
5. 重复上面过程,直到全部旧的worker进程都被关闭掉
3.动态模块安装
Nginx分为5大模块分别为:核心模块,标准HTTP模块,可选HTTP模块,邮件服务模块和第三方模块。
这5个模块由上到下重要性一次递减。
(1)核心模块;
核心模块是Nginx服务器正常运行必不可少的模块,如同操作系统的内核。它提供了Nginx最基本的核心服务。像进程管理、权限控制、错误日志记录等;
(2)标准HTTP模块;
标准HTTP模块支持标准的HTTP的功能;
(3)可选HTTP模块;
可选HTTP模块主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务;
(4)邮件服务模块;
邮件服务模块主要用于支持Nginx的邮件服务;
(5)第三方模块;
第三方模块是为了扩展Nginx服务器应用,完成开发者想要的功能;
3.1非第三方模块安装
查看nginx安装了哪些模块
以安装--with-http_auth_request_module 模块(基于http响应状态码做权限控制)为例
进入到解压的nginx源码包目录里重新编译 在最后添加上新的模块
之后 make 一下 注意不要安装!
编译完成后会生成一个objs目录
将原来的nginx二进制程序备份,使用刚编译好的新二进制程序替代
[root@nginx sbin]# mv /usr/local/nginx/sbin/nginx{,.bak}
[root@nginx nginx-1.24.0]# cp ./objs/nginx /usr/local/nginx/sbin/
再此查看nginx -V 有添加选项 说明安装成功
如果模块要功能没有启用可以 restart 重启服务试试
3.2第三方模块安装
以Nginx-echo模块为例
Nginx-echo可以在Nginx中用来输出一些信息,是在测试排错过程中一个比较好的工具
它可以将信息进行汇总输出, 给维护人员带来很大帮助
下载nginx-echo模块
wget https://github.com/openresty/echo-nginx-module/archive/v0.61.tar.gz -P /soft/
解压
# cd /soft/
# tar xf v0.61.tar.gz -C /usr/local/nginx/modules
nginx -V 查看原来参数
进入nginx解压目录,重新编译
# cd /usr/local/nginx-1.24.0/
# ./configure --原来的编译选项 --add-module=/usr/local/nginx/modules/echo-nginx-module-0.61/
# make
将原来的nginx二进制程序备份,使用刚编译好的新二进制程序替代
[root@nginx sbin]# mv /usr/local/nginx/sbin/nginx{,.bak}
[root@nginx nginx-1.24.0]# cp ./objs/nginx /usr/local/nginx/sbin/
再此查看nginx -V 有添加选项 说明安装成功
至此,echo模块安装完毕.
二、配置文件详解
1.配置文件
nginx主配置文件主要有以下几大块
- 全局块(main):配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
- events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。epoll
- http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
- http.server块:配置虚拟主机的相关参数,一个http中可以有多个server。
- http.server.location块:配置请求的路由,以及各种页面的处理情况。
配置文件路径/etc/nginx/nginx.conf
此路径一般为yum安装路径 具体路径如果不清楚可以使用 ngin -V 查看 conf 路径
# 全局参数设置
user nginx; #设置nginx使用的用户
worker_processes 4; #设置nginx启动进程的数量,一般设置成与逻辑cpu数量相同
error_log logs/error.log; #指定错误日志
pid /var/run/nginx.pid;
events {
worker_connections 1024; #设置一个进程的最大并发连接数
}
# http 服务相关设置
http {
include mime.types; #关联mime类型,关联资源的媒体类型
default_type application/octet-stream; #根据文件的后缀来匹配相应的MIME类型
log_format main 'remote_addr - remote_user [time_local] "request" '
'status body_bytes_sent "$http_referer" '
'"http_user_agent" "http_x_forwarded_for"';
#这一段是Nginx的日志格式配置:
remote_addr:客户端IP地址。
remote_user:客户端用户名。
[time_local]:访问时间,格式为本地时间。
"request":HTTP请求方法、URI和协议版本。
status:HTTP响应状态码。
body_bytes_sent:发送给客户端的数据量,不包括响应头。
"$http_referer":HTTP Referer头信息,表示访问来源。
"http_user_agent":HTTP User-Agent头信息,表示客户端浏览器或应用程序的信息。
"http_x_forwarded_for":HTTP X-Forwarded-For头信息,表示客户端真实IP地址,用于反向代理场景。
access_log /var/log/nginx/access.log main; #设置访问日志的位置和格式
sendfile on; #用于开启文件高效传输模式,一般设置为on,若nginx是用来进行磁盘IO负载应用时,可以设置为off,降低系统负载
tcp_nopush on; # 减少网络报文段数量,当有数据时,先别着急发送,确保数据包已经装满数据,避免了网络拥塞
tcp_nodelay on; # 提高I/O性能,禁用Nagle算法,确保数据尽快发送,提高可数据传输效率 (Nagle算法是一种常用的TCP网络传输优化算法,它将多个小的数据包合并成一个大的数据包发送,以减少网络传输的开销)
gzip on; #是否开启gzip压缩,将注释去掉开启
keepalive_timeout 65; #设置长连接的超时时间,即:请求完成之后还要保持连接多久
# 虚拟服务器的相关设置
server {
listen 80; #设置监听的端口
server_name localhost; #设置绑定的主机名、域名或ip地址
charset koi8-r; # 设置编码字符
location / {
root /var/www/nginx; #设置服务器默认网站的根目录位置,需要手动创建
index index.html index.htm; #设置默认打开的文档
}
error_page 500 502 503 504 /50x.html; #设置错误信息返回页面
location = /50x.html {
root html; #这里的绝对位置是/usr/local/nginx/html
}
}
}
修改配置文件后
[root@localhost ~]# /usr/local/nginx/sbin/nginx -t
#最简单的方法让服务器全局都能使用nginx命令
[root@localhost nginx]# ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx #注意一定要是绝对路径!!!
[root@localhost nginx]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost nginx]# nginx -s reload
2.location 详解
location用于匹配客户请求URL
修饰符:
= 精确匹配,优先级最高
^~ 前缀匹配,优先级高于正则匹配
~ 正则匹配,区分大小写
~* 正则匹配,不区分大小写
/ 没有修饰符,优先级最低
无修饰符的情况也表示URL前缀匹配,但优先级低于正则匹配
若URL为 / 表示匹配所有.其他location未匹配到的均能匹配到
优先级顺序:
- location = /uri
- location ^~ /uri/
- location ~ pattern location ~* pattern
- location /img/
- location / 可匹配所有的访问请求
注:
1). 优先级的高低与location出现的顺序无关
2). 尽量使用单一的location修饰符去完成任务。以免出现优先级问题。
2.1示例
server配置如图:
1.客户端访问 www.qf.com => 返回403 #能匹配到配置1和2,配置1的优先级更高
2.客户端访问 www.qf.com/index.html => 返回/www/qf/index.html文件的内容 #1是等于根 只能匹配到配置2
3.客户端访问 www.qf.com/imgs/a.jpg => 返回/www/imgs/a.jpg图片 #能匹配到配置2,3,4 但配置3的优先级更高
4.客户端访问 www.qf.com/a.jpg => 返回/usr/local/nginx/html/a.jpg图片 #能匹配到2,4 但配置4的优先级更高
5.客户端访问 www.qf.com/www/ => 返回/www/qf/www/index.html文件的内容#只能匹配到配置2
几个注意点:
location匹配的是访问的url中的文件部分,如:http://www.qf.com/imgs/a.jpg?xxx=xxx http://www.qf.com/msie/imgs/a.jpg
URL中的 / 对应的是 $document_root
如果在匹配到的location中的没有定义root ,则为html(安装目录下的html目录)
root指令定义的URL中的 /
3.rewrite
rewrite指令可以说是rewrite指令集中的核心指令。它通过正则表达式的使用改变URI。
可以同时存在一个或者多个指令,按照顺序依次对URL进行匹配和处理。
- 先执行server中的rewrite(如果有的话),得到新的URI, 只执行一次(无论修饰符是last还是break);
- 在location中匹配新的URI;
- 执行location中的rewrite(如果有的话), 如果修饰符是last,重写之后得到新的URI, 再在location中匹配新的URI; 如果修饰符是break,重写后得到新的URI,不再进行location匹配.
regex - 用于匹配URI的正则表达式。使用"()"标记要截取的内容。
注意:
rewrite 接受到的URL不包含HOST地址和参数,因此regex不可能匹配到URI的HOST地址或参数。文件部分
如:
请求的URL为http://myweb.com/source?arg1=value1&arg2=value2
此时rewrite指令接受到的URI 是 "/source", 而不包含"myweb.com",也不包含"?arg1=value1&arg2=value2"
replacement - 匹配成功后用于替换URI 中被截取内容的字符串
默认情况下,如果该字符串是由"http://" 或"https://" 开头的, 则不会继续对URI 进行处理,而是直接将重写后的URI返回给客户端。
如果replacement字符串包括新的请求参数,以往的请求参数会添加到新参数后面。
如果不希望这样,在replacement字符串末尾加一个问号“?”,就可以避免,比如: rewrite ^/users/(.*)$ /show?user=$1? last;
flag - 有break、last、permanent、redirect
last - 停止执行当前这一轮的ngx_http_rewrite_module指令集,然后查找匹配改变后URI的新location;
break - 停止执行当前这一轮的ngx_http_rewrite_module指令集;
redirect - 返回状态码为302的临时重定向;
permanent - 返回状态码为301的永久重定向。
例:
1.如果UA包含"MSIE",rewrite请求到/msid/目录下
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
2.如果query string中包含"post=140",则永久重定向到example.com
if ($args ~ post=140) {
rewrite ^ http://example.com/ permanent;
}
三、虚拟主机
从客户端角度来看,每台虚拟主机是一个独立的服务器。
从服务器角度来看,每台虚拟主机对应只是一个server的配置。
1.基于域名的虚拟主机
所有主机的套接字完全一样,仅以客户端访问的域名进行区分。
配置示例
# 虚拟主机 1
server {
listen 80 default_server; # default_server:默认主机
server_name www.qf1.com;
access_log /www/qf1/logs/access.log main;
error_log /www/qf1/logs/error.log;
location / {
root /www/qf1;
index index.html index.htm;
}
}
# 虚拟主机 2
server {
listen 80;
server_name www.qf2.com;
access_log /www/qf2/logs/access.log main;
error_log /www/qf2/logs/error.log;
location / {
root /www/qf2;
index index.html index.htm;
}
}
# 虚拟主机 3
server {
listen 80;
server_name www.qf3.com;
access_log /www/qf3/logs/access.log main;
error_log /www/qf3/logs/error.log;
location / {
root /www/qf3;
index index.html index.htm;
}
}
客户端配置路由映射
#在 C:\Windows\System32\drivers\etc\hosts 文件中添加两行(linux:/etc/hosts)
192.168.32.192 www.qf1.com
192.168.32.192 www.qf2.com
192.168.32.192 www.qf3.com
2.基于IP的虚拟主机
每个主机套接字中的IP 不一样,以IP来区分。
解析时将不同的域名解析到对应的IP上,要求服务器要有多个IP
配置示例
server {
listen 192.168.10.11:80;
server_name web.11.com;
location / {
root /web11/html;
index index.html index.htm;
}
}
server {
listen 192.168.10.12:80;
server_name web.12.com;
location / {
root /web12/html;
index index.html index.htm;
}
}
3.基于端口的虚拟主机
每个主机套接字中的端口不一样,以端口来区分,在访问时要带端口
配置示例
server {
listen 1111;
server_name web.11.com;
location / {
root /www/11/html;
index index.html index.htm;
}
}
server {
listen 1212;
server_name web.12.com;
location / {
root /www/12/html;
index index.html index.htm;
}
}
四、访问控制
1.用户认证
有时我们会有这么一种需求,就是你的网站并不想提供一个公共的访问或者某些页面不希望公开,我们希望的是某些特定的客户端可以访问.
那么我们可以在访问时要求进行身份认证,就如给你自己的家门加一把锁,以拒绝那些不速之客.
在location中添加以下配置:
location / {
root html;
index index.html index.htm;
auth_basic "user&pass"; # 基本认证
auth_basic_user_file /usr/local/nginx/passwd.db; # 存放用户名和密码的文件
}
生成用户&密码的文件:
[root@nginx ~]# htpasswd -c /usr/local/nginx/passwd.db user1
重载生效后,通过浏览器访问,需要输入正确的用户名和密码才能访问
2.访问控制
方法一:deny/allow
控制逻辑: 从上到下,匹配到即执行相应的策略
例:仅允许192.168.10.0/24访问
location / {
root html;
index index.html index.htm;
allow 192.168.10.0/24;
deny all;
}
方法二:黑/白名单
可以使用geo模块定义一个访问控制的黑/白名单
geo指令可以根据客户端的IP地址来给变量定义不同的值,语法如下:
例:禁止 192.168.10.11 和 127.0.0.1 访问
geo $black_list {
default 0;
192.168.10.11 1;
127.0.0.1 1;
}
server {
listen 80;
server_name www.qf.com;
root /usr/share/nginx/html;
if ($black_list) {
return 403;
}
...
}
3.速度限制
limit_rate指令: 限速
用法:
map指令
map指令根据一个字符串来定义另一个变量的值
用法:
我们可以利用geo和map方便的实现分段限速:
geo $limit {
default 2;
192.168.10.11 1;
192.168.10.13 0;
}
map $limit $rate {
2 128k;
1 1m;
0 0;
}
server {
...
location /download/ {
autoindex on; # 当目录中没有 index文件时列出文件列表, 默认为 off
limit_rate $rate;
}
...
}
limit_rate_after指令
就是在下载多少内容后再以limit_rate限制的速率下载,多用于浏览视频
先给你一点先看,再慢慢给你缓存观看,等你欲罢不能的时候,让你冲会员,冲完会员就快了
用法:
示例:
location /flv/ {
limit_rate_after 500k;
limit_rate 50k;
}
4.流量限制
流量限制 (rate-limiting),是Nginx中一个非常实用的功能。我们可以用来限制用户在给定时间内HTTP请求的数量。
请求,可以是一个简单网站首页的GET请求,也可以是登录表单的 POST 请求。
流量限制可以用作安全目的,比如可以减慢暴力密码破解的速率。通过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(通过日志),还可以用来抵御 DDOS 攻击。
更常见的情况,该功能被用来保护上游应用服务器不被同时太多用户请求所压垮。
Nginx 提供了两种限流方式,一是控制速率,二是控制并发连接数。
4.1Nginx限流配置
模块提供限制请求处理速率能力, 该模块使用了漏桶算法(leaky bucket)。
漏桶算法思路很简单: 水加入到漏桶里(请求入队),漏桶以固定的速度出水(处理请求),当水加的过快,则会直接溢出(拒绝请求),可以看出漏桶算法能强行限制数据的传输速率。
我们可以使用 limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。
配置 limit_req_zone
例: 在http块中进行以下配置:
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
说明:
key :定义限流对象,$binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
zone:定义共享内存区来存储访问信息, one:10m 表示一个大小为10M,名字为one的内存区域。
1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
rate: 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。
Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。
这意味着,100毫秒内,一个请求处理完后,若后续又有请求到达,将拒绝处理该请求。
配置 limit_req
例: 在http/server/location块加入以下配置:
server {
...
location /search/ {
root /www;
index index.html;
limit_req zone=one;
}
...
}
默认情况下,Nginx会在日志中记录由于流量限制而延迟或丢弃的请求,如下所示:
日志条目中包含的字段:
limiting requests - 表明日志条目记录的是被“流量限制”请求
excess - 每毫秒超过对应“流量限制”配置的请求数量
zone - 定义实施“流量限制”的区域
client - 发起请求的客户端IP地址
server - 服务器IP地址或主机名
request - 客户端发起的实际HTTP请求
host - HTTP报头中host的值
4.1处理突发流量
上面例子限制 10r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。
如: limit_req zone=one burst=20;
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。
此处,burst=20 ,若同时有21个请求到达,Nginx 会处理第一个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。
若请求数大于21,将拒绝处理多余的请求,直接返回503.
不过,单独使用 burst 参数并不实用。
假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。
因此,burst 往往结合 nodelay 一起使用。
如: limit_req zone=one burst=20 nodelay;
nodelay 针对的是 burst 参数,burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。
不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。
burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。
这就达到了速率稳定,但突然流量也能正常处理的效果。
4.2限制连接数
ngx_http_limit_conn_module 提供了限制连接数的能力,利用 limit_conn_zone 和 limit_conn 两个指令即可。
下面是 Nginx 官方例子:
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
...
limit_conn perip 10;
limit_conn perserver 100;
...
}
说明:
limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。
limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
需要注意的是:只有当 request header 被后端server处理后,这个连接才进行计数。
五、状态访问
ngx_http_stub_status_module 模块提供对基本状态信息的访问
默认情况下不安装此模块,应在编译时使用 --with-http_stub_status_module 配置参数安装此模块
状态访问统计:
在server中添加如下行
location = /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
重载配置后访问状态页:
说明:
Active connections :活跃连接数
accepts:总共处理的连接数
handled:成功创建的握手数 (相等表示中间没有失败的)
requests :总共处理的请求数 (平均每次握手处理了1.2个数据请求).
Reading :nginx读取到客户端的Header信息数.
Writing :nginx返回给客户端的Header信息数.
Waiting :开启keep-alive的情况下,这个值等于active - (reading + writing),意思就是Nginx说已经处理完正在等候下一次请求指令的驻留连接.
六、Nginx的反向代理与负载均衡
代理(Proxy)也称网络代理。它是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有助于保障网络终端的隐私或安全,防止攻击。代理通常分为正向代理、反向代理及透明代理。
代理服务器的类型:
1、正向代理服务器(标准代理服务器)
目的:内网的服务器通过代理服务器,然后能够访问外网的服务器
原理:内网用户将请求发给代理服务器,代理服务器根据用户需求,向真正的web服务器发出请求,然后获取到网页内容之后,在本地缓存然后发给用户。
缺点:需要用户对浏览器进行设置
2、透明代理服务器
目的和原理与正向代理服务器一致,但一般布署在网关上,用户不需要再对浏览器进行设置
3、反向代理服务器(反向加速服务器)
目的:外网客户端通过代理服务器,能够访问内网服务器的资源
原理:外网客户端访问正常的域名或者IP,其实访问的是代理服务器,代理服务器帮助客户端请求页面,在代理服务器上缓存,然后再发送给客户端。
1.配置反向代理(七层)
proxy_pass是反向代理最核心的指令。代理时,后面如果有路径,将替换location部分;否则,将所有路径拼接到后面。
例:访问URL:http://192.168.10.11/static/index.html
配置1:
location /static/ {
proxy_pass http://192.168.10.12; # 后面不接任何路径,所有URL路径将被拼接到后面
}
将被代理到:http://192.168.10.12/static/index.html
配置2:
location /static/ {
proxy_pass http://192.168.10.12/;
}
将被代理到:http://192.168.10.12/index.html
配置3:
location /static/ {
proxy_pass http://192.168.10.12/abc;
}
将被代理到:http://192.168.10.12/abcindex.html
配置4:
location /static/ {
proxy_pass http://192.168.10.12/abc/;
}
将被代理到:http://192.168.10.12/abc/index.html
总结
- 当proxy_pass指令后面不带URI时,将把所有的原始URI拼接到代理后的服务器上;
- 当proxy_pass指令后面带URI时,将替换掉匹配的URI;
- 当使用正则匹配时,proxy_pass指令后面不应带URI。
2.真实IP
配置代理服务器将客户端 IP 传递给后端服务器
查看后端服务器的访问日志,我们发现日志中记录的客户端 IP 并非真实客户端的 IP,而是代理服务器的 IP 。
之所以会这样,原因是客户端向代理服务器发起请求后,再由代理服务器向后端服务器发起请求。
站在后端服务器的角度,代理服务器才是直接通信的“客户端”,所以记录的是代理服务器的 IP,显然,记录这个 IP 是没有意义的。
由于后端服务器和客户端并没有直接的通信,所以客户端的真实 IP 只能通过代理服务器传递给后端服务器。
我们可以在代理服务器上将客户端的 IP 封装到请求报文中发送给后端服务器。
示例:
在代理服务器上配置:
...
location / {
proxy_pass http://webs;
proxy_set_header X-Real-IP $remote_addr; # 将 $remote_addr 的值封装到请求报文头部的 X-Real-IP 字段中
}
...
做了以上配置后,再次用客户端访问代理服务器,在后端服务器上抓包,可以看到请求头部有了 X-Real-IP 字段,它的值正是客户端真实 IP
配置后端 web 服务器记录下客户端真实 IP
虽然经过以上第1步配置,代理能够将客户端真实 IP 传递过来了,但默认情况下后端服务器并不会在日志中记录请求头部 X-Real-IP 字段的值。
后端服务器为 apache 产品
对于 apache 产品,只需修改配置文件中的 LogFormat 即可:
LogFormat "%{X-Real-ip}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
后端为 nginx
如果后端为nginx,有两种方法可实现:
A、直接修改 log_format ,将 $remote_addr 改为 $http_x_real_ip 即可
B、安装 real_ip 模块(ngx_http_realip_module),然后修改配置文件:
...
server {
...
set_real_ip_from 代理服务器IP;
real_ip_header X-Real-IP;
...
}
...
这样配置后,$remote_addr 的值将会从请求报文中的 X-Real-IP 字段中获取。
2.1深入理解真实IP
几个关于IP的变量:
$remote_addr - 直接与服务器通信的客户端的IP地址
$http_x_real_ip - 从请求报文首部的X-Real-IP字段获取值
$http_x_forwarded_for - 从请求报文首部的X-Forwarded-For字段获取的值
$realip_remote_addr - 最后一个反向代理服务器的IP
$proxy_add_x_forwarded_for - 如果请求报文首部没有 X-Forwarded-For 字段,则此变量值为“$remote_addr”;
如果请求报文首部有 X-Forwarded-For 字段,则此变量值为 “X-Forwarded-For的值, $remote_addr”。
我们可以利用echo模块进行测试
实验一
一个代理服务器的情况
代理服务器: 192.168.10.41
web服务器: 192.168.10.42
客户端: 192.168.10.31
web服务器配置(nginx):
location = /test {
echo "remote_addr: $remote_addr";
echo "http_x_forwarded_for: $http_x_forwarded_for";
echo "http_x_real_ip: $http_x_real_ip";
}
1.代理服务器配置一:
location = /test {
proxy_pass http://192.168.10.42;
}
访问结果:
# curl 192.168.10.41/test
remote_addr: 192.168.10.41
http_x_forwarded_for:
http_x_real_ip:
2.代理服务器配置二:
location = /test {
proxy_pass http://192.168.10.42;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
访问结果:
# curl 192.168.10.41/test
remote_addr: 192.168.10.41
http_x_forwarded_for: 192.168.10.31
http_x_real_ip: 192.168.10.31
分析: 只有一个代理的情况下$http_x_forwarded_for也记录了客户端的真实IP
实验二
多个代理服务器的情况
web服务器配置同实验一
代理服务器一配置
location = /test {
proxy_pass http://192.168.10.41;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
代理服务器二配置
location = /test {
proxy_pass http://192.168.10.42;
proxy_set_header X-Real-IP $remote_addr; 正确 http_real_ip
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
访问结果
# curl 192.168.10.32/test
remote_addr: 192.168.10.41
http_x_forwarded_for: 192.168.10.31, 192.168.10.32
http_x_real_ip: 192.168.10.32 #从这里可以看出X-Real-IP被$remote_addr覆盖了,而$remote_addr是proxy2的上一个proxy的IP
real-ip-remote-addr: 192.168.10.41
分析:
如果有多个代理,第一个代理上的$http_x_forwarded_for记录了客户端的真实IP(X-Forwarded-For字段) 后面代理上记录的是X-Forwarded-For的值及上一个代理的IP,使用","隔开. $http_x_forwarded_for的值由客户端IP及所经过的所有代理服务器(最后一个除外)的值组成。
实验三
利用realip模块获取客户端真实IP
在实验二的基础上,修改web服务器的配置:
location = /test {
set_real_ip_from 192.168.10.0/24; #定义可发送真实IP的地址,可以是一个具体地址,也可以是CIDR地址
real_ip_header X-Forwarded-For; #指定真实IP从哪个请求头中获取
real_ip_recursive on; #是否递归解析,当其值为off时,将把real_ip_header指定请求头中的最后一个IP作为真实IP
echo "remote_addr: $remote_addr";
echo "http_x_forwarded_for: $http_x_forwarded_for";
echo "http_x_real_ip: $http_x_real_ip";
echo "real-ip-remote-addr: $realip_remote_addr";
}
访问结果
# curl 192.168.10.32/test
remote_addr: 192.168.10.31
http_x_forwarded_for: 192.168.10.31, 192.168.10.32
http_x_real_ip: 192.168.10.32
real-ip-remote-addr: 192.168.10.41
分析: 使用realip模块后,nginx可以通过$remote_addr获取到客户端真实IP
实验四
伪装请求头
实验拓扑图同实验二
代理服务器一配置
location = /test {
proxy_pass http://192.168.10.41;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
代理服务器二配置
location = /test {
proxy_pass http://192.168.10.42;
proxy_set_header X-Real-IP $http_x_real_ip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
web服务器配置:
location = /test {
set_real_ip_from 192.168.10.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
echo "remote_addr: $remote_addr";
echo "http_x_forwarded_for: $http_x_forwarded_for";
echo "http_x_real_ip: $http_x_real_ip";
echo "real-ip-remote-addr: $realip_remote_addr";
}
访问结果
# curl -H "X-Real-IP: 10.10.10.11" 192.168.10.32/test
remote_addr: 192.168.10.31
http_x_forwarded_for: 192.168.10.31, 192.168.10.32
http_x_real_ip: 192.168.10.31
real-ip-remote-addr: 192.168.10.41
# curl -H "X-Forwarded-For: 10.10.10.10" -H "X-Real-IP: 10.10.10.11" 192.168.10.32/test
remote_addr: 10.10.10.10
http_x_forwarded_for: 10.10.10.10, 192.168.10.31, 192.168.10.32
http_x_real_ip: 192.168.10.31
real-ip-remote-addr: 192.168.10.41
分析:
当我们改变了x-real-ip的配置后,发现即使一开始将X-Real-IP进行伪装,客户端的真实IP仍然能够正确的传递进来;
而如果一开始对X-Forwarded-For进行伪装,我们获取到的真实IP就不正确了. 为了避免这种情况,我们可以在第一个代理上修改设置,将proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;修改为proxy_set_header X-Forwarded-For $remote_addr;即可
多个代理服务器(nginx)的情况下,想要获取真实IP:
1).通过X-Forwarded-For字段获取:
第一个代理服务器: proxy_set_header X-Forwarded-For $remote_addr;
后面的代理服务器: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
这样最终真实服务器报头的X-Forwarded-For字段的值中的第一个IP一定是客户端的真实IP
通过real-ip模块将客户真实IP赋值给$remote_addr即可
2).通过X-Real-IP字段获取:
第一个代理服务器: proxy_set_header X-Real-IP $remote_addr;
后面的代理服务器: proxy_set_header X-Real-IP $http_x_real_ip;
最终服务器修改日志格式中的变量为 $http_x_real_ip即可
3.负载均衡
该功能由ngx_http_upstream_module + ngx_http_proxy_module模块提供
当我们把后端服务器换成后端服务器组后,此代理服务器便成了负载均衡调度器
服务器组的配置
使用 upstream 指令配置服务器组,一个 http 块中可配置多个服务器组。
示例:定义一个服务器组 backend , 由四台后端服务器组成:
...
upstream backend {
server 192.168.10.11 weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=3s;
server 192.168.10.12 down;
server 192.168.10.13 backup;
}
...
定义后端真实服务器使用 server 指令
address:后端服务器 ipaddress:port。如果后端服务器使用默认端口,则可省略 :port
参数:
weight=N 定义权值,默认为1
max_conns=N 限制最大连接数
backup 备用服务器,当所有服务器都宕机后才会起用
down 不起用的服务器
max_fails=N 认为后端服务器失效的连接失败次数
fail_timeout=Ns 心跳检测响应超时秒数,超过这个时间未响应则认为失败
示例:
...
server {
...
location / {
proxy_pass http://backend; # 将请求代理到服务器组中,即负载均衡
}
...
}
...
3.1调度算法
Nginx 调度算法有很多,除官方提供的多种调度算法外,也有很多第三方模块提供支持。
轮循
将所有的请求平均调度到后端所有的服务器上,不考虑服务器之间的差异及后端服务器的真实负载情况,是一种静态策略
示例:
upstream backend {
server 192.168.10.12;
server 192.168.10.13;
}
加权轮循
将后端服务器设置为不同的权值,权值高的服务器承担更多的负载,不考虑后端服务器的真实负载情况,是一种静态策略
示例:
upstream backend {
server 192.168.10.12 weight=1;
server 192.168.10.13 weight=2;
}
ip_hash
根据客户端ip的前三个字节的hash值进行调度,可实现把同一客户端的请求调度到同一台后端服务器上
示例:
upstream backend {
ip_hash;
server 192.168.10.12;
server 192.168.10.13;
}
least_conn
将请求传递到活动连接数最少的服务器,同时考虑服务器的权重。如果有多个这样的服务器,则使用加权循环平衡方法轮流尝试它们。
示例:
upstream backend {
least_conn;
server 192.168.10.12 weight=1;
server 192.168.10.13 weight=2;
}
hash key
根据自定义 key 的 hash 值进行调度。该 key 可以包含文本,变量,以及它们的组合。
请注意,从组中添加或删除服务器可能会导致将大多数密钥重新映射到不同的服务器。
random
将请求传递到随机选择的服务器,同时考虑服务器的权重。
可选two参数指示 nginx 随机选择两个服务器,然后使用指定的method. 默认方法是least_conn 将请求传递给活动连接数最少的服务器。
该指令出现在 1.15.1 版中。
url_hash
按访问URL的hash值进行调度,可实现同一个URL访问到同一台服务器。适用于后端服务器为缓存服务器。
url_hash需要安装第三模块ngx_http_upstream_hash_module
fair
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
fair需要安装第三方模块ngx_http_upstream_fair_module
总结
2轮循 - 效率高,不够均衡
2动态 - 均衡,效率低。least_conn:基于连接数 fair:基于响应时间
2hash - 特殊场景,ip_hash:session保持 url_hash:缓存调度,提高缓存命中率
七、缓存
缓存一般存的是静态资源
缓存可以提高客户端的访问速度,并能减轻服务器的压力
我们分别从客户端缓存及服务器端缓存两个场景去分析
1.客户端缓存 (设置在服务器上)
通过设置 expires 指令,响应头中将会返回Expires 和Cache-Control字段。
当浏览器发现响应头存在这样的缓存字段,当再次请求相同资源时,就会确认在客户端的资源是否过期
浏览器在不强制刷新的情况下可使用有效期内的缓存。
如以下配置:
curl -I 192.168.10.11
Cache-Control: max-age=86400
2.服务器端缓存(主要设置在反向代理服务器上)
通过 expires 指令设置的缓存,主要是针对客户端浏览器的。
如果我们能将静态资源的缓存设置在服务器端,当多个用户访问同一个资源时,缓存命中率及系统的性能将以指数的形式提升。
2.1配置一个简单nginx缓存服务器
[root@nginx ~]# vim /etc/nginx/nginx.conf
...
upstream backend{
server 192.168.10.21;
server 192.168.10.22;
}
proxy_cache_path /usr/local/nginx/cache levels=1:2:2 keys_zone=test:20m max_size=1g;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://backend;
proxy_set_header X-forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header X-real-ip $remote_addr;
proxy_cache test;
proxy_cache_valid 200 10m;
}
}
...
2.2指令说明
proxy_cache_path
语法:proxy_cache_path path [levels=number] keys_zone=zone_name:zone_size [inactive=time] [max_size=size];
默认值:None
使用字段:http
指定缓存的路径和一些其他参数,缓存的数据存储在文件中,并且使用代理url的哈希值作为关键字与文件名。
levels - 目录结构,可以使用任意的1位或2位数字作为目录结构,如 X, X:X或X:X:X ,最多三级。
keys_zone - 指定所有活动的key和元数据存储的共享内存池区域。
可以定义多个内存池,但必须是不重复的路径,例如:
proxy_cache_path /data/nginx/cache/one levels=1 keys_zone=one:10m;
proxy_cache_path /data/nginx/cache/two levels=2:2 keys_zone=two:100m;
proxy_cache_path /data/nginx/cache/three levels=1:1:2 keys_zone=three:1000m;
inactive - 指定缓存数据的失效时间,默认为10分钟。
max_size - 定义最大缓存大小,超出后则删除最少使用的数据
proxy_cache
语法:proxy_cache zone_name;
默认值:None
使用字段:http, server, location
设置一个缓存区域的名称,一个相同的区域可以在不同的地方使用。
proxy_cache_valid
语法:proxy_cache_valid reply_code [reply_code …] time;
默认值:None
使用字段:http, server, location
为不同的应答设置不同的缓存时间,例如:
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
为应答代码为200和302的设置缓存时间为10分钟,404代码缓存1分钟。
如果只定义时间:
proxy_cache_valid 5m;
那么只对代码为200, 301和302的应答进行缓存。
同样可以使用any参数任何应答。
proxy_cache_valid 200 302 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid any 1m;
八、防盗链
1.什么是防盗链
客户端向服务器请求资源时,为了减少网络带宽,提高响应时间,服务器一般不会一次将所有资源完整的回传给客户端。比如在请求一个网页时,首先会回传该网页的文本内容,当客户端浏览器在解析文本的过程中发现有图片存在的时候,会再次向服务器端发起对该图片资源的请求,服务器将存储的图片资源再发送给客户端。在这个过程中,如果该服务器上只包含网页的文本内容,而没有存储相关的图片资源,而是将图片资源链接到了其他站点的服务器上去了,这就形成了盗链问题。
在这种情况下,客户端请求的图片资源资源实际上是来自其他的服务器。很明显,盗链行为是一种对其他服务器不公平的行为。
2.如何防止被盗链
要实现防盗链,需要了解HTTP协议中的请求头的Referer头域。
通过该头域的值,可以检测到访问目标资源的源地址。这样,如果我们检测到Referer头域中的值并不是自己站点内的URL时就采取阻止措施,实现防盗链。但是要提醒大家的是, 任何HTTP协议头都会有可能被篡改,因此这种方法并不能完全的阻止盗链行为。
3.nginx如何配置防盗链
Nginx的ngx_http_referer_module模块中提供了一个指令valid_referers ,用来获取Referer头域中的值并根据该值的情况给Nginx全局变量 $invalid_referer赋值。 如果Referer头域中没有符合valid_referers指令配置的值,$invalid_referer 变量将会被赋值为1。
语法如下:
valid_referers none | blocked | server_names | string ...;
none: HTTP头中不存在Referer 头域
blocked: 存在Referer头域,但里面的值有可能由于防火墙或者代理服务器的原因被删除或者伪装。
这种情况下,该头域的值以"http://" 或者 "https://" 开头
server_names: 设置一个或者多个域名, 检测Referer 头域的值是不是这些域名中的某个。支持使用通配符*
一个防盗链实例
九、动静分离
动: 动态脚本请求;
静: 静态页面请求;
分离: 动态的请求和静态的请求在一台服务器分离出来, 由nginx-web-server 处理静态页面请求, 由 PHP 处理动态请求.
使用fastcgi:
CGI: 通用网关接口,web-server和动态的脚本语言之间进行通信的接口.
fastCGI: 高速的运行在web-server上的和动态的脚本语言之间进行通信的接口.
运行原理:
fastcgi在linux下是socket,为了调用CGI程序,还需要一个应用程序-wrapper,这个wrapper绑定在socket上.
(1)nginx将动态的HTTP请求交给 socket,通过 fastcgi接口,由wrapper接受请求,然后派生出一个线程,调用请求数据;
(2)wrapper将得到的数据通过 fastcgi接口通过 socket交给 nginx;
(3)nginx把数据交给客户端.
十、平滑升级
主进程支持以下信号:kill -l 查看
示例平滑升级1.8.1到1.10.2:
可以在不中断服务的情况下,新的请求也不会丢失,使用新的 nginx 可执行程序替换旧的(当升级新版本或添加/删除服务器模块时).
绝对路径启动有效
1.查看老版本的编译选项
[root@clone1 ~]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.8.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --user=nginx --group=nginx --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module
2.编译新版
[root@clone1 nginx]# cd nginx-1.10.2
[root@clone1 nginx-1.10.2]# ./configure --user=nginx --group=nginx --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module
[root@clone1 nginx-1.10.2]# make
3.用新编译的命令替换原来的命令
[root@clone1 nginx-1.10.2]# mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak
[root@clone1 nginx-1.10.2]# cp objs/nginx /usr/local/nginx/sbin/
4.启用新的,关闭旧的
[root@clone1 ~]# kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`
主进程将重命名它的 .pid 文件为 .oldbin,然后执行新的可执行程序,依次启动新的主进程和新的工作进程.
[root@clone1 ~]# kill -WINCH `cat /usr/local/nginx/logs/nginx.pid.oldbin`
给旧的主进程发WINCH信号,把旧的工作进程从容关闭, 有新的请求发到新进程
这时,因为旧的服务器还尚未关闭它监听的套接字,所以,通过下面的几步,你仍可以恢复旧的服务器:
1.发送 HUP 信号给旧的主进程 - 它将在不重载配置文件的情况下启动它的工作进程
2.发送 QUIT 信号给新的主进程,要求其从容关闭其工作进程
3.发送 TERM 信号给新的主进程,迫使其退出
4.如果因为某些原因新的工作进程不能退出,向其发送 KILL 信号
新的主进程退出后,旧的主进程会由移除 .oldbin 前缀,恢复为它的 .pid 文件,这样,一切就都恢复到升级之前了.
[root@clone1 ~]# kill -QUIT `cat /usr/local/nginx/logs/nginx.pid.oldbin`
如果尝试升级成功,而你也希望保留新的服务器时,发送 QUIT 信号给旧的主进程使其退出而只留下新的服务器运行.