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.24.2)" echo " dns_servers DNS服务器,逗号分隔 (可选,默认: 223.5.5.5,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.24.2"} #这里要根据自己的网关进行调整,这里代表有第二个参数就用第二参数的值不然就是用默认值 local dns=${3:-"223.5.5.5,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 log_info "IP 配置完成,正在启动网卡(SSH 即将断开,请使用新 IP:$ip 重新连接)" log_info "系统初始化完成!" nmcli connection up $interface } main() { if [[ $# -lt 2 ]]; then usage fi check_root set_hostname "$1" close_selinux_firewalld set_static_ip "$2" "$3" "$4" } main "$@" 1.2 NFS 节点初始化操作
- 克隆新虚拟机,命名为 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,且这个依赖是 “双向的”:
- NFS 服务端:启动 nfs-server 时,会向本机 rpcbind 注册 “NFS 服务对应的 RPC 程序号 + 动态端口”;
- NFS 客户端(Web 节点):执行
mount -t nfs挂载共享目录时,第一步会先访问 NFS 服务端的 rpcbind,查询 “NFS 服务的动态端口”,只有拿到端口后,客户端才能和服务端建立 NFS 通信、完成挂载。
2.2 配置 NFS 共享目录
- 创建 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> <h1>Welcome to chengke.com - NFS Shared Content</h1> <p>Server IP: <span></span></p> <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 生成日志、上传静态资源);
- 配置 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 节点
- 安装 nfs-utils(用于挂载):
dnf install -y nfs-utils - 备份原有 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 - 设置开机自动挂载(/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些许不同,主要是状态是noerror4.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)
四层网络负载均衡,内核级别,不解析http,只认ip+端口,做流量转发。
注意:lb主节点和备节点的dns都要写dns集群的vip,不然在用域名访问的时候没办法通过dns服务来查询到其对应的ip地址,如果直接用vip来访问,就不会经过dns这一步,通过负债均衡直接就可轮询web集群,用域名访问比用vip访问要多一步,后续步骤一样。
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端口通也判定失败) fi5.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段不一样,有点混乱,不过应该没人有这样的疑问吧,太低级了
一、测试前准备
- 验证基础业务可用:
- 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 后端节点)
- Web 访问:
确认所有服务正常运行:
# 批量检查核心服务(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 二、分场景高可用测试
场景 1:单个 Web 节点故障(验证 LVS 自动剔除)
测试步骤:
再次访问,验证 web01 重新加入集群:
curl http://www.chengke.com | grep "web01" # 一段时间后可再次出现 恢复 web01 服务:
ssh [email protected] "systemctl start nginx" 验证 LVS 健康检查生效(web01 被剔除):
ipvsadm -Ln --stats # 查看web01的ActiveConn为0,且无新连接 连续访问 Web VIP,验证请求不分发到 web01:
# 执行5次访问,观察返回结果 for i in {1..5}; do echo -e "第$i次访问:" curl -s http://www.chengke.com | grep "Server Hostname" done 模拟 web01 故障(停止 Nginx 服务):
ssh [email protected] "systemctl stop nginx" 预期结果:
- 步骤 2 中仅返回 web02/web03 的页面内容,无 web01;
- 步骤 3 中 web01 的连接数持续为 0;
- 步骤 5 中 web01 恢复后,请求会重新分发到该节点。
场景 2:DNS 主节点故障(验证从节点接管)
测试步骤:
恢复 dns-master 服务:
ssh [email protected] "systemctl start named" 查看 DNS 健康检查脚本状态(lb-master 执行):
systemctl status keepalived --no-pager | grep checkdns.sh 测试 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 模拟 dns-master 故障(停止 BIND 服务):
ssh [email protected] "systemctl stop named" 预期结果:
- 步骤 2 中解析正常返回(由 dns-slave 提供服务);
- 步骤 3 中无 “checkdns.sh 检测失败” 的持续报错;
- 恢复后,DNS 主从同步正常。
场景 3:LB 主备切换(验证 VIP 漂移)
测试步骤:
恢复 lb-master 服务,验证 VIP 回迁:
ssh [email protected] "systemctl start keepalived" ssh [email protected] "ip a show ens160 | grep 192.168.24.100" # 预期回迁 验证业务不中断:
# Web访问 curl http://www.chengke.com # DNS解析 dig www.chengke.com @192.168.24.100 +short 验证 VIP 漂移到 lb-backup:
ssh [email protected] "ip a show ens160 | grep -E '192.168.24.100|192.168.24.200'" 模拟 lb-master 故障(停止 Keepalived 服务):
ssh [email protected] "systemctl stop keepalived" 查看当前 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 预期结果:
- 步骤 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 优先级调整)
测试步骤:
验证业务恢复:
curl http://www.chengke.com # 正常返回NFS共享页面 恢复 NFS 服务:
ssh [email protected] "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 验证 Web 节点业务状态(页面无法加载,但 LVS 不分发新请求):
curl http://www.chengke.com # 预期返回500或超时(NFS挂载失效) 查看 LVS 节点优先级变化(lb-backup 执行):
# 因check_nfs.sh检测失败,lb-backup优先级降低20(从100→80) systemctl status keepalived --no-pager | grep "priority" 模拟 NFS 故障(停止 NFS 服务):
ssh [email protected] "systemctl stop nfs-server" 预期结果:
- 步骤 2 中 lb-backup 优先级降低,若存在其他备用 LVS 节点,VIP 会漂移;
- 步骤 3 中 Web 页面暂时不可用,但 LVS 不会将新请求分发到无效节点;
- 步骤 5 中 NFS 恢复后,Web 业务自动恢复。
场景 5:Web 节点 NFS 挂载丢失(验证脚本自动恢复)
测试步骤:
验证挂载恢复:
ssh [email protected] "df -h | grep /usr/share/nginx/html" # 显示NFS挂载 ssh [email protected] "curl -s localhost | grep 200" # 正常返回 等待 5 分钟(或手动执行监控脚本)这个在下面的优化建议里面:
ssh [email protected] "/usr/local/bin/nfs_monitor.sh" 验证 Nginx 业务异常(虽 80 端口通,但页面无法返回):
ssh [email protected] "curl -s localhost | grep 200" # 无输出 模拟 web01 的 NFS 挂载丢失:
ssh [email protected] "umount /usr/share/nginx/html" 预期结果:
- 步骤 3 中监控脚本自动重新挂载 NFS;
- 步骤 4 中 Web 节点业务恢复,无需手动干预。
三、测试后恢复与总结
- 最终验证集群状态:
- VIP 分布:lb-master 持有 192.168.24.100,lb-backup 持有 192.168.24.200;
- 业务可用:Web 访问和 DNS 解析均正常;
- 健康检查:所有脚本执行返回 0(
/etc/keepalived/check_*.sh && echo $?)。
恢复所有节点服务(若测试中未恢复):
# 批量启动所有核心服务 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 [email protected] "systemctl start nfs-server && systemctl enable nfs-server" 三、优化改进建议
3.1 架构层面
- NFS 高可用:单 NFS 节点是单点故障,建议部署 NFS+DRBD+Heartbeat 或使用 GlusterFS/Ceph 分布式存储替代单机 NFS;
- 监控告警:部署 Zabbix/Prometheus+Grafana,监控各节点 CPU / 内存 / 磁盘、NFS 挂载状态、nginx 连接数、LB VIP 状态、DNS 解析成功率;
- 日志集中化:部署 ELK/EFK 栈,收集 nginx 访问日志、NFS 日志、LB/Keepalived 日志、DNS 日志,便于故障排查;
- 安全加固:
- NFS 共享限制仅 Web 节点访问(细化 exports 网段);
- 配置 iptables/firewalld 白名单(仅允许集群内 IP 访问 53/80/111/2049 端口);
- 给 DNS 添加 TSIG 密钥,防止主从同步被篡改;
- 启用 nginx HTTPS,替换 80 端口为 443,LB 同步调整端口。
3.2 脚本层面
- 新增 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" [email protected] } # 检查挂载状态 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 - 集群批量操作脚本(管理节点执行,需配置免密登录):
#!/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/root3.3 运维层面
- 配置备份:定时备份各节点的关键配置(/etc/keepalived/、/etc/named/、/etc/exports、/etc/fstab)到 NFS 或远程存储;
- 自动化部署:使用 Ansible/Shell 脚本实现集群一键部署,替代手动操作;
- 文档标准化:整理集群拓扑图、IP 规划表、操作手册、故障排查手册;
- 性能优化:
- NFS 调整缓存参数(如 mount 添加 cache=none/loose);
- nginx 开启缓存,调整 worker_processes 为 CPU 核心数;
- LVS 调整调度算法(如根据 Web 节点性能调整 weight,或使用 wlc 算法)。