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 算法)。
相关推荐
草莓熊Lotso8 分钟前
Linux 文件描述符与重定向实战:从原理到 minishell 实现
android·linux·运维·服务器·数据库·c++·人工智能
历程里程碑12 分钟前
Linux22 文件系统
linux·运维·c语言·开发语言·数据结构·c++·算法
七夜zippoe8 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
Fcy64810 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满10 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠10 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
Harvey90310 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技11 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀12 小时前
Linux环境变量
linux·运维·服务器
zzzsde12 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器