Nginx 作为一款高性能的代理软件,在工作中常被用做负载均衡器、正向反向代理。最近在看了一个对于 Nginx 的视频,老师讲得很不错。笔者也记录了一下整套课程的一些重点和本人的了解。
笔者学习习惯是先实战后实践,所以本文次要偏实战,心愿读者能够通过本文疾速搭建 Nginx,并尝试应用它的个性。对于 Nginx 的运行原理、外围模块、源码剖析等内容可能要等下次了(其实是笔者学艺不精,临时看不太懂),大家感兴趣能够先看看其余优质文章。
思维导图
那咱们开始吧!
筹备工作
Nginx 是什么这里就不赘述了,置信大家都用过,Nginx 目前的发行版本如下:
- Nginx 开源版
- Nginx Plus:F5 基于 Nginx 开源版开发的商业版本
- Openresty:国人开发,整合了 lua 模块
- Tengine:淘宝基于开源版革新,通过双十一验证
本文次要讲述开源版的 Nginx 和 Openresty 开源版
疾速装置
大家到 nginx.com 下载一下源码包,解压编译装置
# 装置依赖
$ yum install -y pcre pcre-devel
$ yum install -y zlib zlib-devel
# 解压编译
$ tar -zxf nginx-1.21.6.tar.gz
$ ./configure --prefix=/usr/local/nginx
$ make && make install
编写一下 service 文件,不便操作
$ cat <<EOF >> /usr/lib/systemd/system/nginx.service
[Unit]
Description=nginx - web server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
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
EOF
$ systemctl daemon-reload
$ systemctl start nginx.service
$ ystemctl enable nginx.service
# 当前重启 nginx 只须要
$ systemctl restart nginx
# 从新加载配置
$ systemctl reload nginx
配置文件在 conf/nginx.conf
,简化一下
worker_processes 1;
events {worker_connections 1024;}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {root html;}
}
}
有没有发现我的配置文件比拟工整,以前是在 SFTP 上记事本编辑,缩进须要手动调整特地麻烦,前面间接用 Vscode 的 nginx-formatter 格式化(前面会跟大家说怎么装置)。
nginx-formatter 通过先用特殊符号替换双引号中的内容防止误操作,通过正则表达式的形式去除空白字符,我的项目链接:
- nginx-config-formatter:https://github.com/slomkowski…
- nginxbeautifier:https://github.com/vasilevich…
对于最小配置的一些阐明:
- worker_processes:Nginx 基于多过程 React 模型,启动时会启动一个 Master 和 N 个 Worker,这里配置 Worker 个数,个别设置为 CPU 逻辑外围数 + 1
- worker_connections:Worker 是真正解决申请的过程,这个配置次要形容一个 Worker 能够解决的连贯下限
- include:蕴含其余 nginx 配置文件
- mine.type:配置了资源后缀名和响应体的 Content-Type 的映射关系
- sendfile:是否开启零拷贝,只有在磁盘的内容会触发 sendfile
- server:定义一个服务
- listen:监听端口
- server_name:能够配置主机名或者域名
- location:匹配的 URL 后缀,root 指的是文件系统门路,index 是欢送页
- root:定义根目录
- index:欢送页
保留后,应用 curl 命令简略测试一下是否胜利
$ curl localhost
curl 是一个简略的 HTTP 申请工具,罕用的几个参数如下:
- -H “Conten-Type: application/josn”:增加申请头
- -I:显示响应头
- -e:设置 Reference
- -X 设置申请形式
- –proxy:设置代理
- -sSL:跟踪重定向
具体文档:https://itbilu.com/linux/man/…
测试环境搭建
为了更好的测试,这里就间接应用 Flask(容器运行)模仿上游服务,宿主机作为 Nginx,网络拓扑如下:
这张图是应用虚构画板画的:https://board.oktangle.com/
$ mkdir ~/flask-demo
$ cd ~/flask-demo
创立app.py
from flask import Flask
from flask import render_template
from flask import request
import socket
app = Flask(__name__)
@app.route("/")
def index():
return render_template('index.html', data={'hostname': socket.gethostname(),
'num': request.args.get('num', 'None')
})
创立 templates 目录,新建 index.html 文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nginx 实战 </title>
</head>
<body>
<h3> 以后服务器 hostname: {{data.hostname}}</h3>
<h3> 从 URL 中获取 参数 num: {{data.num}}</h3>
<p></p>
<img src="/static/img/demo.png" alt="防盗链测试蹄片">
</body>
</html>
原本想输入 IP 的,然而没找到 flask 从 IP 报文获取源地址的 API,拿主机名代替一下
回到主目录,创立 Dockerfile
FROM python:3.8-slim-buster
WORKDIR /python-docker
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .
CMD ["python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=80"]
创立依赖文件 requirements.txt
Flask==2.0.3
最终目录构造如下:
│ app.py
│ Dockerfile
│ requirements.txt
│
├─static
│ └─img
│ demo.png
│
├─templates
└─ index.html
在 Linux 中应用上面命令进行测试环境搭建:
$ docker build -t flask-demo:0.1 .
$ docker network create --driver bridge --subnet 172.20.0.0/24 --gateway 172.20.0.1 flask-cluster
# 启动三个镜像
$ for i in $(seq 1 3); do docker run --name flask-demo-$i --hostname flask-demo-$i --restart=always --network flask-cluster --ip 172.20.0.1$i -d flask-demo:0.1; done;
# 关上 nginx 端口
$ firewall-cmd --add-port=80/tcp --permanent
$ firewall-cmd --reload
# 上面命令用于删除容器
for i in `seq 1 3`; do docker rm -f flask-demo-$i; done
VsCode 配置
应用 Vsocde 中的插件会有更好的学习体验,装置上面插件,Remote SSH 连贯虚拟机或近程主机,当前批改配置就十分不便了。
- nginxbeautifier:Nginx 配置文件格式化工具(感兴趣能够浏览一下代码,300 多行)
- vscode-nginx-conf:VSCode 配置文件代码补全、连贯到官网文档插件
- luaHelper:Lua 脚本语法补全、高亮插件
- Remote SSH:连贯近程主机插件
Nginx 外围性能
Nginx 的外围性能次要围绕着 http – server 模块进行形容。在 server 模块下,server_name 能够配置主机名和域名,Nginx 在解析 HTTP 报文中的 header —— Host
来判断进入哪一个 server 块。这里能够应用通配符,优先级自上而下。基于此能够实现
-
多租户二级域名:
比方
xxx.domain.com
,ServerName 配置*.domain.com
,后端服务器解析 Http 报文中的 Host,依据 xxx 展现对应用户信息,当然这样子不太平安。 - 短网站:这个须要和前面介绍 URLRewrite 配合应用
咱们能够自定义一个 Server 实现 HTTP DNS,但这须要咱们学习到前面 lua 的时候才好实现。
HTTPDNS:因为 DNS 净化、本地 DNS 服务不够智能、DNS 依据本人而非客户返回最近服务器谬误等问题,咱们能够本人基于 HTTP 协定搭建 DNS 服务器,但这须要客户端本人实现发动解析的逻辑,然而浏览器不反对 HttpDNS,解决形式是本地启动一个 DNS 服务器联合 FakeIP 解决,这个能够参考我之前写的 Clash 代理工具解析。
server_name
配置形式:
-
残缺配置
server_name domain1.com domain2.com;
-
正则配置
server_name domain1.~^[0-9]+\.domain.com;
-
通配符配置
server_name domain1.*
配置完 server_name
后,接着就要编写匹配规定 location
了,目前 server 曾经匹配 URL 上的 host 局部,剩下的 uri 则由 location 进行匹配。你能够抉择让 Nginx 帮忙你将申请转发进来(正反向代理),也能够让 Nginx 返回本地文件(资源服务)。
正反向代理
为了保障服务器的安全性,裸露对立网络入口,咱们会应用 Nginx 作为服务的入口,所有流量都会通过这个入口进入上游服务。
这种形式称为反向代理,客户端不晓得服务端的具体地址,只关注与代理服务器的通信。
正向代理则更罕用于跨网段通信,比方机房 A 中的主机不能拜访机房 B 的主机,须要通过可能同时于机房 A、机房 B 中主机通信的代理服务器作为跳板进行跨机房拜访。
其实正向代理和反向代理没有区别,只是通常状况下反向代理是对申请发起方无感,正向代理对发起者有感(甚至须要发起者本人去代理中配置)
这里的 Nginx 也被称为网关,因为网关数量比拟少,容易呈现瓶颈,比方下载大文件的场景,Response 都要通过网关。LVS 的 DR 模式凋谢上游服务器的 OUTPUT 防火墙,使得入栈通过网关,出栈由上游服务间接返回客户端。
反向代理配置
server {
listen 80;
server_name localhost;
location / {proxy_pass http://flask;}
error_page 500 502 503 504 /50x.html;
location = /50x.html {root html;}
}
upstream flask {
server 172.20.0.11:80;
server 172.20.0.12:80;
server 172.20.0.13:80;
}
proxy_pass:会将申请内容转发到 upstream 上有服务上
正向代理配置
正向代理其实就是 HTTP 报文写着须要拜访的指标地址(header 中的 hsot 和 uri),但将报文发给了代理服务器。所以如果你心愿 Nginx 充当代理,只须要进行上面的配置
location / {
proxy_pass $scheme://$host$request_uri;
resolver 8.8.8.8;
}
这是基于七层代理,四层须要引入 stream 模块,七层正向代理只反对 HTTP 协定,如果须要反对 HTTPS 的话须要应用上面模块
- ngx_http_proxy_connect_module:https://github.com/chobits/ng…
负载平衡
在负载一直减少的状况下,咱们很有可能对上游服务进行扩容,即减少更多的节点。这时候就须要 Nginx 负责将申请尽量平均的摊派到上游服务上。Nginx 提供了好几种算法:
Round Robin 轮询
雨露均沾,一人一下。但很多时候物理机或者虚拟机的配置不一样,咱们能够为资源较多的主机配置更高的权重。
upstream flask {
server 172.20.0.11:80 weight=10;
server 172.20.0.12:80 weight=2;
server 172.20.0.13:80 weight=1;
}
这里会大略以 10 : 2 : 1 调配流量,解决 weight 还有几个能够设置的参数:
- down:不参加调度
- backup:除了 backup 以外的服务器都无奈进步服务时被调度
这两个个别不会应用,比方 backup,咱们试图心愿服务呈现故障后备用机器能够顶替,但如果是因为代码逻辑不正确导致的宕机,实践上备用机器上的代码也是有问题的,所以切换后很可鞥也会宕机。
ip_hash
轮询算法有个问题就是每次申请必须是无状态的,即不能应用单个服务器的 session 暂存数据。ip_hash 算法依据客户端 IP 进行散列,所以每次申请都会打到同一个服务器,但该算法的问题在于:
- 因为手机挪动过程中 IP 地址会产生扭转,如果上游服务应用了 session 会导致生效
- 可能呈现大部分 IP 地址散列到同一台上游服务
- 对于内网用户数量较多,比方考试零碎、ERP 零碎,进口 IP 地址只有几个,容易导致流量歪斜
least_conn
起码连贯,行将流量分给流量最小的主机。这个也不罕用,之所以会有流量歪斜很可能是服务权重具备差别。
url_hash
依据 URL 进行散列,适宜资源定位,比方多个文件散落在不同服务中,能够通过 URL 中的文件名进行 hash 找到指标节点。毛病在于:基于 url_hash 上游服务只能部署在一台主机上
fair
基于响应工夫的调度策略,须要依赖第三方插件,这种形式不罕用,因为响应工夫和很多因素有关系,会导致交换机过热流量歪斜。
综上,咱们大多数状况下都会应用 RR 轮询,如果上游服务是有状态的,能够抉择 hash,更非凡的如果零碎正在进行灰度公布,能够本人编写 lua 脚本动静调整。
资源服务
如果你抉择让 Nginx 作为资源服务器,比方你心愿将前端动态资源、图片前置到 Nginx 服务器,上游服务只负责业务解决,那么你很能须要进行动静拆散的配置
server {
location / {
proxy_pass http://127.0.0.1:8080;
root html;
index index.html index.htm;
}
location /css {
root /usr/local/nginx/static;
index index.html index.htm;
}
location /images {
root /usr/local/nginx/static;
index index.html index.htm;
}
location /js {
root /usr/local/nginx/static;
index index.html index.htm;
}
}
咱们也能够应用正则匹配的形式
location ~*/[js|css|images] {
root /usr/local/nginx/static;
index index.html index.htm;
}
对于 location 的书写有通用模式、精准模式和正则模式:
- / 通用匹配,任何申请都会匹配到。
- = 精准匹配,不是以指定模式结尾
- ~ 正则匹配,辨别大小写
- ~* 正则匹配,不辨别大小写
- ^~ 非正则匹配,匹配以指定模式结尾的 location
在同一个配置文件中也有一些规定:
- 多个正则按书写程序进行匹配,fallthrough
- 非正则也是按书写程序匹配,找到匹配度最高的规定执行
- 正则模式优先级高于非正则,优先级程序为“=”匹配 >“^~”匹配 > 正则匹配 > 一般
比方咱们能够将 Flask 中的图片删除,在 Nginx 中进行上面配置
$ docker exec flask-demo-1 rm /python-docker/static/img/demo.png
$ docker exec flask-demo-2 rm /python-docker/static/img/demo.png
$ docker exec flask-demo-3 rm /python-docker/static/img/demo.png
编辑配置文件
location ~*/static {
root flask-demo;
index index.html index.htm;
}
将图片上传到 $NGINX_DIR/html/flask-demo/static/img
中。这里也能够应用 alias。
root 和 alias 的区别:
- root:设置根目录,nginx 会在根目录,依照 URL 的层级寻找文件,root 保留匹配上的局部进行搜寻
alias:和 root 相似,但会舍弃匹配上的局部进行搜寻
location ^~ /static {alias flask-demo/static/;}
应用 alias 时,当 nginx 匹配上 location 后,会舍弃匹配上的文本,比方下面配置的
/static
,当拜访http://localhost/static/img/demo.png
时,会保留/img/demo.png
,如果配置为/static/
则保留下img/demo.png
,保留下来的局部会拼接到`alias
前面,所以这里 alias 最初加了 /,如果 location 保留了 /,这里就不要加。如果填写相对路径,父目录是 NGINX_DIR
URI 重写
当初咱们曾经能够残缺配置 Nginx 了,但有一些场景咱们须要对 URI 进行重写:
- 防盗链:有时候咱们不心愿咱们的图片、JS 被其余网站间接援用
- 暗藏 URL:让用户看不出申请地址,避免歹意拜访
- 增加文件后缀:比方在连贯后增加 .html 后缀,搜索引擎的蜘蛛会辨认并收集外面的内容
只须要在 location
中增加配置
rewrite ^/([0-9]+).html$ /?num=$1 break;
# break 示意应用以后规定
# last 示意匹配下一条规定,如果没有下一条则应用以后规定重写
# redirect 示意 301 长期重定向,浏览器会从新拜访重写后的 URL
# permanent 永恒重定向,301 和 302 次要是给搜索引擎的蜘蛛辨认
比方
location / {rewrite ^/([0-9]+).html$ /?num=$1 break;
proxy_pass http://flask;
}
拜访 localhost/10086.html
,响应体的 html 中能够读取到 arg,正因为进行了 URI 重写
防盗链
Gitee 图床之前在别的网站援用会返回一个 Gitee 的图标,避免其余网站滥用图床,这里应用到了防盗链。当咱们应用浏览去拜访一个网页后,再次向服务器获取图片、js 等资源时会主动带上 Referer 头。
在 Nginx 中咱们能够进行配置,检测 Referer 是否非法
valid_referers none | blocked | server_names | strings ....;
- none:容许没有携带 Referer 头的申请
- blocked:申请头中存在 Referer 字段,但值不是以 ”http(s)://” 结尾的字符串
咱们能够为 demo.png 设置防盗链
location ~* ^.+\.*..(jpeg|gif|png|jpg) {
valid_referers 192.168.80.154;
if ($invalid_referer) {rewirte ^/ /invalid.png}
}
location /static {
valid_referers 192.168.80.154;
if ($invalid_referer) {return 403;}
root flask-demo;
index index.html index.htm;
}
location = /invalid.png {root flask-demo;}
尝试用浏览器拜访一下 192.168.80.154/static/demo.png
会发现返回的是 invalid.png
高可用配置
单台 Nginx 作为网关容易呈现单点故障,所以须要为其筹备一台从机进行 backup。应用 VIP + keepalived 的形式,主节点的 keepalived 通过过程状态或者脚本监控 Nginx 服务,如果 Nginx 宕机则被动放弃 Master 角色,或者从机的 keepalived 发现主节点发送 VRRP Report 超时也会降级为 Master。
- keepalived 官网:https://www.keepalived.org/in…
- VRRP 详解:https://cshihong.github.io/20…
- keepalived 进行服务监控:https://www.wumingx.com/linux…
- keepalived 脑裂问题:https://www.zhihu.com/questio…
简略概括一下,keepalived 基于 VRRP 协定,VRRP 协定可能将多个设施虚构成一台设施对外裸露服务。VRRP 协定中存在 Master 和 Backup,Master 会一直通过组播形式通告以后持有的虚构 IP,并响应局域网内的 ARP 申请。Backup 则保护定时器,只有在肯定工夫内接管不到 Master 的通告报文,或是 Master 的优先级小于以后 Backup 的优先级(相等则比拟 IP 地址大小,大者优先)则降级为 Master,旧 Master 接管到通告也会降级为 Backup。
这里会有几个问题:
如果不通顺,Backup 定时器超时降级为 Master 后受到旧 Master 的通告,发现自己优先级不高又回到 Backup,造成频繁切换。能够设置一下切换提早,延迟时间设置须要衡量。
依照 keepalived 并进行配置
$ yum -y install openssl-devel
$ ./configure --prefix=/usr/local/keepalived
$ make && make install
$ vim etc/keepalived/keepalived.conf
# Master 节点配置
global_defs {router_id node1}
vrrp_instance nginx-demo {
state MASTER
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass admin
}
virtual_ipaddress {192.168.80.220 # 虚 IP}
}
# Backup 节点配置
global_defs {router_id node2}
vrrp_instance nginx-demo {
state BACKUP
interface ens33
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass admin
}
virtual_ipaddress {192.168.80.220 # 虚 IP}
}
$ systemctl start keepalived
Https 配置
https 是二十一世纪最平凡的创造之一,通告非对称加密,公钥加密私钥解密、私钥加密公钥解密、公钥加密公钥无奈解密、CA 机构认证等形式保障通信内容不被篡改、不被伪造、不被窃听
整个 https 原理这里就不赘述了,这里有一点想说的是,比方咱们的服务器和域名在阿里云上购买,向 CA 申请证书时须要认证以后服务器和以后域名都是同一个用户的,阿里云应用 DNS 自动检测的形式,也就是让用户批改 DNS 记录到一个指定地址,CA 认证服务器拜访看看是否胜利(这一步也是主动的)。除了这种形式,还能够让用户本人上传一个文件到服务器中并裸露拜访地址,CA 通过拜访该地址确认服务器的归属人。
Nginx 上配置也比较简单,将证书和私钥上传并配置即可
server {
# 服务器端口应用 443,开启 ssl, 这里 ssl 就是下面装置的 ssl 模块
listen 443 ssl;
# 域名,多个以空格离开
server_name hack520.com www.hack520.com;
# ssl 证书地址
ssl_certificate /usr/local/nginx/cert/ssl.pem; # pem 文件的门路
ssl_certificate_key /usr/local/nginx/cert/ssl.key; # key 文件的门路
# ssl 验证相干配置
ssl_session_timeout 5m; #缓存有效期
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #平安链接可选的加密协议
ssl_prefer_server_ciphers on; #应用服务器端的首选算法
location / {
root html;
index index.html index.htm;
}
}
server {
listen 80;
server_name hack520.com www.hack520.com;
return 301 https://$server_name$request_uri;
}
以上就是实战上篇所有内容啦,心愿大家可能通过本文疾速配置 Nginx 并应用,下篇会介绍一下高级模块、二次开发、缓存等性能,敬请期待!