LVS+Keepalived+DNS+Web+NFS 高可用集群项目完整部署流程

一、集群架构规划(共 8 台虚拟机)

ip段用自己的

当然完成这个案例并不是一定要8台虚拟机,集群是可以合并在一起或者一个功能集群少开一点虚拟机

节点角色 主机名 IP 地址 核心职责
Web 节点 1 web01 192.168.72.201 挂载 NFS 共享,提供 Web 服务
Web 节点 2 web02 192.168.72.202 挂载 NFS 共享,提供 Web 服务
Web 节点 3 web03 192.168.72.203 挂载 NFS 共享,提供 Web 服务
DNS 主节点 dns-master 192.168.72.107 解析www.chengke.com到 Web VIP
DNS 从节点 dns-slave 192.168.72.108 同步 DNS 主节点配置,备用
LB 主节点 lb-master 192.168.72.105 LVS+Keepalived,承载 DNS/Web VIP
LB 备节点 lb-backup 192.168.72.106 LB 故障时接管 VIP
NFS 存储节点 nfs-server 192.168.72.210 提供 Web 内容共享存储
虚拟 IP(VIP) - 192.168.72.100 DNS 服务 VIP
虚拟 IP(VIP) - 192.168.72.200 Web 服务 VIP

按照上面的主机名和ip先把虚拟机初始化好,用下面的脚本非常方便

二、完整部署流程

阶段 1:NFS 节点初始化

1.1 脚本

脚本如下:

复制代码
#!/bin/bash

# useage: sudo ./init_sys.sh <hostname> <ip_address> [gateway] [dns]

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

log_info() {
    echo -e "${GREEN}[INFO]${NC} $(date +'%F %T') $1"
}
log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $(date +'%F %T') $1"
}
log_error() {
    echo -e "${RED}[ERROR]${NC} $(date +'%F %T') $1"
}

check_root() {
    if [[ $EUID -ne 0 ]]; then
        log_error "此脚本必须以root权限运行"
        exit 1
    fi
}

usage() {
    echo "用法: $0 <hostname> <ip_address> [gateway] [dns_servers]"
    echo ""
    echo "参数说明:"
    echo "  hostname    要设置的主机名"
    echo "  ip_address  要设置的静态IP地址 (如: 192.168.72.100)"
    echo "  gateway     网关地址 (如: 192.168.72.2)"
    echo "  dns_servers DNS服务器,逗号分隔 (可选,默认: 192.168.72.100,8.8.8.8)"
    echo ""
    echo "示例:"
    echo "  $0 myserver 192.168.72.100 192.168.72.2 8.8.8.8,223.5.5.5"
    echo "  $0 webserver 10.0.0.50"
    exit 1
}

get_interface() {
    # 尝试获取第一个活动的非环回接口(优化鲁棒性)
    local interface=$(ip -4 route show default | awk '/default/ {print $5}' 2>/dev/null)
    if [[ -z "$interface" ]]; then
        interface=$(ip link show | grep -v lo | grep -E 'state UP|state RUNNING' | head -1 | awk -F': ' '{print $2}' | sed 's/ //g')
    fi
    if [[ -z "$interface" ]]; then
        log_error "无法自动检测网络接口"
        read -p "请输入网络接口名称 (如: eth0, ens160): " interface
        # 二次校验输入
        if [[ -z "$interface" ]]; then
            log_error "接口名称不能为空"
            exit 1
        fi
    fi
    echo "$interface"
}

set_hostname() {
    local hostname=$1
    log_info "正在设置主机名为: $hostname"
    hostnamectl set-hostname $hostname
    # 写入/etc/hosts避免解析问题
    echo "$(hostname -I | awk '{print $1}') $hostname" >> /etc/hosts
    log_info "主机名设置完成"
}

close_selinux_firewalld() {
    log_info "关闭selinux"
    setenforce 0
    sed -i.bak "s/^SELINUX=.*/SELINUX=disabled/" /etc/selinux/config
    # 验证selinux修改
    if grep -q "SELINUX=disabled" /etc/selinux/config; then
        log_info "SELinux已设置为永久禁用"
    else
        log_error "SELinux配置修改失败"
    fi
    
    log_info "关闭防火墙"
    systemctl disable --now firewalld
    if [[ $(systemctl is-active firewalld) == "inactive" ]]; then
        log_info "防火墙已关闭"
    else
        log_warn "防火墙关闭失败,手动检查"
    fi
}

set_static_ip() {
    local interface=$(get_interface)
    local ip=$1
    local gateway=${2:-"192.168.72.2"}
    local dns=${3:-"192.168.72.100,8.8.8.8"}

    log_info "正在为接口 $interface 配置静态IP: $ip/24"
    # 先删除原有同名连接(避免冲突)
    nmcli connection delete $interface 2>/dev/null
    # 创建新连接
    nmcli connection add con-name $interface ifname $interface type ethernet ipv4.method manual ipv4.addresses $ip/24 ipv4.gateway $gateway ipv4.dns $dns ipv4.dns-search chengke.com connection.autoconnect yes
    nmcli connection up $interface
    # 验证IP配置
    if ip addr show $interface | grep -q "$ip/24"; then
        log_info "IP配置成功: $ip/24"
    else
        log_error "IP配置失败,手动检查"
    fi
}

