一、集群架构规划(共 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 节点初始化操作
-
克隆新虚拟机,命名为 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>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 生成日志、上传静态资源);
-
配置 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些许不同,主要是状态是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段不一样,有点混乱,不过应该没人有这样的疑问吧,太低级了
一、测试前准备
-
确认所有服务正常运行:
# 批量检查核心服务(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 -
验证基础业务可用:
- 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 访问:
二、分场景高可用测试
场景 1:单个 Web 节点故障(验证 LVS 自动剔除)
测试步骤:
-
模拟 web01 故障(停止 Nginx 服务):
ssh root@192.168.24.201 "systemctl stop nginx" -
连续访问 Web VIP,验证请求不分发到 web01:
# 执行5次访问,观察返回结果 for i in {1..5}; do echo -e "第$i次访问:" curl -s http://www.chengke.com | grep "Server Hostname" done -
验证 LVS 健康检查生效(web01 被剔除):
ipvsadm -Ln --stats # 查看web01的ActiveConn为0,且无新连接 -
恢复 web01 服务:
ssh root@192.168.24.201 "systemctl start nginx" -
再次访问,验证 web01 重新加入集群:
curl http://www.chengke.com | grep "web01" # 一段时间后可再次出现
预期结果:
- 步骤 2 中仅返回 web02/web03 的页面内容,无 web01;
- 步骤 3 中 web01 的连接数持续为 0;
- 步骤 5 中 web01 恢复后,请求会重新分发到该节点。
场景 2:DNS 主节点故障(验证从节点接管)
测试步骤:
-
模拟 dns-master 故障(停止 BIND 服务):
ssh root@192.168.24.107 "systemctl stop named" -
测试 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 健康检查脚本状态(lb-master 执行):
systemctl status keepalived --no-pager | grep checkdns.sh -
恢复 dns-master 服务:
ssh root@192.168.24.107 "systemctl start named"
预期结果:
- 步骤 2 中解析正常返回(由 dns-slave 提供服务);
- 步骤 3 中无 "checkdns.sh 检测失败" 的持续报错;
- 恢复后,DNS 主从同步正常。
场景 3:LB 主备切换(验证 VIP 漂移)
测试步骤:
-
查看当前 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 -
模拟 lb-master 故障(停止 Keepalived 服务):
ssh root@192.168.24.105 "systemctl stop keepalived" -
验证 VIP 漂移到 lb-backup:
ssh root@192.168.24.106 "ip a show ens160 | grep -E '192.168.24.100|192.168.24.200'" -
验证业务不中断:
# Web访问 curl http://www.chengke.com # DNS解析 dig www.chengke.com @192.168.24.100 +short -
恢复 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 优先级调整)
测试步骤:
-
模拟 NFS 故障(停止 NFS 服务):
ssh root@192.168.24.210 "systemctl stop nfs-server" -
查看 LVS 节点优先级变化(lb-backup 执行):
# 因check_nfs.sh检测失败,lb-backup优先级降低20(从100→80) systemctl status keepalived --no-pager | grep "priority" -
验证 Web 节点业务状态(页面无法加载,但 LVS 不分发新请求):
curl http://www.chengke.com # 预期返回500或超时(NFS挂载失效) -
恢复 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 -
验证业务恢复:
curl http://www.chengke.com # 正常返回NFS共享页面
预期结果:
- 步骤 2 中 lb-backup 优先级降低,若存在其他备用 LVS 节点,VIP 会漂移;
- 步骤 3 中 Web 页面暂时不可用,但 LVS 不会将新请求分发到无效节点;
- 步骤 5 中 NFS 恢复后,Web 业务自动恢复。
场景 5:Web 节点 NFS 挂载丢失(验证脚本自动恢复)
测试步骤:
-
模拟 web01 的 NFS 挂载丢失:
ssh root@192.168.24.201 "umount /usr/share/nginx/html" -
验证 Nginx 业务异常(虽 80 端口通,但页面无法返回):
ssh root@192.168.24.201 "curl -s localhost | grep 200" # 无输出 -
等待 5 分钟(或手动执行监控脚本)这个在下面的优化建议里面:
ssh root@192.168.24.201 "/usr/local/bin/nfs_monitor.sh" -
验证挂载恢复:
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 节点业务恢复,无需手动干预。
三、测试后恢复与总结
-
恢复所有节点服务(若测试中未恢复):
# 批量启动所有核心服务 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" -
最终验证集群状态:
- VIP 分布:lb-master 持有 192.168.24.100,lb-backup 持有 192.168.24.200;
- 业务可用:Web 访问和 DNS 解析均正常;
- 健康检查:所有脚本执行返回 0(
/etc/keepalived/check_*.sh && echo $?)。
三、优化改进建议
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" 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
-
集群批量操作脚本(管理节点执行,需配置免密登录):
#!/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 运维层面
- 配置备份:定时备份各节点的关键配置(/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 算法)。