Keepalived-高可用
简介
- Keepalived软件起初是专门为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx,Haproxy,MySQL等)的高可用解决方案软件。
- Keepalived软件主要是通过VRRP协议实现高可用功能的。VRRP是Virtual Router Redundancy Protocol(虚拟路由器冗余协议)的缩写,VRRP出现的目的就是为了解决静态路由单点故障问题的,他能够保证当个别节点宕机时,整个网络可以不间断 地运行。所以,Keepalived一方面具有配置管理LVS的功能,同时还具有对LVS下面节点进行健康检查的功能,另一方面也可实现系统网络服务的高可用功能。
核心功能
高可用(HA)
通过 VRRP 协议实现主备节点的自动故障转移。当主节点(Master)故障时,备用节点(Backup)会接管虚拟 IP(VIP)和服务,确保业务连续性。典型应用包括 Nginx、HAProxy、数据库等服务的冗余部署
负载均衡
集成 LVS(Linux Virtual Server),支持四层负载均衡算法(如轮询、加权轮询等),并实时监控后端服务器的健康状态,自动剔除故障节点。
健康检查
支持多层(网络层、传输层、应用层)健康检测机制,例如 ICMP 探测、TCP 端口检查、HTTP/SSL 请求验证等,确保服务可用性
工作原理
VRRP 协议
Keepalived 通过 VRRP 将多台服务器虚拟成一个路由器组,选举出 Master 节点(优先级最高者)处理流量。Master 定期发送组播心跳(224.0.0.18),若 Backup 节点未收到心跳,则触发选举并接管 VIP
进程架构
运行时启动三个进程:
- 父进程:监控子进程;
- VRRP 子进程:处理 VRRP 协议状态;
- Checker 子进程:执行健康检查
Keepalived 故障转移原理
- VRRP也就是虚拟路由冗余协议,它的出现就是为了解决静态路由的单点故障。
- VRRP是通过一种竞选协议机制来将路由任务交给某台VRRP路由器的。
- VRRP用IP多播的方式(默认多播地址(224.0.0.18))实现高可用之间通信。
- 工作时主节点发包,备节点接包,当备节点接收不到主节点发的数据包的时候,就启动接
管程序接管主节点的资源。备节点可以有多个,通过优先级竞选,但一般Keepalived系统
运维工作中都是一对。
- VRRP使用了加密协议加密数据,但Keepalived官方目前还是推荐用明文的方式配置认证
类型和密码。
典型应用场景
- Web 服务高可用:如 Nginx 主备切换,避免单点故障
- 数据库主从切换:MySQL/PostgreSQL 的主节点故障自动转移
- 负载均衡集群:结合 LVS 构建高并发负载均衡系统
keepalived.conf 解析
# ===================== Keepalived 配置文件详解 =====================
# 文件名:/etc/keepalived/keepalived.conf
# --------------------- 全局配置 (global_defs) ---------------------
global_defs {
# 节点唯一标识符(集群内必须唯一)
router_id NODE_01
# 告警邮件配置(需本地邮件服务支持)
notification_email {
admin@example.com
ops@example.com
}
notification_email_from keepalived@example.com
# SMTP服务器配置
smtp_server smtp.example.com
smtp_connect_timeout 30
# VRRP严格模式(生产环境建议关闭)
vrrp_strict off
# 免费ARP报文发送间隔(VIP切换后更新ARP表)
vrrp_garp_interval 0.001
# 脚本安全设置(防止非root用户修改脚本)
enable_script_security
}
# --------------------- 健康检查脚本 (vrrp_script) ---------------------
vrrp_script chk_nginx {
# 健康检查脚本路径
script "/usr/local/bin/check_nginx.sh"
# 检查间隔(秒)
interval 2
# 失败时优先级调整值(-253~253)
weight -5
# 成功/失败次数阈值
rise 2 # 连续2次成功才认为健康
fall 2 # 连续2次失败才认为不健康
# 执行脚本的用户
user root
}
# --------------------- VRRP实例配置 (vrrp_instance) ---------------------
vrrp_instance VI_1 {
# 初始角色(实际由优先级决定)
state MASTER
# 绑定VIP的网络接口
interface eth0
# VRRP组ID(0-255,主备必须一致)
virtual_router_id 51
# 选举优先级(1-255,MASTER需更高)
priority 100
# 心跳间隔(秒)
advert_int 1
# 抢占模式配置
nopreempt # 非抢占模式(可选)
preempt_delay 300 # 抢占延迟时间(秒)
# 非抢占模式(BACKUP节点不主动抢占VIP)
nopreempt
# 认证配置(主备必须一致)
authentication {
auth_type PASS # 认证类型:PASS(密码)或 AH(IPSec)
auth_pass 12345678 # 密码(明文,最多8字符)
}
# 虚拟IP地址配置(可多个)
virtual_ipaddress {
192.168.1.100/24 dev eth0 label eth0:1 # VIP格式:IP/掩码 dev 网卡 [标签]
}
# 关联健康检查脚本
track_script {
chk_nginx
}
# 单播通信配置(网络禁用组播时使用)
unicast_src_ip 192.168.1.2 # 本机IP
unicast_peer {
192.168.1.3 # 对端IP
}
# 状态切换通知脚本
notify_master "/etc/keepalived/master.sh" # 切换为MASTER时执行
notify_backup "/etc/keepalived/backup.sh" # 切换为BACKUP时执行
notify_fault "/etc/keepalived/fault.sh" # 进入故障状态时执行
}
# --------------------- LVS虚拟服务器配置 (virtual_server) ---------------------
virtual_server 192.168.1.100 80 {
# 健康检查间隔(秒)
delay_loop 6
# 调度算法:rr(轮询)、wlc(加权最小连接)
lb_algo rr
# 转发模式:DR(直接路由)、NAT、TUN
lb_kind DR
# 协议类型
protocol TCP
# 持久化连接超时(秒)
persistence_timeout 60
# 真实服务器配置
real_server 192.168.1.101 80 {
# 服务器权重
weight 1
# TCP健康检查
TCP_CHECK {
connect_port 80 # 检查端口
connect_timeout 3 # 连接超时(秒)
retry 3 # 重试次数
delay_before_retry 2 # 重试间隔(秒)
}
# HTTP健康检查(可选)
HTTP_GET {
url {
path /healthz
status_code 200
}
connect_timeout 5
}
}
# 第二个真实服务器
real_server 192.168.1.102 80 {
weight 2
TCP_CHECK {
connect_port 80
connect_timeout 3
}
}
}
# --------------------- 高级配置示例 ---------------------
# 多VRRP实例配置(实现多VIP)
vrrp_instance VI_2 {
state BACKUP
interface eth1
virtual_router_id 52
priority 90
virtual_ipaddress {
10.0.0.100/24 dev eth1
}
}
# ===================== 关键配置说明 =====================
# 1. 主备节点必须一致的配置:
# - virtual_router_id
# - authentication
# - advert_int
#
# 2. 防火墙必须放行:
# - VRRP协议(协议号112)
# - 组播地址224.0.0.18
#
# 3. 健康检查脚本:
# - 需赋予可执行权限(chmod +x)
# - 返回0表示健康,非0表示不健康
#
# 4. 避免脑裂:
# - 使用单播通信(unicast_src_ip/unicast_peer)
# - 部署第三方仲裁脚本关键字段说明表
| 配置块 | 关键字段 | 说明 | 示例值 |
|---|---|---|---|
| global_defs | router_id | 节点唯一标识 | NODE_01 |
vrrp_strict | 严格模式 | off(生产环境建议关闭) | |
vrrp_garp_interval | ARP更新间隔 | 0.001 | |
| vrrp_script | script | 健康检查脚本路径 | /usr/bin/check_nginx.sh |
interval | 检查间隔 | 2(秒) | |
weight | 优先级调整值 | -5(失败时降低优先级) | |
| vrrp_instance | state | 初始角色 | MASTER或 BACKUP |
virtual_router_id | VRRP组ID | 51(0-255) | |
priority | 选举优先级 | 100(MASTER), 90(BACKUP) | |
virtual_ipaddress | 虚拟IP配置 | 192.168.1.100/24 dev eth0 | |
unicast_src_ip | 单播本机IP | 192.168.1.2 | |
unicast_peer | 单播对端IP | 192.168.1.3 | |
| virtual_server | lb_algo | 调度算法 | rr(轮询), wlc(加权最小连接) |
lb_kind | 转发模式 | DR(直接路由), NAT, TUN | |
real_server | 后端服务器 | 192.168.1.101 80 | |
TCP_CHECK | TCP健康检查 | connect_port 80 |
keepalived 工作模式
抢占模式
工作原理
抢占模式是 Keepalived 的默认行为。当优先级更高的节点恢复在线时,它会立即尝试重新获取 VIP 控制权。
vrrp_instance VI_1 {
state MASTER
priority 100 # 主节点优先级
# 不设置 nopreempt 即为抢占模式
preempt_delay 0 # 立即抢占(默认)
}工作流程
- 主节点故障
- 备节点(优先级90)接管 VIP
- 原主节点恢复
- 原主节点立即发送 VRRP 通告
- 备节点检测到更高优先级节点
- 备节点释放 VIP
- 原主节点重新获得 VIP
sequenceDiagram
participant Master as 主节点 (priority 100)
participant Backup as 备节点 (priority 90)
participant VIP as 虚拟IP
Master->>VIP: 持有VIP (正常)
Master-->>Backup: VRRP心跳
Master->>x: 故障
Backup->>VIP: 检测到主节点故障,接管VIP
VIP-->>Backup: VIP绑定
Master->>o: 恢复
Master->>Backup: 发送VRRP通告(priority 100)
Backup->>VIP: 释放VIP
Master->>VIP: 重新获取VIP特点
| 特性 | 说明 |
|---|---|
| 切换速度 | 快(毫秒级) |
| VIP 稳定性 | 可能频繁切换 |
| 适用场景 | 主节点性能明显优于备节点 |
| 配置要求 | 默认模式,无需特殊配置 |
非抢占模式
工作原理
启用 nopreempt参数后,即使主节点恢复,也不会主动抢回 VIP 控制权。
vrrp_instance VI_1 {
state BACKUP # 建议所有节点设为BACKUP
priority 100 # 主节点初始优先级
nopreempt # 关键参数:启用非抢占模式
preempt_delay 300 # 延迟时间(可选)
}工作流程
- 主节点故障
- 备节点接管 VIP
- 原主节点恢复
- 原主节点不会立即抢占 VIP
- 备节点继续持有 VIP
- 需要手动干预或等待备节点故障才会切换
sequenceDiagram
participant Master as 主节点 (priority 100)
participant Backup as 备节点 (priority 90)
participant VIP as 虚拟IP
Master->>VIP: 持有VIP (正常)
Master-->>Backup: VRRP心跳
Master->>x: 故障
Backup->>VIP: 检测到主节点故障,接管VIP
VIP-->>Backup: VIP绑定
Master->>o: 恢复
Master-->>Backup: 发送VRRP通告(priority 100)
Backup->>Master: 忽略(非抢占模式)
Backup->>VIP: 继续持有VIP特点
| 特性 | 说明 |
|---|---|
| 切换速度 | 故障切换快,恢复切换慢 |
| VIP 稳定性 | 高(VIP 不会频繁移动) |
| 适用场景 | 稳定服务优先于快速恢复 |
| 配置要求 | 必须设置 nopreempt |
混合模式:延迟抢占
工作原理
结合抢占模式和非抢占模式的优点,设置抢占延迟时间。
vrrp_instance VI_1 {
state BACKUP
priority 100
# 不设置 nopreempt
preempt_delay 300 # 等待300秒后再抢占
}工作流程
- 主节点故障
- 备节点接管 VIP
- 原主节点恢复
- 原主节点等待
preempt_delay秒 - 延迟结束后,原主节点尝试重新获取 VIP
适用场景
- 需要主节点提供服务,但允许短暂延迟
- 避免服务刚恢复就立即切换导致的不稳定
- 典型配置:
preempt_delay 300(5分钟)
模式对比表
| 特性 | 抢占模式 | 非抢占模式 | 延迟抢占 |
|---|---|---|---|
| 配置参数 | 默认 | nopreempt | preempt_delay |
| 主节点恢复行为 | 立即抢占 | 不抢占 | 延迟后抢占 |
| VIP稳定性 | 低 | 高 | 中 |
| 故障切换速度 | 快 | 快 | 快 |
| 恢复切换速度 | 快 | 慢(需手动) | 可配置 |
| 适用场景 | 主节点性能优先 | 服务稳定性优先 | 平衡稳定与恢复 |
| 典型配置 | 数据中心内部 | 生产环境 | 大多数场景 |
脑裂
什么是脑裂?
在高可用(HA)系统中,当主备节点之间的心跳线断开时,双方无法检测到对方状态,均认为自身是主节点,导致系统分裂为两个独立部分。此时:
- 双方可能同时抢占共享资源(如VIP),造成服务冲突。
- 若涉及有状态服务(如数据库),可能导致数据损坏(如双写冲突)
脑裂产生的原因
网络问题
- 心跳线故障(断线、老化、网卡/交换机故障)。
- 防火墙阻挡VRRP协议(如未放行组播地址
224.0.0.18)。 - 网卡配置错误或IP冲突。
配置错误
- 主备节点
virtual_router_id不一致。 - 心跳间隔(
advert_int)或超时时间设置不合理。
- 主备节点
其他因素
- 仲裁节点故障(若使用第三方仲裁)。
- Keepalived软件Bug或资源竞争。
脑裂的解决方案
冗余心跳线路
- 同时使用以太网和串行电缆,避免单点故障。
配置优化
- 确保主备节点
virtual_router_id、auth_pass一致。 - 启用非抢占模式(
nopreempt),避免主节点恢复后重新抢占VIP。
第三方仲裁
通过脚本定期检测网关或参考IP,若无法连通则自动降级或重启Keepalived:
#!/bin/bash
# 双检测仲裁脚本(业务端口+网关)
VIP="192.168.1.100"
GATEWAY="192.168.1.1"
PORT=80 # 业务服务端口
FAIL_COUNT_THRESHOLD=3
LOG_FILE="/var/log/keepalived_arbiter.log"
check_service() {
# 检测本地业务端口是否活跃
if ! nc -z localhost $PORT &>/dev/null; then
return 1
fi
return 0
}
check_gateway() {
if ! ping -c 2 -W 3 $GATEWAY &>/dev/null; then
return 1
fi
return 0
}
LOG_FILE="/var/log/check_nginx.log"
function log() {
local level="${2:-INFO}" # 默认日志级别
echo "[$(date '+%F %T')] [$level] $1" | tee -a "$LOG_FILE"
}
if ip addr show eth0 | grep -q $VIP; then
FAIL_COUNT=0
while [ $FAIL_COUNT -lt $FAIL_COUNT_THRESHOLD ]; do
if ! check_gateway || ! check_service; then
FAIL_COUNT=$((FAIL_COUNT + 1))
log "检测失败: 网关或端口不可达 (次数: $FAIL_COUNT)" "ERROR"
else
break
fi
sleep 5
done
if [ $FAIL_COUNT -ge $FAIL_COUNT_THRESHOLD ]; then
log "仲裁生效: 停止Keepalived"
systemctl stop keepalived
fi
fiKeepalived 高可用Nginx
环境准备
| 服务 | IP | 说明 |
|---|---|---|
| lb-master | 192.168.148.210 | Keepalived主服务器(Nginx主负载均衡器) |
| lb-backup | 192.168.148.211 | Keepalived备服务器(Nginx备负载均衡器) |
| web-01 | 192.168.148.212 | web01服务器 |
| web-02 | 192.168.148.213 | web02服务器 |
| VIP | 192.168.148.200 | 虚拟VIP地址 |
安装Nginx服务
所有服务器安装
dnf -y install nginx
hostname -I | awk '{print $1}' > /usr/shar/nginx/html/index.html
systemctl start nginx配置负载均衡
在lb-master和lb-backup修改/etc/nginx/nginx.conf,修改完重新加载nginx
http {
upstream backend {
# 默认轮询策略
server 192.168.148.210:80;
server 192.168.148.211:80;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
}在nginx配置中添加健康检查接口
cat > /etc/nginx/default.d/health.conf << "EOF"
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
EOF部署keepalived
在lb-master和lb-backup安装
安装
dnf -y install keepalived
#
useradd -Ms /sbin/nologin keepalived_script
# 创建存放脚目录
mkdir -p /opt/scripts /var/log/keepalived
chown keepalived_script:keepalived_script /var/log/keepalived
# 设置sudo权限
echo "keepalived_script ALL=(root) NOPASSWD: /usr/bin/systemctl stop keepalived, /usr/bin/systemctl kill keepalived" > /etc/sudoers.d/keepalived_script
chmod 440 /etc/sudoers.d/keepalived_scriptlb-master
cat > /etc/keepalived/keepalived.conf << "EOF"
! Configuration File for keepalived
global_defs {
router_id LB_01 # 不能和其他Keepalived节点相同(全局唯一)
enable_script_security
script_user keepalived_script
}
vrrp_script chk_nginx { # 自定义健康检查脚本
script "/opt/scripts/check_nginx.sh" # 脚本路径(需可执行权限)
interval 2 # 检查间隔(秒)
weight -5 # 失败时优先级降低值(-253~253)
rise 2 # 成功次数阈值(连续成功2次才认为健康)
fall 3 # 失败次数阈值(连续失败3次才认为不健康)
user keepalived_script
}
vrrp_instance VI_1 {
state BACKUP # 主备都设置BACKUP
interface ens32 # 通信(心跳)接口为ens32,此参数备节点设置和主节点相同
virtual_router_id 51 # 实例ID为51,要和备节点相同
priority 100 # 优先级(1-255)master节点高于backup节点
advert_int 1
#nopreempt
preempt_delay 60 # 1分钟延迟
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.148.200/24 dev ens32 label ens32:1 # VIP配置
}
track_script { # 关联健康检查脚本
chk_nginx
}
# 脑裂检测脚本
notify_master "/opt/scripts/check_split_brain.sh"
}
EOFlb-backup
cat > /etc/keepalived/keepalived.conf << "EOF"
! Configuration File for keepalived
global_defs {
router_id LB_02 # 不能和其他Keepalived节点相同(全局唯一)
enable_script_security
script_user keepalived_script
}
vrrp_script chk_nginx { # 自定义健康检查脚本
script "/opt/scripts/check_nginx.sh" # 脚本路径(需可执行权限)
interval 2 # 检查间隔(秒)
weight -5 # 失败时优先级降低值(-253~253)
rise 2 # 成功次数阈值(连续成功2次才认为健康)
fall 3 # 失败次数阈值(连续失败3次才认为不健康)
user keepalived_script
}
vrrp_instance VI_1 {
state BACKUP # 主备都设置BACKUP
interface ens32 # 通信(心跳)接口为ens32,此参数备节点设置和主节点相同
virtual_router_id 51 # 实例ID为51,要和备节点相同
priority 90 # 优先级(1-255)master节点高于backup节点
advert_int 1
#nopreempt
preempt_delay 60 # 1分钟延迟
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.148.200/24 dev ens32 label ens32:1 # VIP配置
}
track_script { # 关联健康检查脚本
chk_nginx
}
# 脑裂检测脚本
notify_master "/opt/scripts/check_split_brain.sh"
}
EOF检测脚本
环境准备
# 需要安装sshpass进行免密免指纹操作
dnf -y install sshpass仲裁脚本
cat > /opt/scripts/check_split_brain.sh << "EOF"
#!/bin/bash
# 脑裂检测脚本
REMOTE_IP="192.168.148.210" # 对端IP
VIP="192.168.148.200"
LOG_FILE="/var/log/keepalived/check_split_brain.log"
# 日志函数
function log() {
local level="${2:-INFO}" # 默认日志级别
echo "[$(date '+%F %T')] [$level] $1" | tee -a "$LOG_FILE"
}
# 检查对端是否也持有VIP
ping -c 1 -W 1 $REMOTE_IP >/dev/null 2>&1
if [ $? -eq 0 ]; then
# 对端可达,检查是否也绑定了VIP
sshpass -p "123456" ssh -o ConnectTimeout=2 -o StrictHostKeyChecking=no root@$REMOTE_IP \
"ip addr show | grep $VIP" >/dev/null 2>&1
if [ $? -eq 0 ]; then
log "发现脑裂!立即释放VIP" "ERROR"
sudo -n /usr/bin/systemctl stop keepalived 2>&1
# 发送告警通知
# curl -X POST "your_alert_webhook" -d "Split brain detected on $(hostname)"
exit 1
fi
fi
log "未发现脑裂,集群正常"
exit 0
EOF
chmod +x /opt/scripts/check_split_brain.sh
chown keepalived_script:keepalived_script /opt/scripts/check_split_brain.sh检测Nginx服务脚本
cat > /opt/scripts/check_nginx.sh << "EOF"
#!/bin/bash
VIP="192.168.148.200"
CHECK_URL="http://127.0.0.1/health"
LOG_FILE="/var/log/keepalived/check_nginx.log"
SERVER_PORT="80"
# 日志函数
function log() {
local level="${2:-INFO}" # 默认日志级别
echo "[$(date '+%F %T')] [$level] $1" | tee -a "$LOG_FILE"
}
function notify(){
local level=${1:-"INFO"}
# 发送告警通知
# curl -X POST "your_alert_webhook" -d "Split brain detected on $(hostname)"
if [ "$level" = "ERROR" ];then
sudo -n /usr/bin/systemctl stop keepalived 2>&1
sleep 2
if systemctl is-active --quiet keepalived; then
log "无法停止keepalived,尝试强制停止" "CRITICAL"
sudo -n /usr/bin/systemctl kill --signal=SIGKILL keepalived
fi
fi
}
# 检查进程是否存在
if ! systemctl is-active --quiet nginx; then
log "Nginx 服务未运行" "ERROR"
notify "ERROR"
exit 1
fi
# 检查端口是否监听
ss -tlnp | grep ":$SERVER_PORT " >/dev/null 2>&1
if [ $? -ne 0 ]; then
log "Nginx port $SERVER_PORT 无法请求"
notify "ERROR"
exit 1
fi
# 真实HTTP请求检查(关键)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 5 $CHECK_URL)
if [ "$HTTP_CODE" != "200" ]; then
log "Nginx 健康检查失败, HTTP code: $HTTP_CODE"
# 尝试重启nginx
systemctl restart nginx
sleep 2
# 再次检查
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 5 $CHECK_URL)
if [ "$HTTP_CODE" != "200" ]; then
log "Nginx 重启检查失败"
notify "ERROR"
exit 1
fi
fi
# 检查系统资源
LOAD=$(awk '{print $1}' /proc/loadavg)
if (( $(echo "$LOAD > 10" | bc -l) )); then
log "系统负载过高: $LOAD" "WARING"
notify "WARING"
exit 1
fi
# 检查内存使用
MEM_USAGE=$(awk '/MemTotal/{total=$2}/MemAvailable/{avail=$2}END{printf("%.2f", (total-avail)/total*100)}' /proc/meminfo)
if (( $(echo "$MEM_USAGE > 90" | bc -l) )); then
log "内存使用过高: $MEM_USAGE%" "WARING"
notify "WARING"
exit 1
fi
log "Nginx 健康检查正常"
exit 0
EOF
chmod +x /opt/scripts/check_nginx.sh
chown keepalived_script:keepalived_script /opt/scripts/check_nginx.sh启动服务
systemctl start keepalived
systemctl enable keepalived高可用测试
# 查看lb-master服务器的VIP信息
ip a
# 请求VIP地址检测高可用
curl 192.168.148.200
# 停止lb-master服务器keepalived服务模拟宕机
systemctl stop kepalived
# 再次请求VIP地址检测高可用是否可用
curl 192.168.148.200