main() {
    if [[ $# -lt 2 ]]; then
        usage
    fi
    check_root
    set_hostname "$1"
    close_selinux_firewalld
    set_static_ip "$2" "$3" "$4"
    log_info "系统初始化完成!"
}

main "$@"
1.2 NFS 节点初始化操作
  1. 克隆新虚拟机,命名为 nfs-server,执行优化后的初始化脚本:

    上传脚本到nfs-server节点

    scp init_sys.sh root@新虚拟机IP:/root/

    执行脚本(主机名nfs-server,IP 192.168.72.210,网关192.168.72.2,DNS用集群VIP)

    chmod +x /root/init_sys.sh
    /root/init_sys.sh nfs-server 192.168.72.210 192.168.72.2 192.168.72.100

当然你光改ip和主机名也是可行的

可以用以下方式稍加验证防火墙和selinux是否关闭

注意:这里是因为学习重点不在这为了方便才关的,一般要有针对的措施让其不会拦截你的数据,比如防火墙让服务或者端口通行,或者其他安全措施打标签之类的。

复制代码
[root@nfs-serevr ~]# systemctl status firewalld.service 
○ firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: enabled)
     Active: inactive (dead)
       Docs: man:firewalld(1)
[root@nfs-serevr ~]# cat /etc/selinux/config | grep SELINUX
# SELINUX= can take one of these three values:
# NOTE: Up to RHEL 8 release included, SELINUX=disabled would also
SELINUX=disabled
# SELINUXTYPE= can take one of these three values:
SELINUXTYPE=targeted

阶段 2:部署 NFS 服务(nfs-server 节点)

2.1 安装 NFS 相关包
复制代码
log_info "安装NFS和rpcbind服务"
dnf install -y nfs-utils rpcbind
# 设置开机自启
systemctl enable --now rpcbind nfs-server
# 验证服务状态
if [[ $(systemctl is-active nfs-server) == "active" ]]; then
    log_info "NFS服务启动成功"
else
    log_error "NFS服务启动失败"
    exit 1
fi

这里是为了练习脚本,不熟练应该好好练练,直接用命令当然也可行

疑点1:为什么要按照rpcbind

Linux 发行版的包管理器(dnf/yum)会维护软件包的依赖链,nfs-utils(NFS 核心工具包)的依赖清单中默认包含 rpcbind ------ 执行dnf install nfs-utils时,包管理器会自动检测并安装 rpcbind(以及其他依赖),无需你手动指定。

简单来说就是不用指定安装也有,其作用如下:

NFS 是基于 RPC 协议实现的文件共享,RPC 协议的端口映射依赖 rpcbind,且这个依赖是 "双向的":

  1. NFS 服务端:启动 nfs-server 时,会向本机 rpcbind 注册 "NFS 服务对应的 RPC 程序号 + 动态端口";
  2. NFS 客户端(Web 节点) :执行mount -t nfs挂载共享目录时,第一步会先访问 NFS 服务端的 rpcbind,查询 "NFS 服务的动态端口",只有拿到端口后,客户端才能和服务端建立 NFS 通信、完成挂载。
2.2 配置 NFS 共享目录
  1. 创建 Web 内容共享目录:

    创建共享目录,设置权限

    mkdir -p /data/webroot
    chown -R nobody:nobody /data/webroot
    chmod -R 755 /data/webroot

    写入统一的Web测试内容

    echo "<html>

    <head><title>chengke NFS Web</title></head> <body>

    Welcome to chengke.com - NFS Shared Content

    Server IP:

    <script> fetch('https://api.ipify.org?format=json') .then(response => response.json()) .then(data => document.getElementById('server-ip').textContent = data.ip); </script> </body> </html>" > /data/webroot/index.html

注意:这里的测试内容也是自己顺便写就行

温故知识:NFS 默认开启root_squash机制 ------ 将客户端的root用户映射为服务端的nobody用户(防止客户端 root 权限过大篡改服务端文件)。如果共享目录的所有者是root,客户端(Web 节点)以root身份挂载后,会被映射为nobody,导致无法写入文件(比如 nginx 生成日志、上传静态资源);

  1. 配置 NFS 共享规则(/etc/exports):

    编辑exports文件

    cat > /etc/exports << EOF
    /data/webroot 192.168.72.0/24(rw,sync,no_root_squash,no_all_squash,anonuid=0,anongid=0)
    EOF

    生效配置

    exportfs -rv # -r:重新导出所有目录;-v:显示详细信息

    验证共享

    showmount -e localhost

    输出应包含:/data/webroot 192.168.72.0/24

详细解释:

复制代码
rw	读写权限(read-write)	允许 Web 节点(客户端)对共享目录读 + 写(比如更新网页内容、生成日志);若设为ro则仅只读。


sync	同步写入(synchronous)	客户端写入数据时,NFS 服务端先将数据写入磁盘,再向客户端返回 "写入成功";
对比async(异步):服务端先返回成功,再后台写磁盘,速度快但断电易丢数据,生产环境必用sync。
no_root_squash	不压缩 root 权限(核心)	NFS 默认会把客户端的 root 用户映射为服务端的nobody(匿名用户),避免客户端 root 滥用权限;


no_root_squash 表示:客户端 root 用户访问时,保留 root 权限(UID=0),能完全控制共享目录;
对你的场景:Web 节点以 root 挂载 / 操作目录时,有完整的读写权限,不会因权限不足无法修改文件。


no_all_squash	不压缩所有用户权限	与all_squash相反:不将所有客户端用户(包括普通用户)映射为匿名用户,保留客户端原用户的 UID/GID;
对你的场景:Web 节点的nginx用户(若有)访问目录时,能以自身身份操作,无需额外权限适配。
anonuid=0	匿名用户 UID 设为 0(root)	当需要映射匿名用户时,强制将匿名用户的 UID 设为 0(即 root);


配合no_root_squash,进一步保证客户端 root 的权限不丢失。
anongid=0	匿名用户 GID 设为 0(root)	与anonuid=0对应,匿名用户的组 ID 设为 root 组,权限完全匹配。
疑点1:755权限和以上冲突吗

无论 NFS exports 怎么配置,只要系统文件权限不允许的操作,NFS 远程访问一定也不允许。

2.3 NFS 服务优化(可选)

这是优化建议,不是案例强求设置

复制代码
# 修改NFS配置,提升性能和稳定性
cat >> /etc/sysconfig/nfs << EOF
RPCNFSDCOUNT=8
NFSD_V4_GRACE=90
NFSD_V4_LEASE=90
EOF
# 重启NFS服务
systemctl restart nfs-server


RPCNFSDCOUNT=8	设置 NFS 服务器的nfsd进程数量为 8 个
(nfsd是处理客户端 NFS 请求的核心进程)	NFS 默认的nfsd进程数较少(通常 2-4 个),Web 集群有多个节点(web01/web02/web03)同时访问 NFS,增加到 8 个进程能提升并发处理能力,避免请求排队、响应慢;
建议值:通常设为服务器 CPU 核心数(比如 2 核设 4,4 核设 8),匹配硬件性能。

NFSD_V4_GRACE=90	NFS v4 的 "宽限期"(Grace Period),单位:秒
含义:NFS 服务器重启 / 故障恢复后,会等待 90 秒,让客户端重新上报自己的挂载状态、文件锁等信息,避免数据冲突	默认宽限期可能更长(比如 120 秒),设为 90 秒能缩短服务恢复时间:NFS 重启后,Web 集群无需等待太久就能重新挂载共享目录,减少业务中断时间;
不能太短(比如 < 60 秒),否则客户端来不及恢复,会出现文件锁丢失、挂载失败。

NFSD_V4_LEASE=90	NFS v4 的 "租约期"(Lease Period),单位:秒
含义:客户端与 NFS 服务端的会话有效期,客户端需在 90 秒内和服务端保持心跳,超时后服务端会释放该客户端的文件锁、资源	90 秒是 "稳定性 + 资源释放" 的平衡值:
✅ 对 Web 集群:短连接访问场景下,90 秒足够保证会话稳定,避免频繁重新建立连接;
✅ 对服务端:超时后及时释放僵尸会话的资源(比如某个 Web 节点宕机,90 秒后释放其占用的文件锁),避免资源泄漏。

阶段 3:Web 节点挂载 NFS 共享(web01/web02/web03),配置web节点,3个都要

3.1 统一配置 Web 节点
  1. 安装 nfs-utils(用于挂载):

    dnf install -y nfs-utils

  2. 备份原有 Web 目录,挂载 NFS:

    停止nginx服务

    systemctl stop nginx

    备份原有内容

    mv /usr/share/nginx/html /usr/share/nginx/html.bak 这里mv相当于改名的作用

    创建挂载点

    mkdir -p /usr/share/nginx/html

    临时挂载NFS(测试)

    mount -t nfs 192.168.72.210:/data/webroot /usr/share/nginx/html

    验证挂载

    df -h | grep /usr/share/nginx/html

    输出应包含:192.168.72.210:/data/webroot xxx xxx xxx xx% /usr/share/nginx/html

  3. 设置开机自动挂载(/etc/fstab):

如果虚拟机好用来做别的案例怕麻烦也不用,手动挂载也行

复制代码
# 写入fstab,添加软挂载参数避免开机卡壳
echo "192.168.72.210:/data/webroot /usr/share/nginx/html nfs defaults,_netdev,soft,timeo=10,retrans=3 0 0" >> /etc/fstab
3.2 配置 Nginx
复制代码
# 编辑Web站点配置
cat > /etc/nginx/conf.d/chengke.conf << EOF
server {
    listen          80;
    server_name     www.chengke.com;
    include         /usr/share/nginx/html/nginx_vars.conf;
    location / {
        root        /usr/share/nginx/html;
        index       index.html;
        add_header  X-Server-IP \$remote_addr;
        add_header  X-Server-Hostname \$hostname;
    }
}
EOF

# 验证配置并重启
nginx -t
systemctl restart nginx
3.3 Web 节点 VIP+ARP 参数配置(脚本化)
复制代码
#!/bin/bash
# Web节点VIP配置脚本
VIP="192.168.72.200"
LOG_INFO="\033[0;32m[INFO]\033[0m"

# 配置LO接口VIP
echo -e "$LOG_INFO 配置VIP: $VIP"
ifconfig lo:1 $VIP netmask 255.255.255.255 up
ip a show lo | grep $VIP

# 配置ARP参数(避免冲突)
echo -e "$LOG_INFO 配置ARP内核参数"
cat >> /etc/sysctl.conf << EOF
net.ipv4.conf.all.arp_ignore=1
net.ipv4.conf.lo.arp_ignore=1
net.ipv4.conf.all.arp_announce=2
net.ipv4.conf.lo.arp_announce=2
EOF
sysctl -p
echo -e "$LOG_INFO Web节点配置完成"

执行:chmod +x web_vip.sh && ./web_vip.sh

阶段 4:部署 DNS 节点(dns-master/dns-slave)

可以查看主从配置文章

4.1 安装 BIND 服务
复制代码
dnf install -y bind bind-utils
4.2 配置 dns-master

有文章具体解释:

复制代码
# 备份原有配置
cp /etc/named.conf /etc/named.conf.bak

# 编辑主配置
cat > /etc/named.conf << EOF
options {
    listen-on port 53 { 127.0.0.1; 192.168.72.107; 192.168.72.100; };
    directory       "/var/named";
    allow-query     { localhost; 192.168.72.0/24; };
    recursion yes;
    dnssec-validation no;
    forwarders { 8.8.8.8; 223.5.5.5; };
};

logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };
};

