Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Linux Keepalived虚拟IP故障转移

Linux Keepalived配置虚拟IP实现故障转移的全过程

作者:知远漫谈

在现代分布式系统架构中,高可用性是保障服务持续在线、抵御单点故障的核心能力,在 Linux 环境下,Keepalived 是实现虚拟 IP漂移、完成主备切换的经典开源工具,本文将从零开始,带你一步步配置 Keepalived,结合 Java 应用演示真实场景下的高可用部署

引言

在现代分布式系统架构中,高可用性(High Availability, HA)是保障服务持续在线、抵御单点故障的核心能力。无论是电商平台、金融交易系统,还是企业内部的关键业务应用,一旦服务中断,轻则影响用户体验,重则造成经济损失甚至法律风险。因此,构建具备自动故障转移能力的系统架构,已成为每一位后端工程师和运维人员的必修课。

在 Linux 环境下,Keepalived 是实现虚拟 IP(Virtual IP, VIP)漂移、完成主备切换的经典开源工具。它基于 VRRP 协议(Virtual Router Redundancy Protocol),通过心跳检测机制,在主节点宕机时,自动将 VIP 迁移到备用节点,从而实现“无缝”故障转移。

本文将从零开始,带你一步步配置 Keepalived,结合 Java 应用演示真实场景下的高可用部署,并深入剖析其工作原理、常见问题及优化策略。无论你是刚接触运维的新手,还是希望提升系统稳定性的资深开发者,都能从中获得实用价值。

什么是 Keepalived?

Keepalived 最初设计用于 LVS(Linux Virtual Server)负载均衡器的高可用方案,但因其轻量、高效、配置简单,迅速被广泛应用于各类需要 VIP 漂移的场景,如数据库主从切换、Nginx 高可用、自定义服务冗余等。

核心特性:

VRRP 协议小科普
VRRP 是一种容错协议,允许多台路由器组成一个“虚拟路由器”,对外表现为一个统一的网关 IP(即 VIP)。当主路由器故障,备份路由器自动接管 VIP,继续提供服务。Keepalived 将这一思想扩展到任意 TCP/IP 服务上。

实验环境准备

为了便于演示,我们搭建一个最小化的双节点环境:

节点角色主机名IP 地址操作系统
Masternode-master192.168.1.100Ubuntu 22.04 LTS
Backupnode-backup192.168.1.101Ubuntu 22.04 LTS

虚拟 IP(VIP):192.168.1.200

注意:两台机器需在同一局域网内,且能互相 ping 通。防火墙需放行 VRRP 协议(协议号 112)或关闭防火墙进行测试。

安装 Keepalived

在两台机器上分别执行以下命令安装 Keepalived:

sudo apt update
sudo apt install -y keepalived

安装完成后,配置文件位于 /etc/keepalived/keepalived.conf。默认可能不存在,需手动创建。

配置 Keepalived(Master 节点)

编辑 Master 节点的配置文件:

sudo vim /etc/keepalived/keepalived.conf

内容如下:

vrrp_instance VI_1 {
    state MASTER           # 角色:主节点
    interface eth0         # 绑定网卡,根据实际修改(可用 ip a 查看)
    virtual_router_id 51   # 虚拟路由ID,主备必须一致
    priority 100           # 优先级,数值越大越优先成为主节点
    advert_int 1           # 心跳检测间隔(秒)

    authentication {       # 认证配置,主备需一致
        auth_type PASS
        auth_pass 123456
    }

    virtual_ipaddress {
        192.168.1.200/24 dev eth0  # 虚拟IP及其子网掩码、绑定网卡
    }
}

提示:interface 需替换为你机器的实际网卡名称,如 ens33、enp0s3 等。

配置 Keepalived(Backup 节点)

在 Backup 节点上创建相同的配置文件,仅修改三处:

vrrp_instance VI_1 {
    state BACKUP           # 角色改为 BACKUP
    interface eth0
    virtual_router_id 51
    priority 90            # 优先级低于 Master
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.1.200/24 dev eth0
    }
}

启动 Keepalived 服务

在两台机器上分别启动并设置开机自启:

sudo systemctl enable keepalived
sudo systemctl start keepalived
sudo systemctl status keepalived

正常情况下,你应该看到服务处于 active (running) 状态。

验证 VIP 是否生效

在 Master 节点上执行:

ip addr show eth0

你应该能看到类似输出:

inet 192.168.1.100/24 ... 
inet 192.168.1.200/24 scope global secondary eth0

说明 VIP 已成功绑定!

此时从局域网其他机器 ping 192.168.1.200,应能通:

ping 192.168.1.200

模拟故障转移

现在我们手动停止 Master 节点的 Keepalived:

sudo systemctl stop keepalived

等待几秒后,在 Backup 节点上再次执行:

