学习目标
- 理解流行工具生成的配置文件以及各种文档中的配置文件。
- 从头开始将Nginx配置为Web服务器、反向代理服务器和负载均衡器。
- 优化Nginx以获得最大的服务器性能。
简介
Nginx是一种高性能网络服务器,专注于高性能、高并发和低资源使用。核心是一个反向代理服务器。一般用于反向代理和静态资源托管。 对比Apache(httpd)的优势:
- 它可以处理更多的并发请求。
- 它可以在更低资源消耗的前提下更快地交付静态内容。
配置实验场地
虚拟机环境
推荐使用multipass创建虚拟机(MacOS用户可跳过这一步骤, 区别仅在于nginx配置文件的目录)
- 安装multipass
- 创建虚拟机(cloud-config.yml可参考简易配置)
multipass launch --name master --cpus 2 --mem 2G --disk 10G --cloud-init cloud-config.yml
- 进入虚拟机的bash环境
multipass shell master
- 确保nginx已经启动
- 可以查看nginx服务的状态
service nginx status ------------------------------------------- ... Active: active (running) ...
- 如果没有启动,可执行启动命令
sudo service nginx start
- 可以查看nginx服务的状态
其他准备
为了更好的演示多域名下的Nginx配置,推荐使用switchhost修改客户端的配置
初次使用
配置第一个站点
nginx的配置文件在/etc/nginx/
目录下(MacOS中则在/usr/local/etc/nginx/
下),以.conf
扩展名结尾。
- 初始化配置文件
原始的配置文件比较复杂,初次学习时可以将其备份下(将
nginx.conf
重命名为nginx.conf.bak
),后创建新的nginx.conf
,并输入如下内容:events { } http { server { listen 80; server_name local.com; return 200 "Bonjour, mon ami!\n"; } }
- 检验配置文件
sudo nginx -t
- 配置无误后可重新加载配置文件
sudo nginx -s reload
- 通过
curl
查看效果curl -i http://local.com
基础概念: 指令和上下文
指令分为简单指令(;
结尾)和块指令({}
括起来的指令)
能够在其中包含其他指令的块指令称为上下文(类似作用域)。
Nginx有四个核心上下文:
* events
: 用于设置nginx在一般级别处理请求的全局配置,具有唯一性
* http
: 定义服务器将如何处理HTTP和HTTPS请求的配置,也具有唯一性
* server
: 嵌套在http
中,用于在单个主机内配置特定的虚拟服务器
* main
: main
上下文是配置文件本身,所有其他上下文都包含在main
中
静态内容服务
之前最基础的配置中,我们已经可以通过让Nginx响应一个纯文本了,下面尝试让Nginx提供静态文件。
获取源码
为了方便学习,可以在Nginx配置的根目录下创建www
目录用于存放站点项目源代码。
git clone https://github.com/HenryJi529/nginx-demo-projects.git
提供静态内容
现在有了要提供的静态内容,更新nginx.conf
events {
}
http {
server {
listen 80;
server_name local.com;
root /usr/local/etc/nginx/www/static-demo;
}
}
root
用于声明站点的根目录,是Nginx在收到相应请求后查找的路径
访问http://local.com
尽管Nginx已正确提供index.html
,但似乎CSS样式不起作用。
静态文件类型处理
观察静态网站,得出css的路径为 http://local.com/css/style.css
通过curl
查看请求请求报文首部
curl -I http://local.com/css/style.css
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 11:02:00 GMT
Content-Type: text/plain
Content-Length: 474
Last-Modified: Wed, 22 Jun 2022 16:13:08 GMT
Connection: keep-alive
ETag: "62b33f94-1da"
Accept-Ranges: bytes
其中, Content-Type
标记这个css文件为text/plain
而不是text/css
, 这就是网页格式未能展现的原因。
可以通过引入mime.types
文件加以解决(MIME: Multipurpose Internet Mail Extensions)
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
root /usr/local/etc/nginx/www/static-demo;
}
}
include
关键字可以用于引入模块,便于模块化配置。
重新加载配置并通过curl
查看css的响应首部
curl -I http://local.com/css/style.css
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 11:45:10 GMT
Content-Type: text/css
Content-Length: 474
Last-Modified: Wed, 22 Jun 2022 16:13:08 GMT
Connection: keep-alive
ETag: "62b33f94-1da"
Accept-Ranges: bytes
Content-Type
已经成功转化成text/css
。
再通过浏览器访问页面:
root
与alias
root
与alias
都用于在提供静态服务时指定文件路径,但
alias
只能用在location
上下文中- 通过
root
获得的文件目录是root
路径+location
路径;通过alias
获得的文件目录是alias
路径(直接替换掉location
路径)
动态路由
上一小节实现了一个简单的静态内容服务器配置,它获取与URI相匹配的站点文件,并进行相应。
然而,如果访问的URI无法匹配到站点文件,将返回默认的404页面
这一问题可以通过动态路由配置来解决
在本节,需要了解location
上下文、变量、重定向、重写以及try_files
指令。
位置匹配
前缀匹配
更新配置如下:
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
root /usr/local/etc/nginx/www/static-demo;
location /location1 {
return 200 "location1\n";
}
}
}
这里引入了location
上下文,这个上下文通常嵌套在server
块中。
通过curl
测试配置效果
curl -i http://local.com/location1
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 12:51:28 GMT
Content-Type: text/plain
Content-Length: 10
Connection: keep-alive
location1
访问http://local.com/location1/, http://local.com/location11/, http://local.com/location11/1也能获得相同的结果。事实上通过这一配置,任何location1
开头的路径都能被匹配到。这种匹配被称为前缀匹配。
完全匹配
要进行完全匹配,需更新配置为
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
root /usr/local/etc/nginx/www/static-demo;
location = /location1 {
return 200 "location1\n";
}
}
}
这种配置下,向除/location1
外,甚至/location1/
都会得到404响应。
curl -I http://local.com/location1/
-------------------------------------------
HTTP/1.1 404 Not Found
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 13:30:40 GMT
Content-Type: text/html
Content-Length: 153
Connection: keep-alive
完全匹配还有一些诡异的特性,可以看下面的例子
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
location = /www {
root /usr/local/etc/nginx;
}
}
}
通过实验可以知道,此时访问http://local.com/www仍然返回404。根据root与location的原理,此时匹配的应该是/usr/local/etc/nginx/www/index.html
,虽然根目录上却有这样的文件,但因为完全匹配的机制,依旧引发了404。
正则匹配
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
location ~ /hello[0-9] {
return 200 "Hello\n";
}
}
}
通过~
,告诉Nginx执行正则表达式匹配
curl -i http://local.com/hello1/12
-------------------------------------------
HTTP/1.1 200 OK
Server: nginx/1.23.0
Date: Sun, 17 Jul 2022 15:22:49 GMT
Content-Type: text/plain
Content-Length: 6
Connection: keep-alive
Hello
- 实际上,不考虑优先级的情况下,前缀匹配也可以看作特殊的正则匹配,而完全匹配可以看作是路径加上
$
的正则匹配。 - 默认情况下,正则表达式区分大小写,通过
~*
就可以转化为不区分大小写。
匹配优先级
匹配模式 | 标记符 | 优先级 |
---|---|---|
完全匹配 | = |
1 |
优先前缀匹配 | ^~ |
2 |
正则匹配 | ~ or ~* |
3 |
前缀匹配 | None | 4 |
不妨验证一下:
- 更新Nginx配置如下:
events { } http { include mime.types; server { listen 80; server_name local.com; location /hello7 { return 200 "Hello7\n"; } location ^~ /hello8 { return 200 "hello8\n"; } location ~ /hello[0-9] { return 200 "hello\n"; } } }
- 通过
curl
查看结果:- 正则匹配与优先前缀匹配同时满足时,选择优先前缀匹配
curl http://local.com/hello8 ------------------------------------------- hello8
- 正则匹配与前缀匹配同时满足时,选择正则匹配
curl http://local.com/hello7 ------------------------------------------- hello
- 正则匹配与优先前缀匹配同时满足时,选择优先前缀匹配
此外, 值得注意的是,对于同属一个优先级的正则匹配,是按照在文件中的顺序进行匹配的。
内部匹配
@
用于定义一个location块,不能被客户端直接访问,只能被Nginx内部配置指令访问,如try_files
或error_page
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
location / {
error_page 418 = @queryone;
error_page 419 = @querytwo;
error_page 420 = @querythree;
if ( $args ~ "service=one" ) { return 418; }
if ( $args ~ "service=two" ) { return 419; }
if ( $args ~ "service=three" ) { return 420; }
}
location @queryone {
return 200 'do stuff for one\n';
}
location @querytwo {
return 200 'do stuff for two\n';
}
location @querythree {
return 200 'do stuff for three\n';
}
}
}
通过curl
测试:
curl "http://local.com/1?service=one"
-------------------------------------------
do stuff for one
变量
定义与查看
Nginx通过set
可以在配置文件的任何位置定义新变量,
具体用法如下:
# set $<variable_name> <variable_value>;
set name "Farhan"
set age 25
set is_working true
Nginx支持三种类型的变量
* 字符串
* 整型
* 布尔型
除去自己申明外,nginx还有内置变量
这里提供一个简单的方法设置与查看变量:
- 配置nginx.conf
events { } http { include mime.types; server { listen 80; server_name local.com; return 200 "Host - $host\nURI - $uri\nArgs - $args\n"; } }
- 用
curl
访问http://local.com/1?name=henry
显然,curl "http://local.com/1?name=henry&age=18" ------------------------------------------- Host - local.com URI - /1 Args - name=henry&age=18 Name - henry
$host
保存根地址,$uri
保存相对于根的请求URI,而$args
包含所有查询字符串。 此外,通过set $name $arg_name;
可以将查询的字符串变量的值保存在了Nginx的新变量中。
常用的内置变量
$remote_addr
: 存放了客户端的公网IP$host
保存根地址$uri
保存相对于根的请求URI$request_uri
: 包含请求参数的原始URI$args
包含所有查询字符串$http_user_agent
: 客户端浏览器的详细信息$http_cookie
: 客户端的cookie信息$remote_port
: 客户端访问Nginx服务器随机打开的端口$request_method
: 请求资源的方式,GET/PUT/DELETE等$scheme
: 请求的协议,如ftp,https,http等$server_protocol
: 保存了客户端请求资源使用的协议的版本,如HTTP/1.0,HTTP/1.1,HTTP/2.0等$server_addr
: 保存了服务器的IP地址$server_name
: 请求的服务器的主机名$server_port
: 请求的服务器的端口号
命令
return
该命令一般用于对请求的客户端直接返回状态码(3XX,4XX),或者用于测试路由(200)。 语法:
return code [text];
return [code] URL;
rewrite
用于通知客户端,请求的资源已经换地方,例如访问joke.morningstar529.com,转到morningstar529.com/joke/ 用法:
rewrite regex URL [flag];
关于flag中的redirect
与last
:
* redirect: 返回包含 302 代码的临时重定向,在替换字符串不以http://
等开头时使用
* permanent: 返回包含 301 代码的永久重定向
deny
用于禁止访问某个目录下的文件,用法如下:
location /directory {
location ~ .*\.(scss)?$ {
deny all;
}
}
日志系统
默认情况下,nginx的日志文件位于/var/log/nginx/
目录下(MacOS中则在/usr/local/var/log/nginx/
下),以.log
扩展名结尾。
ls -l /usr/local/var/log/nginx
-------------------------------------------
total 512
-rw-r--r-- 1 henry529 admin 168023 Jul 20 10:13 access.log
-rw-r--r-- 1 henry529 admin 79382 Jul 20 08:18 error.log
基础使用
- 配置
nginx.conf
events { } http { include mime.types; server { listen 80; server_name local.com; return 200 "success\n"; } }
- 清空这两个文件
rm /usr/local/var/log/nginx/access.log /usr/local/var/log/nginx/error.log
- 重新开启Nginx服务
如果不向Nginx发送sudo nginx -s reload
reopen
信号,他会继续将日志写入之前打开的流,新文件将保持为空。 - 通过
curl
访问网站根目录curl -i http://local.com ------------------------------------------- HTTP/1.1 200 OK Server: nginx/1.23.1 Date: Sun, 24 Jul 2022 04:59:05 GMT Content-Type: text/plain Content-Length: 8 Connection: keep-alive ... success
- 查看日志
cat /usr/local/var/log/nginx/access.log ------------------------------------------- 127.0.0.1 - - [24/Jul/2022:12:55:16 +0800] "GET / HTTP/1.1" 200 8 "-" "curl/7.79.1"
配置日志系统
- 修改常规日志位置
access_log /var/logs/nginx/admin.log;
- 修改错误日志位置及级别
有八个级别的错误消息:error_log /var/log/error.log warn
- debug – 有助于确定问题所在的有用调试信息。
- info - 不需要阅读但可能很好了解的信息性消息。
- notice - 发生了一些值得注意的正常现象。
- warn - 发生了意外,但不必担心。
- error - 某些事情不成功。
- crit - 存在急需解决的问题。
- alert - 需要迅速采取行动。
- emerg - 系统处于无法使用的状态,需要立即关注。
反向代理
- 定义: 客户端向Nginx服务器发起请求,而Nginx再将请求传递给后端;后端服务器处理完请求后,将其发送回Nginx,之后,Nginx将响应返给客户端
- 特点: 整个过程中,客户端不知道谁在实际处理请求,所以要反向代理
- 作用:
- 提高访问速度(通过缓存)
- 进行负载均衡
- 保证后端服务安全
简易案例
配置nginx.conf
:
events {
}
http {
include mime.types;
server {
listen 80;
server_name local.com;
location / {
proxy_pass https://segmentfault.com/;
}
}
}
访问http://local.com,将看到原始的思否站点:
搭配Python后端
- 在Nginx配置根目录下执行:
python3 www/python-socket-demo/server.py ------------------------------------------- Server started...
- 通过
curl
检测Python后端是否正常运行curl -i 127.0.0.1:8000 ------------------------------------------- HTTP/1.1 200 OK Content-Type: text/html ... Hello World
- 更新nginx配置如下:
events { } http { include mime.types; server { listen 80; server_name local.com; location / { proxy_pass http://localhost:8000; } } }
- 通过浏览器检查反向代理是否成功 显示的正是后端的响应,说明反向代理成功
负载均衡
基本配置方法
由于Nginx的反向代理设计,可以轻松地将其配置为负载均衡器。
这里通过load-balancer-demo/
目录下的三个server来演示下:
- 在三个终端中分别启动三个server:
node www/load-balancer-demo/server-x.js
- 更新nginx配置如下:
其中,events { } http { include mime.types; upstream backend_servers { server localhost:3001; server localhost:3002; server localhost:3003; } server { listen 80; server_name local.com; location / { proxy_pass http://backend_servers; } } }
upstream
是一组可以被视为单个后端的服务器。 - 通过
curl
检测:
可以看到,负载均衡已经实现。while sleep 0.5; do curl http://local.com; done ------------------------------------------- response from server - 1. response from server - 2. response from server - 3. response from server - 1. response from server - 2. response from server - 3. response from server - 1. response from server - 2.
负载均衡策略
- 轮询: 默认方式
upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; }
weight
: 权重方式,默认为1,权重越高,被分配的客户端请求就越多upstream webservers{ server 192.168.100.128:8080 weight=90; server 192.168.100.129:8080 weight=10; }
ip_hash
: 依据ip分配方式,这样每个访客可以固定访问一个后端服务upstream webservers{ ip_hash; server 192.168.100.128:8080; server 192.168.100.129:8080; }
least_conn
: 依据最少连接方式,把请求优先分配给连接数少的后端服务upstream webservers{ least_conn; server 192.168.100.128:8080; server 192.168.100.129:8080; }
url_hash
: 依据url分配方式,这样相同的ur会被分配到同一个后端服务upstream webservers{ hash &request_uri; server 192.168.100.128:8080; server 192.168.100.129:8080; }
fair
: 依据响应时间方式,响应时间短的服务将会被优先分配upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; fair; }
配置文件模块化
一般来说nginx.conf
应该有nginx维护者而不是服务器管理员来更改。所以,在实际配置nginx中,不修改nginx.conf
,而是通过原始配置文件中自带的include conf.d
等命令,导入模块化的站点配置文件。