zone "chengke.com" IN {
    type master;
    file "chengke.com.zone";
    allow-transfer { 192.168.72.108; };
};
EOF

# 配置区域数据文件(时间戳序列号)
cp -p /var/named/named.localhost /var/named/chengke.com.zone
cat > /var/named/chengke.com.zone << EOF
\$TTL 1D
@       IN SOA  ns1 admin.chengke.com. (
                                        2026012001 ; serial
                                        1D        ; refresh
                                        1H        ; retry
                                        1W        ; expire
                                        3H )      ; minimum
        NS      ns1
        NS      ns2
ns1     A       192.168.72.107
ns2     A       192.168.72.108
www     A       192.168.72.200
txt     TXT     "AaBbCcDdEeFf"
EOF

# 验证配置并启动
named-checkconf /etc/named.conf
named-checkzone chengke.com /var/named/chengke.com.zone
systemctl enable --now named
4.3 配置 dns-slave
复制代码
cp /etc/named.conf /etc/named.conf.bak
cat > /etc/named.conf << EOF
options {
    listen-on port 53 { 127.0.0.1; 192.168.72.108; 192.168.72.100; };
    directory       "/var/named";
    allow-query     { localhost; 192.168.72.0/24; };
    recursion yes;
    dnssec-validation no;
    forwarders { 8.8.8.8; 223.5.5.5; };
};

