参考来自二、Nginx四层与七层负载均衡 – Egon林海峰 (egonlin.com)
一、什么是四层与七层负载均衡
判定标准:osi七层协议,能解析到哪一层你就哪一层的设备
下定义:
1、客户端产生一个数据包,就好比是一个快递包,层层包裹七层
2、四层:解析到四层,看到的是tcp或udp协议,然后基于ip+port进行转发
3、七层:解析到七层,看到的是七层协议,例如http协议,然后url地址进行转发
强调:
1、一个完整的包,包含了七层,相当于一个快递被层层包裹了七层
2、为何要包这么多层?
为了让沿途帮忙转发的设备都能知道下一步该转发给谁
3、网络设备在解析包的时候,会读取某一层的内容,也可能会修改
但是该有几层还是几层
二、四层负载均衡的应用场景
用在要基于ip+port转发的场景
例如:
1、代理MySQL、redis
2、四层代理七层来放大并发
补充:
tcp:流式协议–》stream
udp:数据报协议
三、七层负载均衡的应用场景
用在要基于七层协议转发的场景
例如BS架构的软件,应用层都是http协议
那代理多台web服务器就需要用到七层负载均衡
四、负载均衡的软硬介绍
四层可选方案
1、F5(支持4层、7层)
2、LVS(支持4层,堪比F5)
七层可选方案
3、haproxy(4层、7层)
4、nginx(4层、7层、web服务)
./configure –prefix=/usr/local/nginx –with-openssl –with-stream # 启用/支持stream模块
实验1:四层代理数据库(不完整的方案)
一、准备机器+环境
三台机器
关闭防火墙、selinux
配置静态ip
时间同步
二、机器规划
四层负载均衡:192.168.71.112 13306
数据库1: 192.168.71.113 3306
数据库2: 192.168.71.116 3306
三、先部署好数据库
准备好数据库1:
yum install mariadb* -y
systemctl start mariadb
systemctl enable mariadb
初始化数据:xxx.sql
# 示例:非交互方式执行sql
# mysql -uroot --password='' -e "show databases;"
# 导入sql
# mysql -uroot --password='' < xxx.sql
create database testDB;
create table testDB.messages(id int(11),text varchar(15));
select * from testDB.messages;
准备一个远程账号
grant all on testDB.* to 'egon'@'%' identified by '123';
flush privileges;
准备数据库2:
同上一模一样的操作
四、准备四层负载均衡
yum install nginx -y
# nginx -V 验证是否开启了stream支持,--with-stream=dyamic
yum install -y nginx-mod-stream
# vim /etc/nginx/nginx.conf
worker_processes 4;
worker_rlimit_nofile 40000;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 8192;
}
stream {
upstream my_servers {
server 192.168.10.11:8080 max_fails=3 fail_timeout=5s;
server 192.168.10.12:8080 max_fails=3 fail_timeout=5s;
server 192.168.10.13:8080 max_fails=3 fail_timeout=5s;
}
server {
listen 80;
# proxy_pass后还是建议跟一个upstream,而不是直接只跟一个ip:port只代理一台机器(如proxy_pass 192.168.10.14:8080 这就只代理一个ip+port了,失去了负载均衡效果,负载均衡还是要搭配upstream一起用才对)
proxy_pass my_servers;
}
}
systemctl restart nginx
systemctl enable nginx
实验2:四层代理七层–》放大并发
方案:
四层–》nginx
七层–》nginx
四层:192.168.71.115 9999
七层负载均衡01: 192.168.71.116 8081
七层负载均衡02: 192.168.71.111 8081
web01: 192.168.71.112 8080
web02: 192.168.71.113 8080
web03: 192.168.71.114 8080
一、初始化环境
所有机器
关闭防火墙、selinux
配置静态ip
时间同步
二、准备web服务器
web服务 + web应用(前端+后端)
web服务—-》nginx
web应用—-》前端:1.txt
web03:
yum install nginx -y
vim /etc/nginx/nginx.conf # 改listen 8080
echo “================web03” > /usr/share/nginx/html/1.txt
systemctl restart nginx
systemctl enable nginx
web02:
yum install nginx -y
vim /etc/nginx/nginx.conf # 改listen 8080
echo "================web02" > /usr/share/nginx/html/1.txt
systemctl restart nginx
systemctl enable nginx
web01:
yum install nginx -y
vim /etc/nginx/nginx.conf # 改listen 8080
echo “================web01” > /usr/share/nginx/html/1.txt
systemctl restart nginx
systemctl enable nginx
三、配置七层代理web服务器
七层负载均衡02
yum install nginx -y
[root@lb01 ~]# vim /etc/nginx/nginx.conf
events {
worker_connections 1024;
}
http {
upstream web_app_servers {
server 192.168.1.103:8080 weight=1;
server 192.168.1.104:8080 weight=1;
server 192.168.1.105:8080 weight=1;
}
server {
listen 8081;
location / {
proxy_pass http://web_app_servers;
}
}
}
systemctl restart nginx && systemctl enable nginx
七层负载均衡01
与上面完全一致
四、配置四层代理
yum install nginx -y
# nginx -V 验证是否开启了stream支持,--with-stream=dyamic
yum install -y nginx-mod-stream
# vim /etc/nginx/nginx.conf
worker_processes 4;
worker_rlimit_nofile 40000;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 8192;
}
stream {
upstream my_servers {
server 192.168.10.11:8080 max_fails=3 fail_timeout=5s;
server 192.168.10.12:8080 max_fails=3 fail_timeout=5s;
server 192.168.10.13:8080 max_fails=3 fail_timeout=5s;
}
server {
listen 80;
# proxy_pass后还是建议跟一个upstream,而不是直接只跟一个ip:port只代理一台机器(如proxy_pass 192.168.10.14:8080 这就只代理一个ip+port了,失去了负载均衡效果,负载均衡还是要搭配upstream一起用才对)
proxy_pass my_servers;
}
}
systemctl restart nginx
systemctl enable nginx
五、四层负载均衡添加日志
四层负载均衡是没有access的日志的,因为在nginx.conf的配置中,access的日志格式是配置在http下的,而四层负载均衡配置是在http以外的;如果需要日志则需要配置在stream下面。
Nginx 的流模块在处理 TCP 流量时,并不会在每个请求都写入一条日志。相反,它在 TCP 连接结束后(也就是说,当连接关闭时)才会记录一条日志。
这与 HTTP 不同,因为TCP 是一种基于连接的协议,每个连接可能包含多个请求和响应。由于 Nginx 的 stream 模块在更低的网络层处理这些连接,它并没有一个明确定义的 “请求”。所以,在 TCP 连接生命周期中,只有当连接关闭时,Nginx 才会记录连接的总体信息。因此,你如果你在访问服务器时看不到立即写入日志,那是因为你的 TCP 连接还没关闭。
一般情况下,浏览器可能会在一段时间内复用一个 TCP 连接,发送多个请求,直到连接超时或者主动关闭连接。
因此,你可能只有在这个 TCP 连接关闭时才会在日志中看到记录。
如果你希望立即看到日志,你可以尝试在发送请求后立即关闭 TCP 连接。
如果你使用 curl 或者 telnet 工具发送请求的话,他们通常在请求结束后就会立即关闭连接。
[root@lb ~]# cat /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
stream {
log_format proxy1 '$remote_addr $remote_port - [$time_local] $status $protocol '
'"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"';
access_log /var/log/nginx/proxy1.log proxy1;
upstream lb_servers {
server 192.168.71.12:9090 max_fails=3 fail_timeout=5s;
server 192.168.71.13:9090 max_fails=3 fail_timeout=5s;
}
server {
listen 80;
proxy_pass lb_servers;
}
}
#注意给权限,我们的启动user就是nginx
chown -R nginx.nginx /var/log/nginx/
#查看日志
tail -f /var/log/nginx/proxy1.log
六、后端访问真实IP,7层负载均衡配置
为何让客户端web服务拿到访问者的真实ip
针对获取访问者真实ip地址这件事,需要思考两件事
1、如何才能把访问者的真实ip地址透传给后端web服务
最轻量级的方案:就是在协议里加,具体点说如果是http协议那就可以加到请求头里。这种把内容放到协议里来实现透传信息的思想在后续你做更高级的一些架构构思时也会经常用到
2、在哪里配置
web服务器上不需要配置,因为它是享受成果的人,需要配置的是那些沿途转发的代理们(四层负载均衡、七层负载均衡)需要用到—个关键参数X-Forwarded-For,
为了快速了解X-Forwarded-For,我们先当四层负载均衡不存在,我们就让用户直接访问七层负载均衡,那七层负载均衡如何透传客户端的真实ip呢?·七层负载均衡:会解开http协议头,然后重新封包再向后端转发http协议的请求,这个过程中真实访问者ip地址就被丢弃掉了,因为七层负载均衡重新封装了包,于是乎,包送到web服务器上时,web服务器上日志$remote_addr获取的就是七层负载均衡的ip地址,而不是真正的访问者真实ip地址,这整套处理逻辑必然是无法更改的,除非你更改上下游的源代码,那样子就侵入太大了。如何才能不改变、不破坏七层负载均衡拆解包转发逻辑的源代码,还能实现把访问者的真实ip地址透传给后端的web服务器呢?很简单,我们可以这么做,往请求头里加一点东西就行:七层负载均衡在收到http包里肯定有客户端ip,别扔、把它赋值给另外一个key名,然后附在请求头里,送给客户端就行,客户端再从这个独特的key名取出来就能拿到真实的访问者ip地址了,如此,我们便在没有改变任何源代码的情况下,就实现了这个功能的增加,这是一种思想,一种解决问题的高级思想,我愿意将其称之为屎上雕花,这并非开玩笑,你平时工作中就是会碰到很多很多的屎,这些屎不可能推翻重来,而且源代码都不能改,一改就是连锁反应的前溃,最好的方法就是原来的屎就不动了,还能往里加点新功能就是最棒的。具体配置如下
7层负载均衡配置:
cat > /etc/nginx/nginx.conf << EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
upstream webserver {
server 192.168.71.14:8080;
server 192.168.71.15:8080;
server 192.168.71.16:8080;
}
server {
listen 9090;
location / {
proxy_pass http://webserver;
# -------------------》只加上下面这一段即可
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
EOF
===
解释:
1、X-Forwarded-For 头字段,它是一个被逐级代理服务器添加的、包含了所有中间 IP 的顺序列表,
列表的最左侧是原始客户端的 IP。如果请求没有经过任何代理,这个变量为空。如果经过多级代理,
这个变量会依次添加代理服务器的 IP,因此要获取真实的客户端 IP,应取这个列表的第一个 IP。
2、 HTTP 的 X-Real-IP 头字段,它被设计为单个 IP 地址,代表原始客户端的 IP。
当请求通过代理时,代理服务器通常会添加或者覆盖这个字段;
但请注意,并非所有的代理服务器都会设置这个字段。
因此,要根据你所使用的负载均衡器或代理服务器的行为来正确获取客户端的 IP。
web服务端配置
# $http代表从http请求头里取
$http_x_forwarded_for # 获取X-Forwarded-For的值
$http_x_real_ip # 获取X-Real-IP的值
=================================
日志配置如下
http { # $remote_addr获取的还是负载均衡的ip地址这一点不会改变,我们默认增加了两个"$http_x_forwarded_for" "$http_x_real_ip",这两个都可以拿到访问者的真实ip
log_format main1 '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$http_x_real_ip"' ;
}
server {
access_log /var/log/nginx/access.log main1;
。。。其他部分都不动
}
七、四层负载均衡+七层负载均衡透传真实IP
四层负载均衡(192.168.71.11)—->七层负载均衡(192.168.71.12)—一>web服务器(192.168.71.14、192.168.71.15)
要想从四层负载均衡透传真实IP到七层负载均衡,我们先要明白四层负载均衡工作在传输层,只有TCP/UDP协议,这两种协议中只包含源IP、目标IP、源端口号及目的端口号,并不会携带http头信息,这样的话我们就无法定义X-Forwarded-For字段传递客户端真实IP,这就意味着用户的真实IP就会丢失,最后导致我们后端web节点获取到只会是四层负载均衡的IP。
要解决这个问题,我们需要在四层负载均衡上启用proxy协议,该协议通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),其本质是在三次握手结束后由代理在传输中插入了一个携带了原始连接元组信息的数据包。
4层负载均衡
cat > /etc/nginx/nginx.conf << EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
stream {
upstream lb_servers {
server 192.168.71.12:9090 max_fails=3 fail_timeout=5s;
server 192.168.71.13:9090 max_fails=3 fail_timeout=5s;
}
server {
listen 80;
proxy_pass lb_servers;
proxy_protocol on; # ---------》新增这一条:代表开启proxy protocol 协议
}
}
EOF
7层负载均衡
[root@lb01 /etc/nginx]# cat /etc/nginx/nginx.conf
events {
worker_connections 1024;
}
http {
upstream web_app_servers {
server 192.168.71.112:80 weight=1;
server 192.168.71.113:80 weight=1;
}
server {
listen 80 proxy_protocol; # 因为上游的四层开启了proxy协议,所以此处需要配合启用proxy_protocol的监听
set_real_ip_from 0.0.0.0/0; # 信任所有上游源,根据实际情况调整
real_ip_header proxy_protocol; # 将proxy_protocol获取的IP替换原$remote_addr值
location / {
proxy_pass http://web_app_servers;
# 增加下面一段
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
web服务器
http {
log_format main '日志格式末尾增加两个段 "$http_x_forwarded_for" "$http_x_real_ip"';
access_log /var/log/nginx/access.log main;
server {
# 新增下面一段
set_real_ip_from 0.0.0.0/0; # 信任所有上游源,根据实际情况调整
real_ip_header X-Forwarded-For; # 将X-Forwarded-For字段包含的IP替换原$remote_addr值
#real_ip_header X-Real-IP; # 将X-Real-IP字段包含的IP替换原$remote_addr值
real_ip_recursive on;
......
}