ip addr show eth0

你会发现 VIP 192.168.1.200 已经出现在 Backup 节点上!

同时,ping 测试依然畅通,证明故障转移成功

重启 Master 节点的 Keepalived,若配置为抢占模式(默认),VIP 会自动切回 Master。

结合 Java 应用:构建高可用 HTTP 服务

光有 VIP 漂移还不够,我们需要让真正的业务服务——比如一个 Java Web 应用——也能随 VIP 自动切换访问目标。

为此,我们在两台机器上各部署一个简单的 Spring Boot 应用,监听本地端口(如 8080),并通过 VIP + Nginx 反向代理对外提供统一入口。

编写 Java 示例程序

使用 Spring Boot 创建一个返回主机名和当前时间的服务:

// 文件:HelloController.java
package com.example.ha;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        try {
            String hostname = InetAddress.getLocalHost().getHostName();
            return "Hello from " + hostname + " at " + LocalDateTime.now();
        } catch (UnknownHostException e) {
            return "Hello from unknown host at " + LocalDateTime.now();
        }
    }
}
// 文件:HaApplication.java
package com.example.ha;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HaApplication {
    public static void main(String[] args) {
        SpringApplication.run(HaApplication.class, args);
    }
}

打包成可执行 JAR:

./mvnw clean package

在两台机器上分别运行:

java -jar ha-demo.jar --server.port=8080

访问各自 IP 的 http://<IP>:8080/hello,应能看到不同主机名。

配置 Nginx 作为前端代理

为了让外部用户通过 VIP 访问服务,我们在两台机器上都安装 Nginx,并配置反向代理到本地 Java 应用。

安装 Nginx:

sudo apt install -y nginx

编辑配置文件:

sudo vim /etc/nginx/sites-available/default

替换为:

server {
    listen 80;
    server_name _;
    location / {
        proxy_pass http://127.0.0.1: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;
    }
}

重启 Nginx:

sudo systemctl restart nginx

现在,访问 http://192.168.1.200/hello,你将看到来自 Master 节点的响应。

停掉 Master 的 Keepalived,刷新页面,你会看到响应来源自动切换到了 Backup 节点!

恭喜!你已实现 Java 应用的高可用架构!

故障转移流程图解(mermaid)

下面用 mermaid 图表直观展示整个故障转移过程:

该图表清晰展示了客户端始终通过 VIP 访问服务,而底层节点故障对用户透明无感知。

高级配置技巧

1. 非抢占模式(nopreempt)

默认情况下,当原 Master 恢复后,会重新抢占 VIP。如果你希望“谁先启动谁为主”,可在 Master 和 Backup 配置中加入:

nopreempt

并确保 Master 的 priority 仍高于 Backup。

注意:nopreempt 只在 state BACKUP 时有效,所以即使配置在 Master 上,也建议统一设为 BACKUP + priority 控制主从。

2. 自定义健康检查脚本

有时我们希望在 Java 应用崩溃时,即使 Keepalived 还活着,也触发 VIP 转移。这就需要“服务级健康检查”。

编辑 Master 配置,加入:

vrrp_script chk_java {
    script "/usr/local/bin/check_java.sh"
    interval 2
    weight -20   # 如果脚本失败,降低20分优先级
}
vrrp_instance VI_1 {
    ...
    track_script {
        chk_java
    }
}

创建检查脚本:

sudo vim /usr/local/bin/check_java.sh

内容:

#!/bin/bash
curl -f http://localhost:8080/hello > /dev/null 2>&1
exit $?

赋予执行权限:

sudo chmod +x /usr/local/bin/check_java.sh

重启 Keepalived:

sudo systemctl restart keepalived

现在,如果 Java 应用挂掉,VIP 会自动漂移到 Backup!

3. 多播 vs 单播

默认 Keepalived 使用多播通信(224.0.0.18),某些云环境或容器网络不支持多播,需改用单播。

vrrp_instance 中添加:

unicast_peer {
    192.168.1.101   # Backup IP
}

Backup 节点则填写 Master IP:

unicast_peer {
    192.168.1.100   # Master IP
}

Java 端主动感知 VIP 切换(可选进阶)

虽然 VIP 对客户端透明,但在某些场景下,Java 应用可能需要知道自己是否“当前活跃节点”,以便执行特定逻辑(如定时任务只在主节点运行)。

我们可以编写一个线程,定期检查本机是否持有 VIP:

@Component
public class VipMonitor {
    private static final String VIP = "192.168.1.200";
    private volatile boolean isMaster = false;
    @PostConstruct
    public void startMonitoring() {
        new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    boolean hasVip = checkLocalHasVip();
                    if (hasVip != isMaster) {
                        isMaster = hasVip;
                        System.out.println("VIP 状态变更: " + (isMaster ? "成为主节点" : "降为备节点"));
                        onRoleChange(isMaster);
                    }
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "VipMonitor").start();
    }
    private boolean checkLocalHasVip() {
        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                Enumeration<InetAddress> addresses = iface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr.getHostAddress().equals(VIP)) {
                        return true;
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return false;
    }
    private void onRoleChange(boolean isNowMaster) {
        if (isNowMaster) {
            // 启动主节点专属任务,如:数据同步、报表生成等
            scheduleMasterOnlyTasks();
        } else {
            // 停止主节点任务
            cancelMasterOnlyTasks();
        }
    }
    private void scheduleMasterOnlyTasks() {
        System.out.println("启动主节点专属定时任务...");
        // 示例:每分钟执行一次
        // yourScheduler.scheduleAtFixedRate(...);
    }
    private void cancelMasterOnlyTasks() {
        System.out.println("取消主节点专属定时任务...");
        // yourScheduler.shutdown();
    }
    public boolean isMaster() {
        return isMaster;
    }
}

这样,你的 Java 应用不仅能被动接受流量,还能主动参与高可用决策!

安全加固建议

虽然 Keepalived 本身轻量高效,但在生产环境中仍需注意安全:

  1. 认证密码复杂化:避免使用 123456 这类弱密码。
  2. 限制 VRRP 通信源 IP:通过防火墙规则,只允许备机 IP 发送 VRRP 包。
  3. 启用 unicast 替代 multicast:避免广播风暴或被中间人监听。
  4. 监控日志:定期查看 /var/log/syslogjournalctl -u keepalived,及时发现异常切换。

生产环境最佳实践

  1. 至少三节点部署:避免“脑裂”(Split Brain)问题。可通过第三方仲裁(如 ZooKeeper、Redis)或脚本检测决定最终主节点。
  2. VIP 与服务强绑定:确保 VIP 漂移时,对应服务确实可用(通过健康检查脚本)。
  3. 优雅关闭:在系统 shutdown 前,主动降低 Keepalived 权重,让 VIP 提前漂移,减少服务中断时间。
  4. 配合 Consul/ZooKeeper:对于微服务架构,可结合服务注册中心,实现更智能的流量调度。

常见问题排查

Q1: VIP 无法绑定?

Q2: 主备同时持有 VIP(脑裂)?

Q3: 健康检查脚本不生效?

性能与扩展性考量

Keepalived 本身性能极高,单实例可支撑数千个 VIP 和数十万次状态检测。但在超大规模集群中,仍需考虑:

与其他高可用方案对比

方案优点缺点适用场景
Keepalived轻量、配置简单、成熟稳定功能较单一,依赖 VIPLVS/Nginx/自定义服务
Pacemaker + Corosync功能强大,支持资源组、约束配置复杂,学习成本高数据库集群、企业级 HA
Consul服务发现+健康检查+KV存储一体资源占用高,需维护集群微服务架构
Etcd + 自研脚本灵活可控开发维护成本高定制化需求强的场景

对于大多数中小规模 Java 应用,Keepalived + Nginx + 健康检查脚本的组合,是最具性价比的选择。

总结

通过本文,我们完成了从零配置 Keepalived,实现 VIP 故障自动转移,并将其与 Java Spring Boot 应用结合,构建了一个真正意义上的高可用服务架构。整个过程涵盖了:

✅ Keepalived 安装与基础配置
✅ VIP 漂移验证与故障模拟
✅ Java 应用部署与 Nginx 反向代理整合
✅ 自定义健康检查脚本增强可靠性
✅ Java 端主动感知角色变化(进阶)
✅ 生产环境最佳实践与安全建议

高可用不是“加个工具”就能解决的问题,而是贯穿架构设计、服务部署、监控告警、故障演练的系统工程。Keepalived 是其中重要一环,帮助我们在基础设施层面屏蔽单点故障,为上层业务保驾护航。

下一步你可以尝试:

  1. 在三台机器上部署 Keepalived,实现“一主两备”。
  2. 编写更复杂的健康检查脚本,如检测 JVM 内存、线程池状态。
  3. 将 VIP 信息写入 Redis 或 MySQL,供其他服务读取当前主节点。
  4. 结合 Prometheus + Grafana,监控 VIP 切换次数与耗时。
  5. 在 Docker 容器中运行 Keepalived,探索容器化高可用方案。

结语

技术之路,贵在实践。希望本文不仅教会你如何配置 Keepalived,更能启发你思考系统稳定性背后的本质——冗余、检测、切换、恢复。每一次故障转移的背后,都是无数工程师对“永不宕机”的执着追求。

以上就是Linux Keepalived配置虚拟IP实现故障转移的全过程的详细内容,更多关于Linux Keepalived虚拟IP故障转移的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文