logging {
    channel default_debug {
        file "data/named.run";
        severity dynamic;
    };
};

zone "chengke.com" IN {
    type slave;
    masters { 192.168.72.107; };
    file "slaves/chengke.com.zone";
    transfer-timeout 60;
};
EOF

# 启动并同步数据(这个是原本有文件,修改后删除原本的文件,重启服务会产生最新的文件)
第一次启动没有这一步
rm -f /var/named/slaves/chengke.com.zone
systemctl enable --now named
dig -t A www.chengke.com @192.168.72.108  # 验证同步

结果类似:

复制代码
[root@dns-master ~]# dig -t A www.chengke.com @192.168.24.107

; <<>> DiG 9.16.23-RH <<>> -t A www.chengke.com @192.168.24.107
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24861
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: d2dbeb307d943d49010000006970eb7f596e4fa92eb5080d (good)
;; QUESTION SECTION:
;www.chengke.com.		IN	A

;; ANSWER SECTION:
www.chengke.com.	86400	IN	A	192.168.24.200

;; Query time: 0 msec
;; SERVER: 192.168.24.107#53(192.168.24.107)
;; WHEN: Wed Jan 21 23:06:39 CST 2026
;; MSG SIZE  rcvd: 88


这里ip些许不同,主要是状态是noerror
4.4 DNS 节点 VIP 配置(脚本化)
复制代码
#!/bin/bash
# DNS节点VIP配置脚本
VIP="192.168.72.100"
LOG_INFO="\033[0;32m[INFO]\033[0m"

echo -e "$LOG_INFO 配置VIP: $VIP"
ifconfig lo:1 $VIP netmask 255.255.255.255 up
ip a show lo | grep $VIP

echo -e "$LOG_INFO 配置ARP参数"
cat >> /etc/sysctl.conf << EOF
net.ipv4.conf.all.arp_ignore=1
net.ipv4.conf.lo.arp_ignore=1
net.ipv4.conf.all.arp_announce=2
net.ipv4.conf.lo.arp_announce=2
EOF
sysctl -p
echo -e "$LOG_INFO DNS节点配置完成"

执行:chmod +x dns_vip.sh && ./dns_vip.sh

阶段 5:部署 LVS+Keepalived(lb-master/lb-backup)

5.1 安装依赖
复制代码
dnf install -y ipvsadm keepalived bind-utils
5.2 初始化 ipvsadm
复制代码
ipvsadm-save -n > /etc/sysconfig/ipvsadm
systemctl enable --now ipvsadm
ipvsadm -Ln  # 验证
5.3 配置 lb-master 的 Keepalived
复制代码
! Configuration File for keepalived
global_defs {
   router_id LVS_master  # 节点标识,与backup区分
}

# ========== NFS健康检查配置 ==========
vrrp_script check_nfs {
    script "/etc/keepalived/check_nfs.sh"  # NFS检查脚本路径
    interval 5          # 每5秒检测一次
    weight -20          # 检测失败则优先级减20(触发VIP漂移)
    fall 2              # 连续2次失败判定为真故障
    rise 2              # 连续2次成功恢复正常
}

# ========== Web VIP实例(lb-master为备节点) ==========
vrrp_instance VI_web {
    state BACKUP        # 备节点(lb-backup为MASTER)
    interface ens160    # 替换为你的实际网卡(如eth0/ens33)
    virtual_router_id 52 # 与lb-backup保持一致
    priority 90         # 优先级低于lb-backup的100
    advert_int 1        # VRRP通告间隔1秒
    authentication {
        auth_type PASS
        auth_pass 1111  # 与lb-backup保持一致
    }
    virtual_ipaddress {
        192.168.72.200  # Web服务VIP
    }
    # 关联NFS健康检查:NFS故障时降低本节点优先级
    track_script {
        check_nfs
    }
}

