单台 Nginx 服务器承载全部流量,硬件故障、内核 panic、磁盘写满、误杀进程任意一件事发生,网站就会整体不可用。很多运维新手第一次遇到这种情况时,才意识到"重启就好"并不成立,因为服务器可能根本进不去。要解决这个问题,思路是把入口层做成两台甚至多台机器组成的集群,对外只暴露一个虚拟 IP,任何一台 宕机后流量自动转移到另一台。
这篇文章用一个可以照着做的双机主备实验,讲清楚 Linux 高可用集群的落地方法:怎么规划虚拟 IP,怎么在主备两台机器上写 keepalived.conf,怎么用健康检查脚本判断 Nginx 是否还活着,怎么让 VIP 自动漂移,以及怎么验证切换真的生效。全程使用真实可执行的命令,并区分 RHEL/CentOS/Rocky 与 Debian/Ubuntu 两套发行版。文中出现的主机名、IP 和密码都要替换成你自己的环境值。
高可用集群要解决什么问题
高可用(High Availability,简称 HA)的目标不是让机器永远不坏,而是让"服务中断时间"尽可能短。业界常说的几个 9,指的是全年可用率:99% 大约一年停 3.65 天,99.9% 约 8.76 小时,99.99% 约 52 分钟,99.999% 不足 6 分钟。双机主备加自动切换,通常能把单点故障造成的停机从"小时级"压到"分钟级"甚至"秒级"。
实现方式上有几个层次需要分清楚。最基础的是冗余部署,两台机器跑同样的服务,但只有一台对外服务,另一台待命。再往上是故障检测,需要有一个机制持续判断当前主节点是否健康,检测手段可以是进程检查、端口检查、HTTP 探测或者心跳报文。最后是故障转移,检测到异常后把对外地址、流量或负载均衡配置切换到备节点。
在 Linux 生态里,最常用的组合是 Keepalived 加 Nginx。Keepalived 负责虚拟 IP 的管理和漂移,Nginx 负责实际的七层反向代理。两者职责清晰,配置简单,适合中小企业、个人站长的双机高可用场景。规模更大、对一致性要求更高的场景,会用到 Pacemaker 加 Corosync 这类更重的集群资源管理器,但学习和维护成本也更高。
需要提醒的是,双机主备解决的是"入口"和"无状态应用层"的高可用。如果后端只有一台数据库,数据库仍然是单点,前端切换得再快也救不了数据层。完整的高可用要一层一层从入口到数据逐层消除单点,这篇文章先把入口层讲透。
实验规划:IP、主机名与拓扑
动手之前先把地址规划清楚,避免写到一半发现 IP 冲突。示例环境如下,请全部替换为你自己的值:
| 角色 | 主机名 | 物理网卡 IP | 虚拟 IP | 说明 |
|---|---|---|---|---|
| 主节点 MASTER | node1 | 192.0.2.11 | 192.0.2.100 | 正常时持有 VIP |
| 备节点 BACKUP | node2 | 192.0.2.12 | 192.0.2.100 | 主节点故障时接管 VIP |
| 后端应用 | app1 | 192.0.2.21 | — | Nginx 反向代理的目标 |
虚拟 IP(VIP,也叫浮动 IP)不属于物理网卡,平时配置在主节点上,主节点故障时由备节点通过 ARP 广播宣布"这个 IP 现在归我",交换机据此更新转发。VIP 需要与物理网卡在同一网段且未被占用,动手前务必用 ping 和 arping 确认它没有被人使用。示例中的 192.0.2.0/24 是文档保留网段,真实环境请用你的内网地址。
两台机器都要安装 Nginx 与 Keepalived。RHEL/CentOS/Rocky 系(Rocky 8/9、CentOS Stream 8/9、RHEL 8/9)使用 dnf,较老的 CentOS 7 用 yum:
# RHEL / CentOS / Rocky 系
sudo dnf install -y nginx keepalived
sudo systemctl enable --now nginx
Debian/Ubuntu 系(Debian 11/12、Ubuntu 20.04/22.04/24.04)使用 apt:
# Debian / Ubuntu 系
sudo apt update
sudo apt install -y nginx keepalived
sudo systemctl enable --now nginx
安装后用 systemctl status 确认两个服务都在运行。如果 Nginx 默认站点占用了 80 端口,后面配置反向代理时要注意端口与站点冲突。
配置后端应用与 Nginx 反向代理
为了让验证结果可见,先在 app1 上准备一个能返回主机名的简单页面。这里用 Nginx 直接返回静态内容:
# app1: /etc/nginx/conf.d/backend.conf
server {
listen 8080;
server_name _;
location / {
default_type text/plain;
return 200 "backend app1 is serving\n";
}
}
在 app1 上执行 sudo nginx -t && sudo systemctl reload nginx 后,用 curl http://127.0.0.1:8080/ 确认返回正常。然后回到 node1 和 node2,配置反向代理指向后端。注意生成的 HTTP 头要带上真实客户端信息:
# node1 与 node2: /etc/nginx/conf.d/proxy.conf
server {
listen 80;
server_name _;
location / {
proxy_pass http://192.0.2.21:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
两台代理节点配置必须完全一致。配置好后 sudo nginx -t 检查语法,sudo systemctl reload nginx 平滑加载。此时分别在两台机器上 curl http://127.0.0.1/ 应当都能看到后端返回的内容。如果失败,先排查是 Nginx 没起、还是到后端 8080 的网络不通。
编写 keepalived.conf 主备配置
Keepalived 的核心配置在 /etc/keepalived/keepalived.conf。主节点和备节点大部分内容相同,只有 state、priority 和 router_id 三处不一样。先看主节点:
# node1 (MASTER): /etc/keepalived/keepalived.conf
global_defs {
router_id node1 # 每台机器唯一,通常用主机名
enable_script_security # 脚本以非 root 运行更安全(视版本支持)
}
vrrp_script chk_nginx {
script "/etc/keepalived/chk_nginx.sh"
interval 2 # 每 2 秒检查一次
weight -20 # 检查失败时优先级减 20
fall 2 # 连续 2 次失败才判定异常
rise 2 # 连续 2 次成功才恢复
}
vrrp_instance VI_1 {
state MASTER
interface eth0 # 改成你的实际网卡名,用 ip a 查看
virtual_router_id 51 # 主备必须一致,0-255
priority 100 # 主节点数值应高于备节点
advert_int 1 # VRRP 通告间隔,单位秒
authentication {
auth_type PASS
auth_pass ReplaceWithStrongPass # 主备必须一致,最长 8 字符
}
track_script {
chk_nginx
}
virtual_ipaddress {
192.0.2.100/24 # 虚拟 IP,主备必须一致
}
}
备节点只需改三个值:
# node2 (BACKUP): /etc/keepalived/keepalived.conf
global_defs {
router_id node2
enable_script_security
}
vrrp_script chk_nginx {
script "/etc/keepalived/chk_nginx.sh"
interval 2
weight -20
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90 # 必须低于主节点的 100
advert_int 1
authentication {
auth_type PASS
auth_pass ReplaceWithStrongPass
}
track_script {
chk_nginx
}
virtual_ipaddress {
192.0.2.100/24
}
}
几个参数需要重点理解。state 只决定初始角色,真正决定谁持有 VIP 的是 priority 和抢占策略。virtual_router_id 是 VRRP 组的编号,如果同网段有多个 Keepalived 集群,编号必须错开,否则会互相干扰。auth_pass 是明文简单认证,只防止误配置,不防攻击,所以不要在不可信网络里裸奔 VRRP。网卡名一定要用 ip a 查到的真实名字,现在很多云主机是 ens33、eth0 之外的命名。
健康检查脚本与通知脚本
track_script 引用的脚本要自己创建。它的核心作用是检查 Nginx 进程或端口是否还活着,返回码为 0 表示健康,非 0 表示异常。下面这个脚本同时检查进程和端口:
# 两台机器: /etc/keepalived/chk_nginx.sh
#!/usr/bin/env bash
# 检查 nginx 进程存在且 80 端口在监听,二者有一个不满足即视为异常
if ! pgrep -x nginx >/dev/null 2>&1; then
exit 1
fi
if ! ss -lnt | grep -q ':80 '; then
exit 1
fi
exit 0
赋予执行权限并确保 root 可读:
sudo chmod 700 /etc/keepalived/chk_nginx.sh
sudo chown root:root /etc/keepalived/chk_nginx.sh
逻辑是这样:正常情况下主节点 priority 为 100,备节点为 90,主节点持有 VIP。当主节点的 Nginx 挂掉,检查脚本连续两次失败,weight -20 使主节点优先级降到 80,低于备节点的 90,备节点随即抢占并接管 VIP。这样一个纯应用故障也能触发漂移,而不必等到整机宕机。
如果需要记录切换事件、发送告警,可以加 notify 脚本。Keepalived 支持 notify_master、notify_backup、notify_fault、notify_stop 等多个钩子,在 vrrp_instance 内声明:
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
对应的 notify.sh 可以用 $1 接收状态参数,写入日志或调用你的告警接口。注意脚本要短、要幂等,不要在通知脚本里做耗时操作,否则会拖慢状态机。
启动服务并验证虚拟 IP
配置写完先做语法检查,再启动。Keepalived 提供 -t 参数校验配置:
sudo keepalived -t -f /etc/keepalived/keepalived.conf
没有报错后,两台机器都启动并设置开机自启:
sudo systemctl enable --now keepalived
sudo systemctl status keepalived --no-pager
用 ip a 在主节点上应当看到网卡多了一个 192.0.2.100 地址,备节点上没有。用 ip addr show eth0 | grep 192.0.2.100 可以快速确认。验证 VIP 是否整条链路可用:
# 从任意一台同网段机器或客户端执行
curl http://192.0.2.100/
如果能看到后端返回的内容,说明"客户端 → VIP → 主节点 Nginx → 后端应用"这条链路已经打通。此时可以再用 arping 确认 VIP 的 MAC 指向主节点网卡。
用停服务的方式验证切换
真正的验证是让故障发生。最安全的方式是停掉主节点的 Keepalived,观察 VIP 是否漂移到备节点。先在备节点上打开一个窗口持续观察地址变化:
# node2 上持续查看,只显示 VIP 相关行
watch -n 1 "ip -4 addr show eth0 | grep 192.0.2.100"
然后在主节点执行:
sudo systemctl stop keepalived
正常情况下,1 到 3 秒内备节点的 ip a 会出现 VIP,主节点上的 VIP 消失。此时再用 curl http://192.0.2.100/,服务应当继续可用。之后在主节点重新启动:
sudo systemctl start keepalived
由于主节点优先级更高且默认开启抢占,VIP 会在几秒后自动回到主节点。要验证应用层故障切换,可以改停 Nginx 而不是 Keepalived:
# 风险提示:这会让本机 80 端口短暂不可用,请确认在测试窗口执行
sudo systemctl stop nginx
由于 track_script 生效,主节点优先级下降,备节点接管 VIP。恢复时执行 sudo systemctl start nginx,主节点优先级回升后夺回 VIP。
防火墙、单播与常见坑
在 RHEL/CentOS/Rocky 系上,Keepalived 使用 VRRP 协议,协议号是 112。firewalld 默认可能直接放行,但如果收紧了策略,需要显式放行:
# RHEL / CentOS / Rocky 系
sudo firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
sudo firewall-cmd --reload
Debian/Ubuntu 系如果启用了 ufw,通常默认放行 VRRP,但若收紧了规则也可显式允许:
# Debian / Ubuntu 系(ufw 一般无需额外放行 VRRP)
sudo ufw status verbose
很多云环境不支持 VRRP 组播(224.0.0.18),这时主备互相收不到通告,可能各自都认为自己是主,出现"双主"。解决办法是改用单播,在主备配置里显式指定对端地址:
unicast_src_ip 192.0.2.11 # 本机物理 IP
unicast_peer {
192.0.2.12 # 对端物理 IP
}
单播模式只向指定对端发通告,更适合云主机和跨网段场景。切记 unicast_src_ip 必须填本机物理网卡地址,不能填 VIP。
另外几个高频坑:virtual_router_id 冲突会导致两个无关集群互相抢 VIP;auth_pass 主备不一致会导致认证失败、无法形成主备;网卡名写错会导致 Keepalived 起不来;同一网段里 VIP 没规划好、被别人占用会导致地址冲突和断网。改动任何一项都要先在测试环境验证。
常见问题
启动 keepalived 后主备都没有 VIP,怎么排查?
先看 systemctl status keepalived 和 journalctl -u keepalived 的报错,常见原因是配置语法错误、网卡名不存在、auth_pass 不一致。再用 tcpdump -i eth0 vrrp 抓包,确认两端能收到 VRRP 通告。
主备同时持有 VIP,出现双主怎么办?
双主多半是通告不通引起的,比如云平台屏蔽了 VRRP 组播或者防火墙拦了 112 协议。改用 unicast 单播模式,并检查两端物理地址互通、virtual_router_id 一致。
停掉主的 Nginx,为什么 VIP 没有切换?
先确认从节点确实能收到通告、track_script 脚本返回码正确、weight 权重足够让优先级低于备节点。可以手动执行 /etc/keepalived/chk_nginx.sh; echo $? 验证脚本逻辑。
VIP 切换后客户端访问很慢或偶发失败?
可能是 ARP 缓存未及时更新。可以在客户端查看 ARP 表,必要时清缓存;也可以在 Keepalived 中配置 garp_master_delay 让主节点更快地发送免费 ARP 通告。
这套方案能保证数据库高可用吗?
不能。Keepalived 加 Nginx 只解决入口和代理层。数据库要做主从复制加故障切换,属于另一套机制,需要单独设计。
总结
- 高可用入口层的基本形态是"双机 + 虚拟 IP + 自动切换",Keepalived 管 VIP,Nginx 管代理,两者职责分离。
- 主备配置的关键差异只有
state、priority、router_id;virtual_router_id、auth_pass、VIP 必须两端完全一致。 track_script让应用故障也能触发漂移,脚本要短、要幂等,并赋予正确的权限。- 验证不能只看服务是否启动,必须实际停服务、停 Keepalived,观察 VIP 是否在秒级内漂移并恢复。
- 云环境优先用单播模式,注意防火墙对 VRRP 协议 112 的放行,谨防双主和地址冲突。
企业QQ咨询




