关于nginx:Nginx-实战核心知识点整理上

5次阅读

共计 11229 个字符,预计需要花费 29 分钟才能阅读完成。

Nginx 作为一款高性能的代理软件,在工作中常被用做负载均衡器、正向反向代理。最近在看了一个对于 Nginx 的视频,老师讲得很不错。笔者也记录了一下整套课程的一些重点和本人的了解。

笔者学习习惯是先实战后实践,所以本文次要偏实战,心愿读者能够通过本文疾速搭建 Nginx,并尝试应用它的个性。对于 Nginx 的运行原理、外围模块、源码剖析等内容可能要等下次了(其实是笔者学艺不精,临时看不太懂),大家感兴趣能够先看看其余优质文章。

思维导图

那咱们开始吧!

筹备工作

Nginx 是什么这里就不赘述了,置信大家都用过,Nginx 目前的发行版本如下:

  1. Nginx 开源版
  2. Nginx Plus:F5 基于 Nginx 开源版开发的商业版本
  3. Openresty:国人开发,整合了 lua 模块
  4. 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 通过先用特殊符号替换双引号中的内容防止误操作,通过正则表达式的形式去除空白字符,我的项目链接:

  1. nginx-config-formatter:https://github.com/slomkowski…
  2. 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 块。这里能够应用通配符,优先级自上而下。基于此能够实现

  1. 多租户二级域名:

    比方 xxx.domain.com,ServerName 配置 *.domain.com,后端服务器解析 Http 报文中的 Host,依据 xxx 展现对应用户信息,当然这样子不太平安。

  2. 短网站:这个须要和前面介绍 URLRewrite 配合应用

咱们能够自定义一个 Server 实现 HTTP DNS,但这须要咱们学习到前面 lua 的时候才好实现。

HTTPDNS:因为 DNS 净化、本地 DNS 服务不够智能、DNS 依据本人而非客户返回最近服务器谬误等问题,咱们能够本人基于 HTTP 协定搭建 DNS 服务器,但这须要客户端本人实现发动解析的逻辑,然而浏览器不反对 HttpDNS,解决形式是本地启动一个 DNS 服务器联合 FakeIP 解决,这个能够参考我之前写的 Clash 代理工具解析。

server_name 配置形式:

  1. 残缺配置

    server_name  domain1.com domain2.com;
  2. 正则配置

    server_name  domain1.~^[0-9]+\.domain.com;
  3. 通配符配置

    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 还有几个能够设置的参数:

  1. down:不参加调度
  2. backup:除了 backup 以外的服务器都无奈进步服务时被调度

这两个个别不会应用,比方 backup,咱们试图心愿服务呈现故障后备用机器能够顶替,但如果是因为代码逻辑不正确导致的宕机,实践上备用机器上的代码也是有问题的,所以切换后很可鞥也会宕机。

ip_hash
轮询算法有个问题就是每次申请必须是无状态的,即不能应用单个服务器的 session 暂存数据。ip_hash 算法依据客户端 IP 进行散列,所以每次申请都会打到同一个服务器,但该算法的问题在于:

  1. 因为手机挪动过程中 IP 地址会产生扭转,如果上游服务应用了 session 会导致生效
  2. 可能呈现大部分 IP 地址散列到同一台上游服务
  3. 对于内网用户数量较多,比方考试零碎、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

在同一个配置文件中也有一些规定:

  1. 多个正则按书写程序进行匹配,fallthrough
  2. 非正则也是按书写程序匹配,找到匹配度最高的规定执行
  3. 正则模式优先级高于非正则,优先级程序为“=”匹配 >“^~”匹配 > 正则匹配 > 一般

比方咱们能够将 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 进行重写:

  1. 防盗链:有时候咱们不心愿咱们的图片、JS 被其余网站间接援用
  2. 暗藏 URL:让用户看不出申请地址,避免歹意拜访
  3. 增加文件后缀:比方在连贯后增加 .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 并应用,下篇会介绍一下高级模块、二次开发、缓存等性能,敬请期待!

正文完
 0