# ========== Web服务负载规则(替换为Nginx业务检查) ==========
virtual_server 192.168.72.200 80 {
    delay_loop 6          # 健康检查间隔6秒
    lb_algo rr            # 轮询调度算法
    lb_kind DR            # DR模式(直接路由)
    persistence_timeout 50 # 会话持久化50秒
    protocol TCP
    timeout 30

    real_server 192.168.72.201 80 {
        weight 1          # 权重1
        MISC_CHECK {      # 替换为Nginx业务层检查
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.201"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.202 80 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.202"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.203 80 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.203"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

# ========== DNS VIP实例(lb-master为主节点) ==========
vrrp_instance VI_dns {
    state MASTER         # 主节点
    interface ens160
    virtual_router_id 51 # 与lb-backup保持一致
    priority 100         # 优先级高于lb-backup的90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111  # 与lb-backup保持一致
    }
    virtual_ipaddress {
        192.168.72.100  # DNS服务VIP
    }
}

# ========== DNS服务负载规则 ==========
virtual_server 192.168.72.100 53 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    persistence_timeout 50
    protocol UDP         # DNS默认使用UDP协议
    timeout 30

    real_server 192.168.72.107 53 {
        weight 1
        MISC_CHECK {     # DNS自定义解析检查
            misc_path "/etc/keepalived/checkdns.sh -h 192.168.24.107 -d txt.chengke.com"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.108 53 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/checkdns.sh -h 192.168.24.108 -d txt.chengke.com"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}
5.4 配置 lb-backup 的 Keepalived
复制代码
! Configuration File for keepalived
global_defs {
   router_id LVS_backup # 节点标识,与master区分
}

# ========== NFS健康检查配置 ==========
vrrp_script check_nfs {
    script "/etc/keepalived/check_nfs.sh"
    interval 5
    weight -20
    fall 2
    rise 2
}

# ========== Web VIP实例(lb-backup为主节点) ==========
vrrp_instance VI_web {
    state MASTER         # 主节点
    interface ens160
    virtual_router_id 52 # 与lb-master保持一致
    priority 100         # 优先级高于lb-master的90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111  # 与lb-master保持一致
    }
    virtual_ipaddress {
        192.168.72.200  # Web服务VIP
    }
    track_script {
        check_nfs        # 关联NFS健康检查
    }
}

# ========== Web服务负载规则(替换为Nginx业务检查) ==========
virtual_server 192.168.72.200 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    persistence_timeout 50
    protocol TCP
    timeout 30

    real_server 192.168.72.201 80 {
        weight 1
        MISC_CHECK {      # 替换为Nginx业务层检查
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.201"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.202 80 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.202"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.203 80 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/check_nginx.sh 192.168.24.203"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

# ========== DNS VIP实例(lb-backup为备节点) ==========
vrrp_instance VI_dns {
    state BACKUP         # 备节点
    interface ens160
    virtual_router_id 51 # 与lb-master保持一致
    priority 90          # 优先级低于lb-master的100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111  # 与lb-master保持一致
    }
    virtual_ipaddress {
        192.168.72.100  # DNS服务VIP
    }
}

# ========== DNS服务负载规则 ==========
virtual_server 192.168.72.100 53 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    persistence_timeout 50
    protocol UDP
    timeout 30

    real_server 192.168.72.107 53 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/checkdns.sh -h 192.168.24.107 -d txt.chengke.com"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
    real_server 192.168.72.108 53 {
        weight 1
        MISC_CHECK {
            misc_path "/etc/keepalived/checkdns.sh -h 192.168.24.108 -d txt.chengke.com"
            connect_timeout 3
            retry 3
            delay_before_retry 3
        }
    }
}

5.5 创建健康检查脚本(lb-master/lb-backup 均需配置)

5.5.1 NFS 健康检查脚本(check_nfs.sh)

用于检测 NFS 服务器是否正常提供共享服务,异常时触发 Keepalived 优先级调整:

复制代码
cat > /etc/keepalived/check_nfs.sh << EOF
#!/bin/bash
# NFS服务健康检查脚本:检测挂载可用性
NFS_SERVER="192.168.72.210"
SHARE_DIR="/data/webroot"
TEST_DIR="/tmp/nfs_test_mount"

# 创建临时挂载目录
mkdir -p \$TEST_DIR

# 尝试挂载NFS(超时5秒,避免阻塞)
mount -t nfs -o timeo=5 \$NFS_SERVER:\$SHARE_DIR \$TEST_DIR &>/dev/null

if [ \$? -eq 0 ]; then
    # 挂载成功,清理临时目录
    umount \$TEST_DIR &>/dev/null
    rmdir \$TEST_DIR &>/dev/null
    exit 0  # 健康检查通过(返回0)
else
    # 挂载失败,清理临时目录
    rmdir \$TEST_DIR &>/dev/null
    exit 1  # 健康检查失败(返回非0)
fi
EOF

# 赋予执行权限
chmod +x /etc/keepalived/check_nfs.sh
5.5.2 DNS 健康检查脚本(checkdns.sh

用于检测 DNS 节点是否能正常解析指定记录,确保 DNS 服务可用:

复制代码
cat > /etc/keepalived/checkdns.sh << EOF
#!/bin/bash
# DNS健康检查脚本:检测指定DNS服务器的解析能力
while getopts "h:d:" opt; do
    case \$opt in
        h) DNS_IP=\$OPTARG ;;   # DNS服务器IP
        d) DOMAIN=\$OPTARG ;;   # 检测的域名
        *) echo "用法: \$0 -h <DNS_IP> -d <检测域名>"; exit 1 ;;
    esac
done

# 校验参数完整性
if [ -z "\$DNS_IP" ] || [ -z "\$DOMAIN" ]; then
    echo "参数缺失!示例:\$0 -h 192.168.72.107 -d txt.chengke.com"
    exit 1
fi

# 执行DNS解析(超时3秒,匹配指定TXT记录)
dig -t TXT \$DOMAIN @\$DNS_IP +timeout=3 +short | grep -q "AaBbCcDdEeFf"

if [ \$? -eq 0 ]; then
    exit 0  # 解析成功,健康检查通过
else
    exit 1  # 解析失败,健康检查不通过
fi
EOF

# 赋予执行权限
chmod +x /etc/keepalived/checkdns.sh
5.5.3 nginx健康检查脚本(check_nginx.sh)
复制代码
#!/bin/bash
# Nginx业务层健康检查脚本(检测页面是否正常返回200)
# 用法:./check_nginx.sh <Web节点IP>
WEB_IP=$1

# 校验参数
if [ -z "$WEB_IP" ]; then
    echo "参数缺失!示例:$0 192.168.72.201"
    exit 1
fi

# 检测Nginx是否能正常返回200状态码(超时3秒)
curl -s -o /dev/null -w "%{http_code}" http://$WEB_IP/index.html --connect-timeout 3 | grep -q "200"

if [ $? -eq 0 ]; then
    exit 0  # Nginx业务正常(页面返回200)
else
    exit 1  # Nginx业务异常(即使80端口通也判定失败)
fi

5.6 启动 Keepalived 服务(lb-master/lb-backup)

复制代码
# 启动服务并设置开机自启
systemctl enable --now keepalived

# 验证服务状态(无报错则正常)
systemctl status keepalived --no-pager

# 验证LVS规则是否生效(核心检查)
ipvsadm -Ln
# 预期输出示例:(这是我的例子,ip段不同)
[root@lb-master keepalived]# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.24.200:80 rr persistent 50
  -> 192.168.24.201:80            Route   1      0          0         
  -> 192.168.24.202:80            Route   1      0          0         
  -> 192.168.24.203:80            Route   1      0          0         
UDP  192.168.24.100:53 rr persistent 50
  -> 192.168.24.107:53            Route   1      0          0         
  -> 192.168.24.108:53            Route   1      0          0 

查看vip是否成功显示:

例如:

复制代码
[root@lb-master keepalived]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:09:90:2b brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    inet 192.168.24.105/24 brd 192.168.24.255 scope global noprefixroute ens160
       valid_lft forever preferred_lft forever
    inet 192.168.24.100/32 scope global ens160
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe09:902b/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

DNS 节点配置优化(可选)

在 dns-master 的区域数据文件中添加 NFS 节点解析(便于管理):

复制代码
vim /var/named/chengke.com.zone
# 添加以下行
nfs     A       192.168.72.210
# 增加serial值(如改为1)
@       IN SOA  ns1 admin.chengke.com. (
                                        1       ; serial 【必须修改,否则从节点不同步】
                                        1D      ; refresh
                                        1H      ; retry
                                        1W      ; expire
                                        3H )    ; minimum
# 重启DNS服务
systemctl restart named
# dns-slave验证同步
ls /var/named/slaves/chengke.com.zone
dig -t A nfs.chengke.com @192.168.72.108

测试(以下测试因为多种原因可能都需几十秒后验证结果,不能马上得到结果)

注意:而且原理上来说你nginx和keepalived配置不在一个虚拟机,停止nginx不会出现vip漂移等现象。原理就是通过脚本检测来停止keepalived来实现的漂移

将以下的24都改为72,做了多次,ip段不一样,有点混乱,不过应该没人有这样的疑问吧,太低级了

一、测试前准备

  1. 确认所有服务正常运行:

    复制代码
    # 批量检查核心服务(lb-master执行,需配置免密登录)
    for node in 192.168.24.201 192.168.24.202 192.168.24.203 192.168.24.107 192.168.24.108 192.168.24.105 192.168.24.106 192.168.24.210; do
        echo -e "\n=== 节点 $node ==="
        ssh root@$node "systemctl status nginx named keepalived nfs-server --no-pager | grep Active"
    done
  2. 验证基础业务可用:

    • Web 访问:curl http://www.chengke.com(返回 200 状态码 + NFS 共享页面)
    • DNS 解析:dig www.chengke.com @192.168.24.100 +short(返回 192.168.24.200)
    • LVS 规则:ipvsadm -Ln(显示所有 Web/DNS 后端节点)

二、分场景高可用测试

场景 1:单个 Web 节点故障(验证 LVS 自动剔除)

测试步骤:
  1. 模拟 web01 故障(停止 Nginx 服务):

    复制代码
    ssh root@192.168.24.201 "systemctl stop nginx"
  2. 连续访问 Web VIP,验证请求不分发到 web01:

    复制代码
    # 执行5次访问,观察返回结果
    for i in {1..5}; do
        echo -e "第$i次访问:"
        curl -s http://www.chengke.com | grep "Server Hostname"
    done
  3. 验证 LVS 健康检查生效(web01 被剔除):

    复制代码
    ipvsadm -Ln --stats  # 查看web01的ActiveConn为0,且无新连接
  4. 恢复 web01 服务:

    复制代码
    ssh root@192.168.24.201 "systemctl start nginx"
  5. 再次访问,验证 web01 重新加入集群:

    复制代码
    curl http://www.chengke.com | grep "web01"  # 一段时间后可再次出现
预期结果:
  • 步骤 2 中仅返回 web02/web03 的页面内容,无 web01;
  • 步骤 3 中 web01 的连接数持续为 0;
  • 步骤 5 中 web01 恢复后,请求会重新分发到该节点。

场景 2:DNS 主节点故障(验证从节点接管)

测试步骤:
  1. 模拟 dns-master 故障(停止 BIND 服务):

    复制代码
    ssh root@192.168.24.107 "systemctl stop named"
  2. 测试 DNS VIP 解析是否正常:

    复制代码
    # 执行3次解析,验证无失败
    for i in {1..3}; do
        dig www.chengke.com @192.168.24.100 +short
        dig txt.chengke.com @192.168.24.100 +short  # 验证健康检查记录
    done
  3. 查看 DNS 健康检查脚本状态(lb-master 执行):

    复制代码
    systemctl status keepalived --no-pager | grep checkdns.sh
  4. 恢复 dns-master 服务:

    复制代码
    ssh root@192.168.24.107 "systemctl start named"
预期结果:
  • 步骤 2 中解析正常返回(由 dns-slave 提供服务);
  • 步骤 3 中无 "checkdns.sh 检测失败" 的持续报错;
  • 恢复后,DNS 主从同步正常。

场景 3:LB 主备切换(验证 VIP 漂移)

测试步骤:
  1. 查看当前 VIP 持有状态(lb-master 为主节点,持有 DNS VIP):

    复制代码
    # lb-master执行
    ip a show ens160 | grep 192.168.24.100  # 预期显示DNS VIP
    # lb-backup执行
    ip a show ens160 | grep 192.168.24.200  # 预期显示Web VIP
  2. 模拟 lb-master 故障(停止 Keepalived 服务):

    复制代码
    ssh root@192.168.24.105 "systemctl stop keepalived"
  3. 验证 VIP 漂移到 lb-backup:

    复制代码
    ssh root@192.168.24.106 "ip a show ens160 | grep -E '192.168.24.100|192.168.24.200'"
  4. 验证业务不中断:

    复制代码
    # Web访问
    curl http://www.chengke.com
    # DNS解析
    dig www.chengke.com @192.168.24.100 +short
  5. 恢复 lb-master 服务,验证 VIP 回迁:

    复制代码
    ssh root@192.168.24.105 "systemctl start keepalived"
    ssh root@192.168.24.105 "ip a show ens160 | grep 192.168.24.100"  # 预期回迁
预期结果:
  • 步骤 3 中 lb-backup 同时持有 DNS VIP(192.168.24.100)和 Web VIP(192.168.24.200);
  • 步骤 4 中 Web 访问和 DNS 解析均正常;
  • 步骤 5 中 lb-master 恢复后,DNS VIP 回迁(因优先级更高)。

场景 4:NFS 服务器故障(验证 LVS 优先级调整)

测试步骤:
  1. 模拟 NFS 故障(停止 NFS 服务):

    复制代码
    ssh root@192.168.24.210 "systemctl stop nfs-server"
  2. 查看 LVS 节点优先级变化(lb-backup 执行):

    复制代码
    # 因check_nfs.sh检测失败,lb-backup优先级降低20(从100→80)
    systemctl status keepalived --no-pager | grep "priority"
  3. 验证 Web 节点业务状态(页面无法加载,但 LVS 不分发新请求):

    复制代码
    curl http://www.chengke.com  # 预期返回500或超时(NFS挂载失效)
  4. 恢复 NFS 服务:

    复制代码
    ssh root@192.168.24.210 "systemctl start nfs-server"
    # Web节点重新挂载NFS
    for ip in 192.168.24.201 192.168.24.202 192.168.24.203; do
        ssh root@$ip "mount -a && systemctl restart nginx"
    done
  5. 验证业务恢复:

    复制代码
    curl http://www.chengke.com  # 正常返回NFS共享页面
预期结果:
  • 步骤 2 中 lb-backup 优先级降低,若存在其他备用 LVS 节点,VIP 会漂移;
  • 步骤 3 中 Web 页面暂时不可用,但 LVS 不会将新请求分发到无效节点;
  • 步骤 5 中 NFS 恢复后,Web 业务自动恢复。

场景 5:Web 节点 NFS 挂载丢失(验证脚本自动恢复)

测试步骤:
  1. 模拟 web01 的 NFS 挂载丢失:

    复制代码
    ssh root@192.168.24.201 "umount /usr/share/nginx/html"
  2. 验证 Nginx 业务异常(虽 80 端口通,但页面无法返回):

    复制代码
    ssh root@192.168.24.201 "curl -s localhost | grep 200"  # 无输出
  3. 等待 5 分钟(或手动执行监控脚本)这个在下面的优化建议里面:

    复制代码
    ssh root@192.168.24.201 "/usr/local/bin/nfs_monitor.sh"
  4. 验证挂载恢复:

    复制代码
    ssh root@192.168.24.201 "df -h | grep /usr/share/nginx/html"  # 显示NFS挂载
    ssh root@192.168.24.201 "curl -s localhost | grep 200"  # 正常返回
预期结果:
  • 步骤 3 中监控脚本自动重新挂载 NFS;
  • 步骤 4 中 Web 节点业务恢复,无需手动干预。

三、测试后恢复与总结

  1. 恢复所有节点服务(若测试中未恢复):

    复制代码
    # 批量启动所有核心服务
    for node in 192.168.24.201 192.168.24.202 192.168.24.203; do
        ssh root@$node "systemctl start nginx && systemctl enable nginx"
    done
    for node in 192.168.24.107 192.168.24.108; do
        ssh root@$node "systemctl start named && systemctl enable named"
    done
    for node in 192.168.24.105 192.168.24.106; do
        ssh root@$node "systemctl start keepalived && systemctl enable keepalived"
    done
    ssh root@192.168.24.210 "systemctl start nfs-server && systemctl enable nfs-server"
  2. 最终验证集群状态:

    • VIP 分布:lb-master 持有 192.168.24.100,lb-backup 持有 192.168.24.200;
    • 业务可用:Web 访问和 DNS 解析均正常;
    • 健康检查:所有脚本执行返回 0(/etc/keepalived/check_*.sh && echo $?)。

三、优化改进建议

3.1 架构层面

  1. NFS 高可用:单 NFS 节点是单点故障,建议部署 NFS+DRBD+Heartbeat 或使用 GlusterFS/Ceph 分布式存储替代单机 NFS;
  2. 监控告警:部署 Zabbix/Prometheus+Grafana,监控各节点 CPU / 内存 / 磁盘、NFS 挂载状态、nginx 连接数、LB VIP 状态、DNS 解析成功率;
  3. 日志集中化:部署 ELK/EFK 栈,收集 nginx 访问日志、NFS 日志、LB/Keepalived 日志、DNS 日志,便于故障排查;
  4. 安全加固
    • NFS 共享限制仅 Web 节点访问(细化 exports 网段);
    • 配置 iptables/firewalld 白名单(仅允许集群内 IP 访问 53/80/111/2049 端口);
    • 给 DNS 添加 TSIG 密钥,防止主从同步被篡改;
    • 启用 nginx HTTPS,替换 80 端口为 443,LB 同步调整端口。

3.2 脚本层面

  1. 新增 NFS 挂载检测脚本(web 节点定时执行):

    #!/bin/bash

    /usr/local/bin/check_nfs_mount.sh

    MOUNT_POINT="/usr/share/nginx/html"
    NFS_SERVER="192.168.72.210:/data/webroot"
    LOG_FILE="/var/log/nfs_mount_check.log"

    log_info() {
    echo "(date +'%F %T') [INFO] 1" >> LOG_FILE } log_error() { echo "(date +'%F %T') [ERROR] 1" >> LOG_FILE
    # 发送告警(示例:邮件/钉钉/企业微信)
    # echo "NFS挂载失败: $MOUNT_POINT" | mail -s "NFS Mount Error" admin@chengke.com
    }

    检查挂载状态

    if ! mount | grep -q "NFS_SERVER on MOUNT_POINT"; then
    log_error "NFS挂载丢失,尝试重新挂载"
    mount -t nfs NFS_SERVER MOUNT_POINT
    # 验证重新挂载
    if mount | grep -q "NFS_SERVER on MOUNT_POINT"; then
    log_info "NFS重新挂载成功"
    systemctl restart nginx
    else
    log_error "NFS重新挂载失败,手动处理"
    fi
    else
    log_info "NFS挂载正常"
    fi

    添加到crontab

    */5 * * * * /usr/local/bin/check_nfs_mount.sh

  2. 集群批量操作脚本(管理节点执行,需配置免密登录):

    #!/bin/bash

    /usr/local/bin/cluster_manage.sh

    集群节点列表

    NODES=(
    "web01:192.168.72.201"
    "web02:192.168.72.202"
    "web03:192.168.72.203"
    "dns-master:192.168.72.107"
    "dns-slave:192.168.72.108"
    "lb-master:192.168.72.105"
    "lb-backup:192.168.72.106"
    "nfs-server:192.168.72.210"
    )

    执行命令函数

    exec_cmd() {
    local cmd=1 for node in "{NODES[@]}"; do
    hostname=(echo node | cut -d':' -f1)
    ip=(echo node | cut -d':' -f2)
    echo "===== 执行命令到 hostname (ip) ====="
    ssh root@ip "cmd"
    echo "======================================="
    echo
    done
    }

    重启指定服务函数

    restart_service() {
    local service=1 exec_cmd "systemctl restart service"
    }

    显示帮助

    usage() {
    echo "用法: $0 [选项] [参数]"
    echo "选项:"
    echo " exec <cmd> 批量执行命令"
    echo " restart <svc> 批量重启服务"
    echo " status <svc> 批量查看服务状态"
    exit 1
    }

    case 1 in exec) if [[ -z 2 ]]; then
    usage
    fi
    exec_cmd "2" ;; restart) if [[ -z 2 ]]; then
    usage
    fi
    restart_service "2" ;; status) if [[ -z 2 ]]; then
    usage
    fi
    exec_cmd "systemctl status $2"
    ;;
    *)
    usage
    ;;
    esac

