Linux高可用集群怎么搭建?Keepalived与Nginx实战解析

单台 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 的放行,谨防双主和地址冲突。