服务器高可用架构中,Nginx作为反向代理承担流量入口职责,单点故障会导致整个后端服务不可用。Keepalived通过VRRP协议实现虚拟IP(VIP)在多台Nginx节点间漂移,当主节点宕机时,备节点在秒级接管VIP,实现流量无缝切换。本文记录一套双机热备Nginx集群的完整搭建过程,涵盖安装、配置、健康检查、故障切换验证和日志排查。
VRRP协议原理与双机热备架构设计
VRRP(Virtual Router Redundancy Protocol)将多台物理服务器虚拟为一个逻辑路由器组,组内选举一个Master节点持有VIP,其余为Backup节点。Master周期性发送VRRP通告包,Backup节点在约定时间内未收到通告即认为Master宕机,按优先级选举新Master并接管VIP。整个切换过程对客户端透明,因为VIP地址不变。
双机热备架构:两台Nginx服务器分别配置为VRRP Master和Backup,共享一个VIP对外提供服务。Nginx通过upstream模块将请求分发到后端应用服务器集群。Keepalived不仅管理VIP漂移,还通过vrrp_script模块持续检测Nginx进程存活状态,一旦检测到Nginx进程退出,主动降低自身优先级触发VIP切换。
Keepalived安装与环境准备
以CentOS Stream 9为例,通过EPEL仓库安装Keepalived:
dnf install -y epel-release
dnf install -y keepalived nginx psmisc
keepalived -v
nginx -v
# 开放VRRP协议端口(IP协议号112)
firewall-cmd --add-rich-rule='rule protocol value="vrrp" accept' --permanent
firewall-cmd --add-service=http --permanent
firewall-cmd --add-service=https --permanent
firewall-cmd --reload
网络规划:主节点IP 192.168.10.11,备节点IP 192.168.10.12,VIP 192.168.10.100,后端应用服务器 192.168.20.21-23。两台Nginx服务器的配置必须保持一致,可通过rsync+inotify实现配置文件自动同步。
Keepalived核心配置文件详解
主节点(192.168.10.11)的/etc/keepalived/keepalived.conf:
global_defs {
router_id NGINX_MASTER
enable_script_security
script_user root
}
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -20
fall 2
rise 1
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass K8jN2mPq
}
virtual_ipaddress {
192.168.10.100/24 dev eth0
}
track_script {
check_nginx
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
备节点配置差异:router_id改为NGINX_BACKUP,state改为BACKUP,priority设为90。其余配置完全一致。
关键参数说明:priority 100为主节点优先级。weight -20表示健康检查脚本失败时,优先级降低20点,从100变为80,低于备节点的90,触发VIP漂移。fall 2表示连续2次检查失败才判定为故障。advert_int 1为VRRP通告间隔1秒,故障检测窗口约为2-3秒。
Nginx健康检查脚本编写
/etc/keepalived/check_nginx.sh脚本检测Nginx进程是否存活:
#!/bin/bash
NGINX_PID=$(pidof nginx)
if [ -z "$NGINX_PID" ]; then
systemctl restart nginx
sleep 2
NGINX_PID=$(pidof nginx)
if [ -z "$NGINX_PID" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Nginx is DOWN" >> /var/log/keepalived_nginx.log
exit 1
fi
fi
exit 0
赋予执行权限:chmod +x /etc/keepalived/check_nginx.sh。enable_script_security和script_user root确保脚本以root身份运行。
Nginx反向代理与upstream负载均衡配置
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 16384;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time uct=$upstream_connect_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
error_log /var/log/nginx/error.log warn;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
keepalive_requests 1000;
gzip on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml;
upstream backend_app {
least_conn;
server 192.168.20.21:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.20.22:8080 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.20.23:8080 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.20.24:8080 backup;
keepalive 32;
}
server {
listen 80;
server_name _;
location /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
location / {
proxy_pass http://backend_app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_next_upstream error timeout invalid_header http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
location /ws {
proxy_pass http://backend_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}
}
least_conn策略将请求分发到当前连接数最少的后端服务器。max_fails 3 fail_timeout=30s表示30秒内失败3次的服务器被标记为不可用。proxy_next_upstream配置了当后端返回502/503或超时时,自动重试下一台服务器。
故障切换验证与日志排查
# 主节点启动
systemctl enable keepalived nginx
systemctl start nginx
systemctl start keepalived
# 验证VIP绑定在主节点
ip addr show eth0 | grep 192.168.10.100
# 预期输出: inet 192.168.10.100/24 scope global secondary eth0
# 备节点启动(不应持有VIP)
ip addr show eth0 | grep 192.168.10.100
# 预期无输出
# 模拟主节点Nginx故障
killall nginx
# 等待2-3秒
# 主节点日志: VRRP_Instance(VI_1) Entering FAULT STATE
# 备节点日志: VRRP_Instance(VI_1) Entering MASTER STATE
# 在备节点验证VIP已漂移
ip addr show eth0 | grep 192.168.10.100
# 预期输出: inet 192.168.10.100/24 scope global secondary eth0
脑裂问题诊断与防御方案
脑裂(Split-Brain)是双机热备最危险的故障模式:主备节点间的VRRP通告因网络分区中断,双方都认为对方故障,各自提升为Master并绑定VIP。诊断方法:检查两台服务器是否同时持有VIP。
防御方案:通过独立心跳链路和fencing机制。在两台Nginx服务器之间增加一条直连网线作为VRRP专用心跳通道:
vrrp_instance VI_1 {
state MASTER
interface eth0
lvs_sync_daemon_interface eth1
virtual_router_id 51
priority 100
advert_int 1
preempt_delay 10
garp_master_delay 5
garp_master_repeat 3
authentication {
auth_type PASS
auth_pass K8jN2mPq
}
virtual_ipaddress {
192.168.10.100/24 dev eth0
}
track_script { check_nginx }
track_interface { eth0 eth1 }
}
track_interface监控网络接口状态,当eth0或eth1 down时触发优先级调整。preempt_delay 10设置恢复后延迟10秒再抢占,避免频繁切换。
Keepalived notify脚本实现告警通知
#!/bin/bash
STATE=$1
HOSTNAME=$(hostname)
VIP="192.168.10.100"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
case $STATE in
master) MSG="${DATE} [${HOSTNAME}] 切换为MASTER,接管VIP ${VIP}" ;;
backup) MSG="${DATE} [${HOSTNAME}] 切换为BACKUP,释放VIP ${VIP}" ;;
fault) MSG="${DATE} [${HOSTNAME}] 进入FAULT状态" ;;
*) MSG="${DATE} [${HOSTNAME}] 未知状态: ${STATE}" ;;
esac
echo "$MSG" >> /var/log/keepalived_notify.log
curl -s -X POST \
"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${MSG}\"}}"
完成以上配置后,故障切换时间可控制在3秒以内,满足大多数业务场景的连续性要求。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/nginx-fan-xiang-dai-li-gao-ke-yong-ji-qun-da-jian/