部署方式:

复制代码
chmod +x /usr/local/bin/nfs_monitor.sh
# 添加定时任务
echo "*/5 * * * * /usr/local/bin/nfs_monitor.sh" >> /var/spool/cron/root

3.3 运维层面

  1. 配置备份:定时备份各节点的关键配置(/etc/keepalived/、/etc/named/、/etc/exports、/etc/fstab)到 NFS 或远程存储;
  2. 自动化部署:使用 Ansible/Shell 脚本实现集群一键部署,替代手动操作;
  3. 文档标准化:整理集群拓扑图、IP 规划表、操作手册、故障排查手册;
  4. 性能优化
    • NFS 调整缓存参数(如 mount 添加 cache=none/loose);
    • nginx 开启缓存,调整 worker_processes 为 CPU 核心数;
    • LVS 调整调度算法(如根据 Web 节点性能调整 weight,或使用 wlc 算法)。
相关推荐
晚风吹长发2 小时前
初步了解Linux中的命名管道及简单应用和简单日志
linux·运维·服务器·开发语言·数据结构·c++·算法
weixin_462446233 小时前
Linux 下使用 xfreerdp3 远程连接 Windows(从安装到实战使用)
linux·运维·windows
爱丶狸3 小时前
Linux三剑客之sed
linux·运维·服务器
China_Yanhy4 小时前
区块链运维日记 · 第 1 日 :消失的 2000 笔提现 —— 致命的 Nonce 冲突
运维·区块链
终端行者4 小时前
Nginx limit_conn_zone 模块详解 Nginx如何限流 防止CC攻击
网络·nginx
趁着年轻吃点苦4 小时前
录用通知-自助系统的服务器部署指南
运维·服务器
仗剑恬雅人4 小时前
LINUX数据库高频常用命令
linux·运维·服务器·数据库·ssh·运维开发
LetsonH5 小时前
服务器配置(开机自启+XRDP远程)
运维·服务器
Getgit5 小时前
Linux系统的特点有哪些
java·linux·运维·网络·sql