文章目录
简介
- 上个系列直接深入 nginx,实战也少,不便于理解
- 这里从基础到进阶,多配几个例子,再去看之前的内容,理解会更加深入
安装
- 先从原始的 nginx 开源版本学起,也就是只提供了反向代理和负载均衡功能的 NGINX
- 具体安装过程跳过
- 使用 docker 安装要将配置文件映射出来,容器里面不好改
- 新增配置,让 systemd 管理 nginx;或者说,配置成系统服务
vi /usr/lib/systemd/system/nginx.service [Unit] Description=nginx - web server After=network.target remote-fs.target nss-lookup.target [Service] Type=forking # 这里注意,/usr/local/nginx/ 是安装目录才行,不是的话要改 PIDFile=/usr/local/nginx/logs/nginx.pid ExecStartPre=/usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf ExecStart=/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s stop ExecQuit=/usr/local/nginx/sbin/nginx -s quit PrivateTmp=true [Install] WantedBy=multi-user.target
- 重新加载系统服务:
systemctl daemon-reload
- 把之前启动的 nginx 停掉:
./nginx -s stop
- 启动:
systemctl start nginx.service
- 开机启动:
systemctl enable nginx.service
- 把之前启动的 nginx 停掉:
目录结构
- 推荐文章
工作流程
- NGINX 的最基本工作流程
- 启动 nginx 就可以看到默认有 master+worker 进程,真正干活的是 worker
- 当重新加载配置时,master 会优雅关闭现有 worker 并重建新的 worker
- 想深入了解 NGINX 进程管理可以看这篇文章
配置文件
- 配置文件及语法可以看这篇文章,但是可能有点晦涩,这里结合案例讲解
- 最小配置,就是把注释部分都删掉的 nginx.conf
worker_processes 1; # 应当和内核数匹配 events { worker_connections 1024; # 最大请求连接数 } http { include mime.types; # 根据后缀,判断文件类型,带在HTTP报文头部,便于客户端解析 default_type application/octet-stream; # 如果上面都不匹配,就用流传输 sendfile on; # 使用 Linux 的高效网络传输,数据0拷贝 keepalive_timeout 65; # 保持长连接 # 虚拟主机 server { listen 80; # 监听服务器80端口 server_name localhost; # 匹配域名或主机名(hosts可配置) location / { root html; # 指定资源目录(相对) index index.html index.htm; # 指定目录下文件 } error_page 500 502 503 504 /50x.html; # 请求 50x 这个文件 location = /50x.html { # 怎么找到这个文件呢?这里使用精准匹配 root html; } } }
虚拟主机
- NGINX 可以配置多个虚拟主机监听多个端口,充分利用服务器资源
- 我们把 http 里面的虚拟 server 复制一份改一下端口号测试,重启 NGINX
- 现在访问两个端口都能返回 index 页面
server_name
- 这部分很重要,要看懂
- 注:这个
server_name
是用来匹配请求的域名/主机名的- 从浏览器访问,如果使用虚拟机 IP
192.168.109.129
访问,不需要域名解析,也不经过 hosts 文件解析,直接到 nginx,但是这个 IP 不和 localhost 匹配,也不和任何 server_name 匹配,那就走第一个虚拟 server(按顺序的) - 在 80 端口定义两个虚拟 server 测试一下
- 虚拟机访问
http://console
返回 index2 页面(完整匹配);用 IP 访问,返回 index 页面(没匹配上) - 注:这里
console
是在 hosts 文件新增的主机名,还是指向我们本地的 IP - 也就是说,虽然请求在 host 解析 IP 然后被 nginx 监听(捕获)了,也就是说 nginx 是不能直接看到 hostname/domain 的,变成 IP(网卡地址) 后才到 nginx的,但 hostname还是能在 nginx 匹配的(请求报文中还带有hostname,可以抓包看一下)
- 同理,域名(domain )被 DNS 解析了但也能在 nginx 匹配
- 最终解析成 IP 是因为定位网卡需要 IP,然后根据网络协议(tcp/udp)指定的端口号路由到具体的某个应用程序,nginx 就位于这之间,具体位置是哪里呢?请看后续分解
- 从浏览器访问,如果使用虚拟机 IP
- server_name 后可以跟多个 domain/hostname,空格分隔即可
- 既然是匹配,那就有通配符、正则表达式可以用;也叫泛域名解析
- 我们在 host 文件新增几个域名测试一下
192.168.109.129 www.royspace.com www.royspace.cn xxx.royspace.xyz
- nignx 配置如下
- 这个规则的意思是,只要以
www.royspace
开头即可,后面带什么都行 - 类似的,如果将
*
写在前面,比如:*.royspace.com
就是以royspace.com
结尾即可,前面啥都行 - 这个
*
就是通配符,如果想用正则,需要在匹配字符串前面加~
- 例如:
~^[0-9]+\.royspace\.com$
表示以数字开头
- 我们在 host 文件新增几个域名测试一下
- 泛域名解析用途广泛
- 多用户二级域名
- 短网址
- httpdns
反向代理
- 什么是反向代理
- 简而言之就是,客户端的请求由 NGINX 决定转发到哪
- 简而言之就是,客户端的请求由 NGINX 决定转发到哪
- 后面会介绍 Linux 内置的 LVS 负载均衡器,和 NGINX 的隧道式代理不同
- 大文件传输会是 NGINX 的性能瓶颈,可以使用 DR 等方案解决
- 使用 NGINX 的架构
- 传统架构
- 中小型互联网项目架构(pass)
- 传统架构
- 配置反向代理:
proxy_pass
- 转发给百度的域名,就是请求百度的首页;参数还是会带上的
- 转发给百度的域名,就是请求百度的首页;参数还是会带上的
负载均衡
- 给多个 server(上游服务器)建立变量,配置负载均衡
- 注:配置证书之前,不支持代理 HTTPS
- 负载均衡策略
- 上面的配置默认采用轮询策略
- 权重策略
upstream httpd { server 127.0.0.1:8050 weight=10 down; server 127.0.0.1:8060 weight=1; server 127.0.0.1:8070 weight=1 backup; }
- down、backup 策略(不常用)
- ip_hash(解决session问题)、fair、leastconn 策略(不常用)
动静分离
- 静态资源放在 nginx,不向后端请求,提高效率
- 小型的展示类型的网站比较常用
alias
- nginx 作为静态资源服务器时会使用 root 或 alias,有啥区别呢?
- alias指定的目录是准确的,即 location 匹配后,文件直接是在alias目录下查找的,不会再去指定的目录的子目录找;访问
/css/a.css
,那么这个文件就在 /usr/local/nginx/static/css 下location /css { alias /usr/local/nginx/static/css; index index.html index.htm; }
- root指定的目录是 location 匹配访问的 path 目录的上一级目录,这个path目录一定要是真实存在root指定目录下的;访问
/css/b.css
,/usr/local/nginx/static 下面必须有个叫 css 的文件夹存放着 b.csslocation ~* /(css|img|js) { root /usr/local/nginx/static; index index.html index.htm; }
- 使用alias标签的目录块中不能使用 rewrite 的break(原因未知)
location
- 虚拟 server 内部依赖 location 关键字匹配 URI,进而反向代理、负载均衡
- location 的匹配规则
/
通用匹配,任何请求都会匹配到=
精准匹配,请求地址必须和这里的规则完全一样才能匹配到~
正则匹配,区分大小写~*
正则匹配,不区分大小写^~
非正则匹配
- 匹配规则的优先级
- 多个正则,直接按定义的先后顺序匹配,成功后就不会继续往后面匹配
- nginx 中很多地方都用了按定义顺序优先匹配的规则,例如:server_name 匹配
- 通用匹配,一直往下,直到找到匹配度最高的(最大前缀匹配)
- 非正则与正则同时存在,如果正则匹配成功,则不会再执行普通匹配
- 所有规则同时存在,
=
>^~
> 正则 > 通用 - 口诀:精准禁则先,正则最长后
- 多个正则,直接按定义的先后顺序匹配,成功后就不会继续往后面匹配
Rewrite
- 隐藏后端服务器的真实地址,前端只需访问包含关键信息的静态页面(http://ip/3.html),就能跳转到真实的后端地址(http://ip/index.jsp?pageNum=3)
- 这里使用了正则匹配,前面匹配到的参数可以在后面使用
break
表示本条规则匹配完成即终止,不再匹配后面的任何规则last
本条规则匹配完成后,继续向下匹配新的 location URI 规则(先不转发)- 这里遇到个问题
- 直接访问:
http://192.168.109.129:8080/index.html?pageNum=666
可以显示页数 - 访问:
http://192.168.109.129:8080/666.html
就不显示 - 分析:proxy_pass 会带上 location
/
匹配到的完整 URI,包括 query 参数;如果直接带上666.html
128 是会报错的,说明rewrite
还是起作用的了;直接传 pageNum 生效是因为 rewrite 没匹配上,直接代理过去了 - 所以应该是 rewrite 匹配上并重写了,但重写失败,传递了空字符串或者
$1
出现问题(只写了 index.html) - 将 rewrite 中 pageNum 写死也未显示,将 index.html 改为 index.jsp 出现 tomcat 界面
- 说明丢失了 query_string 参数,rewrite 只是重写 URI 的,不负责参数,并不背锅
- why? 需要了解更多底层知识
upstream us { server 192.169.109.130:80 weight=8 down; server 192.168.109.128:8080 weight=1 backup; } server { listen 80; server_name localhost; location / { #if ($request_uri ~ ^/([0-9]+).html$){ #set $a $1; #} rewrite ^/([0-9]+).html$ /index.html?pageNum=$1 break; #proxy_set_header Host $host; #proxy_set_header X-Real-IP $remote_addr; #proxy_pass http://192.168.109.128:8080/; proxy_pass http://us?$args; #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location ~*/(css|images|js|fonts) { root /usr/local/nginx/static; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
- why? 需要了解更多底层知识
- 注:location 匹配 URI,GET 参数存在内置变量
$query_string
里,但这个变量不能手动修改
- 直接访问:
防火墙
- 应用服务器的防火墙配置
- 相关命令(给 128 应用服务器设置)
systemctl start firewalld # 指定端口和 ip 访问 firewall-cmd --permanent --add-rich-rule="rule family="ipv4" source address="192.168.109.129" port protocol="tcp" port="8080" accept" # 重载规则 firewall-cmd --reload # 重启 systemctl restart firewalld # 移除规则 firewall-cmd --permanent --remove-rich-rule="rule family="ipv4" source address="192.168.109.129" port="8080" protocol="tcp" accept"
- OK,现在只有 129 才能访问
- 此后,我们称 nginx 这台服务器为网关服务器
- 目前的配置(有上面说的那个rewrite问题,为啥别人的可以?排除浏览器原因,是否在nginx-1.21就可以)
upstream us { server 192.169.109.130:80 weight=8 down; server 192.168.109.128:8080 weight=1 backup; } server { listen 8080; server_name localhost; location / { # proxy之前定义 rewrite ^/([0-9]+).html$ /index.html?pageNum=$1 break; proxy_pass http://us; } location ~*/(css|images|js|fonts) { root /usr/local/nginx/static; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
防盗链
- 在浏览器中,如果我们请求的资源返回后,再去请求其他的资源,比如上面说的 nginx 作为网关服务器,返回了 128 的资源后,这个页面还需要加载一些静态资源(css/js/imgs),就会自动的再次向作为静态资源服务器的 nginx 发起请求;此时,浏览器就会根据 HTTP 协议规定的,给我们在 Header 中携带
Referer
,值就是我们第一次请求时的域名,表明这次请求的“推荐人”还是这个域(129) - 想要通过 referer 限制资源的访问,要在 nginx 中需要验证防盗链的 location 中配置
valid_referers 192.168.109.129; # 这里暂时设置为 ip if ($invalid_referer) { return 403; }
- 此时访问 129 没问题,直接访问应用服务器(130,得是没启动防火墙的才行)不能加载静态资源
- 必须有 referer,还不能错
- 直接访问 130 的资源地址(不访问到应用服务器,也就没有 referer)也不行,控制台可以看到 403 forbidden;但如果我们需要直接访问(没有 referer的时候)能拿到静态资源,可以配置:
- 也就是说,可以没有,但有的话不能错
valid_referers none 192.168.109.129; # 这里暂时设置为 ip if ($invalid_referer) { return 403; # 可以通过 rewrite 返回图片 }
- 也就是说,可以没有,但有的话不能错
curl
- 使用 curl 发起测试请求可以有效避免缓存问题
- 常用命令
# 只返回头信息 curl -I http://192.168.44.101/img/logo.png # 上面设置 none,即可以没有referer # 设置 referer curl -e "http://baidu.com" -I http://192.168.109.129/img/logo.png # 这个referer不对,应该返回forbidden
error page
- 可以自定义返回的错误页面
server { listen 82; server_name localhost; location / { return 401; } error_page 401 /401.html; location = /401.html { root html; } }
高可用
- 应用服务器可以通过搭集群的方式,均衡负载,并让系统能够一直提供服务(应对单点故障)
- 网关服务器(NGINX)作为流量转发中心,自然也需要考虑高可用
- 常见的高可用方案如图所示
- 对外(client),我们只有一个 IP(200),因为一块网卡是可以设置多个 IP 的,这个虚拟 IP 在正常情况下也就设置在主机(100),也叫 Master
- 当正在使用的网关服务器宕机后,就会重新竞选出 Master,这个虚拟 IP 就会设置在新的主机上
- NGINX 通过配置 Keepalived 实现 HA
- 安装:
yum -y install keepalived
- 也通过下载源码编译安装
- 最小配置
# vim /etc/keepalived/keepalived.conf global_defs { router_id roy666 # 129 } vrrp_instance myHA { state MASTER # 这台机器先作为 master interface ens33 # 和本机网卡名要一致 virtual_router_id 51 priority 100 # 竞选时的权重 advert_int 1 authentication { # 每个机器都要装keepalived,可能有很多机器用于不同类型服务器的HA,通过这个分组,同一组保持一致 auth_type PASS auth_pass 1111 } virtual_ipaddress { # 相当于图上 44.200 192.168.109.133 # 注意不要和其他服务器冲突 } }
# 备份机 global_defs { router_id roy777 # 192.168.109.128 那台机器 } vrrp_instance myHA { state BACKUP # 备份机 interface ens33 virtual_router_id 51 # 要和master一致,确保在同一组 priority 50 advert_int 1 authentication { # 要和master一致 auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.109.133 } }
- 启动 Keepalived,在 Master 上能看到
- 启动 BACKUP 的 Keepalived
- 测试
- 在 cmd ping 这个虚拟 IP,然后关掉 Master(init 0)
- 在短暂的一次请求超时后,在另一台机器雄起
- 在 cmd ping 这个虚拟 IP,然后关掉 Master(init 0)
- 可以了解一下 ARP 协议
升级
- Keepalived 默认监听主机的 keepalived 进程是否存在来判断是否出问题
- 有时 NGINX 服务器报错并不能让给 k 进程 down 掉,我们可以写一个脚本放在每个机器上,监听 nginx 的 error log,一旦出错,就 down 掉 k 进程,选择其他主机
- 当然,nginx 在 Master 上报错并不能保证在 Backup 上就没问题,还是要赶紧排错
加密算法
- 互联网信息的加密传输
- 对称加密
- 非对称加密:更安全,需要公私钥;但也不是绝对的安全,伪装成客户端和服务端就有可能篡改所有信息
- 那如何保证互联网传输的安全性?核心逻辑是:有一个不在互联网中传输的可信任的东西作为证人,这个东西就是 CA 机构
- 基本流程
- 服务器向 CA 机构申请证书(花钱买)
- 客户请求服务器时,给客户端颁发证书,这个证书(用CA的公钥才能解开)里带着服务器的公钥
- 客户端后续的网络请求都用这个公钥加密,只有服务端的私钥才能解密
- 为什么就安全了呢?
- 未使用证书前,给客户端发公钥的可能是坏人,坏人拿着一套假的公私钥玷污数据,然后假冒客户端,将玷污后的数据用真公钥加密再给服务端,服务端并不能发现异常,坏人达到目的
- 使用证书后,坏人也能拿到证书并解开获取服务器的公钥,但是想要再伪装成 CA 机构加密出假证书是不可能的,因为只要客户端保证使用的是正版操作系统,那只有 CA 证书是真的,用内置 CA 公钥解密才不会出现问题,进而拿到真实的服务器的公钥,保证了这一步,就打破了坏人的伪造链
- 也就是说,这个不在互联网中传输的证人就是我们的正版操作系统/浏览器(里面内置的CA公钥)和 CA(私钥)
- 因此,要防止坏人伪造成 CA,给我们颁发恶意证书,哄骗我们使用假公钥,那就一定要使用正版OS 和浏览器,对证书要敏感,不能轻易信任
- 颁发给客户端的证书,其实就是 CA 用私钥加密公钥后的密文,这个公钥是 CA 根据我们提交的域名生成的,当然,也有对应的私钥(服务器加密信息使用),服务器安装 CA 签发的 SSL 证书就可以拿到(也可直接下载);但是 CA 的私钥是用来传递证书或者说传递 CS 之间通信的公钥的,只用一次,也不会给服务器,只要验证了是真实证书,client 获取了真实服务器公钥就等于获取了互信;后续用客户端公钥和服务器下载的私钥即可加密通信
- 别搞混:CA 私钥/公钥,服务器公钥/私钥,都是由 CA 生成
- 也就是说,给服务器的证书只有一份,连接服务器的客户端拿的都是相同的公钥
- 如何保证服务端传给客户端的数据不被坏人用真实公钥解密呢?
- 首先,服务器返回的信息一定要脱敏,被拦截了影响不大
- 坏人解密后不能再加密了,传递到客户端也不会被相信,也就是无法篡改数据
自签名
- 了解即可,不常用,因为存在安全隐患
- 自签名,就是在服务器生成一对公私钥,公钥包装成证书,私钥就相当于服务器私钥
- 安装证书拿到公钥开始加密传输数据,服务器用私钥加解密
- 因为不需要第三方机构验证证书真实性(本来就是伪造的),也就没了上述内置的解开证书的 CA 公钥,没了 CA 私钥加密服务器公钥这一层
申请证书
- 申请正规机构的证书
- 先买一个域名
- 为了更真实,可以再买个 VPS(云服务器)
- 买个一个月最低配置即可
- 配置服务器(安装软件)
- lnmp 是一套用的比较多的搭配(Linux+nginx+MySQL+PHP)
- 推荐通过这里安装,省事
- 后续的配置以及域名解析、泛域名解析暂且不表
- 给域名申请证书
- 还是在阿里云买
- 很显然,大机构的证书买不起,那就选免费的那个,时长一年
- 这里是系统自动验证我这个服务器的安全性,也可以通过手动上传个东西到指定位置做为验证
- 下载证书,可以选择服务器类型;我们就选 NGINX
- 会得到 pem 证书文件和 key 私钥,直接在 nginx 配置
server { listen 443 ssl; server_name localhost; ssl_certificate /usr/local/nginx/conf/xxx.crt # 颁发给客户端,浏览器内置的ca公钥会验证并解析出服务器公钥加密通讯 ssl_certificate_key /usr/local/nginx/conf/xxx.key # 服务器私钥(CA生成) }
- 就可以用 HTTPS 访问啦
- 还是在阿里云买
- 目前,nginx 作为网关服务器配置了 HTTPS 传输,接收外网客户端请求,应用服务器可以和 nginx 在同一个内网使用 HTTP 传输