纯热备场景下，用最少的组件（Keepalived + MySQL 原生复制）实现故障全自动切换、自动恢复、零数据丢失。兼容 MySQL 5.7 / 8.0 / MariaDB。一、设计思路1.1 核心问题在做这个方案之前，先想清楚三个问题：问题 1：为什么不用 MHA / ProxySQL？MHA 的 Manager 是单点，且切换后 Manager 自动退出，旧主恢复后仍需人工处理ProxySQL 功能强但引入了新的高可用问题（ProxySQL 本身也要做 HA），纯热备场景下得不偿失Keepalived 极其成熟，几万行 C 代码跑了二十年，比任何"智能"方案都可靠问题 2：为什么用双主而不是主从？传统主从 + VIP 的问题：切换后需要 CHANGE MASTER 反转拓扑。双主（互为主从）的好处是：不管 VIP 在哪台，另一台本来就是它的从库，切换时无需改复制拓扑，VIP 漂过去就能直接写。问题 3：如何防止恢复后 VIP 自动回切？这是整个方案最核心的设计点。来看看常规方案的问题：常规方案（仅用 priority + weight）： db-a priority=100 (主), db-b priority=90 (备) db-a 宕机 → db-a=80, db-b=90 → db-b 接管 ✓ db-a 恢复 → db-a=100, db-b=90 → db-a 抢回 ✗ （不必要的第二次中断！）如果加 nopreempt：nopreempt 只对 Keepalived 进程启动时的状态转换生效对 track_script 引起的动态权重变化不生效更糟的是，如果两台都加 nopreempt，连正常的故障切换都会被阻止本方案的解法：让 health check 脚本利用 MySQL 原生的复制 IO 线程状态来判断"主库是否还活着"：check_mysql.sh 逻辑： 1. MySQL 进程是否存活？→ 否 → exit 1（降权） 2. 我是只读（read_only=ON）吗？ - 是 → IO 线程是否在运行？ - 是 → 主库还活着，我安心当备机 → exit 1（降权，不抢占） - 否 → 主库挂了，我可以竞选 → exit 0 - 否 → 我已是主库 → exit 0这样恢复后的节点自动检测到当前主库还活着，主动降低优先级，不会抢占 VIP。1.2 方案架构应用 ──► VIP (192.168.7.33) ──► 当前主库 (read_only=OFF) └─► 备机 (read_only=ON) 双向复制 (GTID + 半同步) | check 脚本监控复制 IO 线程 判断主库是否存活 → 控制优先级1.3 故障切换时序正常状态： db-a (priority=100, read_only=OFF, IO-thread 活跃) db-b (priority=90, read_only=ON, IO-thread 活跃 → check 返回 1 → 实际=70) db-a 宕机后 8 秒内： db-a check 连续失败 2 次 → priority=80 db-b check 发现 IO 线程断开 → 返回 0 → priority=90 90 > 80 → db-b 接管 VIP notify_master 将 db-b read_only 关闭 db-a 恢复： MySQL 启动，read_only=ON（my.cnf 默认） IO 线程连上 db-b → 活跃 → check 返回 1 → priority=80 db-b 仍是 priority=90 80 < 90 → VIP 留在 db-b（不回切！）1.4 Priority 推演表场景db-a 状态db-a 实际 prioritydb-b 状态db-b 实际 priorityVIP 在初始正常read_only=OFF100read_only=ON, IO 活跃70 (90-20)db-adb-a 宕机MySQL 挂了80 (100-20)read_only=ON, IO 断开90db-bdb-a 恢复read_only=ON, IO 活跃80 (100-20)read_only=OFF90db-b（不回切）db-b 宕机read_only=ON, IO 断开100MySQL 挂了70 (90-20)db-a二、环境信息角色IP初始状态Keepalived prioritydb-a192.168.7.31read_only=ON，等 Keepalived 竞选100db-b192.168.7.32read_only=ON，等 Keepalived 竞选90VIP192.168.7.33——注意：两台 my.cnf 都配置 read_only=ON。谁成为 MASTER 谁由 notify 脚本打开写入。这样恢复后的节点总是以只读模式启动，不会出现双主同时可写的窗口。三、多版本兼容速查配置项MySQL 5.7MySQL 8.0MariaDB 10.x过期日志expire_logs_days=7binlog_expire_logs_seconds=604800expire_logs_days=7从库更新log_slave_updateslog_replica_updateslog_slave_updates半同步主插件semisync_master.sosemisync_source.sosemisync_master.so半同步从插件semisync_slave.sosemisync_replica.sosemisync_slave.so半同步主变量rpl_semi_sync_master_enabledrpl_semi_sync_source_enabledrpl_semi_sync_master_enabled复制语句CHANGE MASTER TOCHANGE REPLICATION SOURCE TOCHANGE MASTER TO查看复制状态SHOW SLAVE STATUSSHOW REPLICA STATUSSHOW SLAVE STATUSsuper_read_only支持 (5.7.8+)支持不支持（去掉所有相关行）MASTER_AUTO_POSITION支持支持不支持（用 file+pos）loose- 前缀支持支持支持四、MySQL 配置4.1 db-a /etc/my.cnf[mysqld] # ---- 基础配置 ---- server-id = 1 # 全局唯一，db-b 设为 2 log-bin = mysql-bin # 开启 binlog binlog_format = ROW # 行级复制，保证数据一致性 gtid_mode = ON # 开启 GTID enforce_gtid_consistency = ON # GTID 强制一致性 # ---- 日志与复制 ---- # MySQL 8.0 用 log_replica_updates；5.7/MariaDB 用 log_slave_updates log_replica_updates = ON # MySQL 8.0 用 binlog_expire_logs_seconds；5.7/MariaDB 用 expire_logs_days = 7 binlog_expire_logs_seconds = 604800 # 7 天自动清理 # ---- 自增冲突预防 ---- # 双主架构下必须错开自增值，否则同时插入会冲突 auto_increment_increment = 2 # 步长：2 auto_increment_offset = 1 # 偏移：A 生成奇数 (1,3,5...)；B 设为 2 生成偶数 # ---- 半同步复制（MySQL 8.0）---- # 5.7/MariaDB 请替换为：semisync_master.so / semisync_slave.so # 使用 plugin_load_add 而非 plugin-load，避免覆盖默认插件列表 plugin_load_add = "semisync_source.so;semisync_replica.so" # loose- 前缀：插件未加载时仅警告，不阻止 MySQL 启动 # 避免首次初始化时因变量不存在而报错 loose_rpl_semi_sync_source_enabled = 1 loose_rpl_semi_sync_replica_enabled = 1 rpl_semi_sync_source_timeout = 10000 # 半同步超时 10 秒后降级为异步 # ---- 读写状态 ---- # 初始只读！谁成为 MASTER 由 Keepalived notify 脚本打开写入 read_only = ON super_read_only = ON # MariaDB 删掉此行4.2 db-b 与 db-a 的差异# db-b 仅以下三处不同： server-id = 2 # 唯一 ID auto_increment_offset = 2 # B 生成偶数 (2,4,6...) read_only = ON # 同上，初始只读 super_read_only = ON4.3 MariaDB 特别注意事项# MariaDB 的 my.cnf 去掉这两行： # super_read_only = ON ← 删除，不支持 # plugin_load_add = "..." ← 用下面代替MariaDB 半同步插件在安装时自动加载，通常不需要在 my.cnf 中配置 plugin_load_add。如需手动加载：INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';五、双向复制搭建5.1 创建复制用户（在 db-a 上执行一次即可，会自动同步到 db-b）CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl@123'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES;5.2 建立复制MySQL 8.0 语法：-- 在 db-a 上执行：指向 db-b CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.7.32', SOURCE_USER='repl', SOURCE_PASSWORD='Repl@123', SOURCE_AUTO_POSITION=1; -- 基于 GTID 自动定位 START REPLICA; -- 在 db-b 上执行：指向 db-a CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.7.31', SOURCE_USER='repl', SOURCE_PASSWORD='Repl@123', SOURCE_AUTO_POSITION=1; START REPLICA;MySQL 5.7 语法：-- 将 SOURCE 替换为 MASTER，REPLICA 替换为 SLAVE CHANGE MASTER TO MASTER_HOST='192.168.7.32', MASTER_USER='repl', MASTER_PASSWORD='Repl@123', MASTER_AUTO_POSITION=1; START SLAVE;MariaDB 语法（不支持 MASTER_AUTO_POSITION）：-- 先在 db-b 上查看当前 binlog 位置： SHOW MASTER STATUS; -- 记录 File 和 Position -- 在 db-a 上执行： CHANGE MASTER TO MASTER_HOST='192.168.7.32', MASTER_USER='repl', MASTER_PASSWORD='Repl@123', MASTER_LOG_FILE='mysql-bin.000003', -- 替换为实际值 MASTER_LOG_POS=237; -- 替换为实际值 START SLAVE; -- db-b 指向 db-a 同理5.3 验证-- MySQL 8.0 SHOW REPLICA STATUS\G -- MySQL 5.7 / MariaDB SHOW SLAVE STATUS\G -- 确保以下两项均为 Yes： -- Replica_IO_Running / Slave_IO_Running: Yes -- Replica_SQL_Running / Slave_SQL_Running: Yes六、Keepalived 配置6.1 密码安全加固# 在两台服务器上执行，避免脚本中出现明文密码 mysql_config_editor set --login-path=keepalived_check \ --host=localhost --user=root --password # 输入 MySQL root 密码 # 验证 mysql --login-path=keepalived_check -e "SELECT 1"6.2 db-a /etc/keepalived/keepalived.confvrrp_script chk_mysql { script "/etc/keepalived/check_mysql.sh" interval 2 # 每 2 秒检查一次 weight -20 # 检查失败时扣减 20 点优先级 fall 2 # 连续失败 2 次才触发扣减 rise 2 # 连续成功 2 次才恢复优先级 } vrrp_instance VI_MYSQL { state BACKUP # 两台都设 BACKUP，靠 priority 决出 MASTER interface ens192 # 根据实际网卡名修改 virtual_router_id 51 # 同一组 VRRP 必须相同 priority 100 # 主机 100；备机 90 advert_int 1 # VRRP 通告间隔 1 秒 authentication { auth_type PASS auth_pass MySqlVIP # 两台密码必须一致 } virtual_ipaddress { 192.168.7.33/24 dev ens192 # VIP 地址 } track_script { chk_mysql # 关联健康检查脚本 } notify_master "/etc/keepalived/notify_master.sh" notify_backup "/etc/keepalived/notify_backup.sh" notify_fault "/etc/keepalived/notify_fault.sh" }db-b 配置完全相同，仅 priority 改为 90。6.3 check_mysql.sh —— 核心健康检查脚本#!/bin/bash # ============================================================ # 设计思路（这是整个方案最关键的部分）： # # 常规方案的致命缺陷： # 如果只在健康检查中判断 MySQL 是否存活，那么恢复后的节点 # （优先级更高）会立即抢回 VIP，导致不必要的第二次中断。 # # 如果同时判断 read_only 状态，备机的 read_only=ON 会 # 导致其优先级永远被扣减，主库宕机后 80 > 70 无法切换。 # # 本方案的解法： # 利用 MySQL 复制 IO 线程的状态作为"主库是否还活着"的代理指标。 # - 备机（read_only=ON）+ IO 线程运行中 → 主库活着 → 不抢占 # - 备机（read_only=ON）+ IO 线程中断 → 主库挂了 → 可以接管 # - 主库（read_only=OFF） → 已经是主 → 保持 # # 兼容性： # - MySQL 5.7: Slave_IO_Running # - MySQL 8.0: Replica_IO_Running # - MariaDB: Slave_IO_Running # 脚本同时 grep 两个关键字，覆盖所有版本 # ============================================================ export HOME=/root # Keepalived 不设 HOME，必须显式指定 # 1. 检查 MySQL 进程是否存活 mysqladmin --login-path=keepalived_check ping &>/dev/null || exit 1 # 2. 如果我是只读状态，判断主库是否还活着 READ_ONLY=$(mysql --login-path=keepalived_check -e "SELECT @@read_only" -sN 2>/dev/null) if [ "$READ_ONLY" -eq 1 ]; then # 检查复制 IO 线程：兼容 MySQL 5.7/8.0 和 MariaDB IO_RUNNING=$(mysql --login-path=keepalived_check \ -e "SHOW REPLICA STATUS\G" 2>/dev/null \ | grep -iE "Replica_IO_Running|Slave_IO_Running" \ | awk '{print $2}') if [ "$IO_RUNNING" = "Yes" ]; then # 主库还活着（IO 线程正常接收 binlog） # 我继续当备机，降低优先级，不参与竞选 exit 1 fi # 主库挂了（IO 线程中断），我可以竞选主库 fi exit 06.4 notify_master.sh —— 升级为主库#!/bin/bash # 当本机赢得 VRRP 选举成为 MASTER 时由 Keepalived 自动调用 # 作用：关闭 read_only，允许应用写入 export HOME=/root # 必须！否则 mysql 找不到登录配置 LOG=/var/log/keepalived_notify.log echo "$(date): 升级为 MASTER，关闭 read_only" >> $LOG # MySQL 8.0/5.7 需要同时关闭 super_read_only # MariaDB 不支持 super_read_only → 删掉 ", SET GLOBAL super_read_only=OFF" 部分 mysql --login-path=keepalived_check \ -e "SET GLOBAL read_only=OFF; SET GLOBAL super_read_only=OFF;" \ >> $LOG 2>&16.5 notify_backup.sh —— 降级为备机#!/bin/bash # 当本机失去 MASTER 身份降级为 BACKUP 时由 Keepalived 自动调用 # 作用：开启 read_only，防止双主同时写入 export HOME=/root LOG=/var/log/keepalived_notify.log echo "$(date): 降级为 BACKUP，开启 read_only" >> $LOG mysql --login-path=keepalived_check \ -e "SET GLOBAL read_only=ON; SET GLOBAL super_read_only=ON;" \ >> $LOG 2>&16.6 notify_fault.sh —— 故障时的最后防线#!/bin/bash # 当 VRRP 检测到异常状态（如脑裂）时由 Keepalived 自动调用 # 作用：强制删除本机 VIP，确保不会出现双端同时持有 VIP export HOME=/root LOG=/var/log/keepalived_notify.log echo "$(date): FAULT 状态，强制下线 VIP" >> $LOG /sbin/ip addr del 192.168.7.33/24 dev ens192 2>/dev/null6.7 部署# 赋予执行权限 chmod +x /etc/keepalived/check_mysql.sh chmod +x /etc/keepalived/notify_*.sh # 启动 systemctl start keepalived systemctl enable keepalived # 验证 VIP ip addr show ens192 | grep 192.168.7.33七、填坑实录以下是实际部署中遇到的每一个坑，以及排查思路。坑 1：<<EOF 不加引号，变量被提前展开（致命）现象：脚本写入了，但 VIP 死活不切换。停掉 MySQL 后 Keepalived 毫无反应。排查：cat /etc/keepalived/check_mysql.sh 一看，脚本内容变成了：# $? 变成了字面量 0！永远不会 exit 1 if [ 0 -ne 0 ]; then exit 1 fi # $(mysql ...) 被替换成了写入时的返回值 READ_ONLY=0 # $READ_ONLY 被展开成了空字符串 if [ "" -eq 1 ]; then exit 1 fi根因：cat > file <<EOF 中 heredoc 的分隔符没有用引号括起来，bash 在写入文件前对内容执行了变量替换和命令替换。$?、$(date)、$LOG、$READ_ONLY 全部变成了写入时的字面值。解决：所有 heredoc 使用 <<'EOF'（单引号阻止任何展开）。# 错误 ❌ —— 变量会被展开 cat > /etc/keepalived/check_mysql.sh <<EOF ... EOF # 正确 ✅ —— 内容原样写入 cat > /etc/keepalived/check_mysql.sh <<'EOF' ... EOF教训：写完脚本后立刻 cat 检查内容。如果看到硬编码的数字和日期，那就是 heredoc 没加引号。坑 2：Keepalived 执行脚本时不设置 $HOME现象：VIP 漂移成功了，但 notify_master.sh 执行 mysql --login-path 时报错：ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)注意 (using password: NO)——说明 mysql 客户端根本没读到密码。排查：手工执行脚本正常，但 Keepalived 触发时就失败。原因是 Keepalived 启动的子进程环境变量几乎是空的，$HOME 为空或指向 /，mysql 找不到 ~/.mylogin.cnf。解决：所有脚本开头添加 export HOME=/root。mysql 和 mysqladmin 都需要这个变量。教训：systemd / Keepalived 启动的进程环境变量极少。$HOME、$PATH、$USER 都不要假设存在。坑 3：在健康检查中判断 read_only 导致备机永远无法接管现象：主库宕机后，备机不接管。priority 推演如下：db-a 宕机前：db-a=100, db-b=70 (90-20，因为 read_only=ON → check 失败) db-a 宕机后：db-a=80 (100-20), db-b=70 80 > 70 → VIP 仍在 db-a ✗根因：备机的 read_only=ON 是正常状态，不应该被当作"故障"来扣减优先级。解决：不检查 read_only 状态来判断故障。改用复制 IO 线程状态（见第六节 check_mysql.sh 的设计思路）。坑 4：nopreempt 对 track_script 的动态权重变化无效现象：加了 nopreempt，恢复后的节点仍然抢回 VIP。根因：Keepalived 的 nopreempt 只在 VRRP 实例初次启动时生效（BACKUP → MASTER 的初始转换）。当 track_script 引起优先级动态变化时，nopreempt 不阻止抢占。更糟的是，如果两台都加 nopreempt，连正常的故障切换都会被阻止——备机不会抢占优先级已经降低的主库。解决：不使用 nopreempt，完全依靠 check 脚本中的复制 IO 感知逻辑来控制优先级。坑 5：MariaDB 不支持 super_read_only现象：MariaDB 上执行 SET GLOBAL super_read_only=ON 报错：ERROR 1193 (HY000): Unknown system variable 'super_read_only'解决：所有配置和脚本中出现 super_read_only 的地方，MariaDB 都要删掉：# MySQL 8.0 / 5.7.8+： mysql -e "SET GLOBAL read_only=OFF; SET GLOBAL super_read_only=OFF;" # MariaDB（仅保留 read_only）： mysql -e "SET GLOBAL read_only=OFF;"坑 6：MySQL 8.0 初始化时半同步变量报错现象：mysqld --initialize 时报：unknown variable 'rpl_semi_sync_source_enabled=1'根因：初始化阶段半同步插件尚未安装，MySQL 不认识这些变量。解决：使用 loose- 前缀：# ❌ 初始化报错 rpl_semi_sync_source_enabled = 1 # ✅ 插件未加载时仅警告 loose_rpl_semi_sync_source_enabled = 1坑 7：--initialize 在已有数据目录上失败现象：修改 my.cnf 后重新初始化报错退出。解决：清空数据目录后重新初始化：systemctl stop mysqld rm -rf /var/lib/mysql/* # 注意备份！ mysqld --initialize --user=mysql grep 'temporary password' /var/log/mysqld.log # 查找临时密码 systemctl start mysqld八、VRRP 切换的业务中断问题8.1 问题的本质Keepalived 的 VIP 漂移本质上是网络层切换：VIP 从一个网卡解绑、ARP 广播更新、再绑定到另一个网卡。这个过程必然导致：现有 TCP 连接被 RST（复位）应用必须重建连接在 ARP 缓存刷新前（最多几秒），数据包可能发往旧节点在不改应用代码的前提下，无法做到绝对的零中断。 但可以通过几个层次来大幅缩短中断时间。8.2 方案一：缩短检测窗口 + JDBC 自动重连（改动最小）当前中断时长：≈ 6-8 秒（interval 2 × fall 2 + VRRP 协商 + ARP 刷新）优化后：≈ 2-3 秒Keepalived 调优——把检测参数压到最激进：vrrp_script chk_mysql { script "/etc/keepalived/check_mysql.sh" interval 1 # 每秒检查，原来 2 秒 weight -20 fall 1 # 失败 1 次立刻触发，原来 2 次 rise 2 # 恢复保持 2 次，避免抖动 }fall 设为 1 的风险：MySQL 瞬时抖动可能触发误切换。如果 MySQL 稳定性没问题可以这么设；保守的话保留 fall=2，中断也仅多 1 秒。JDBC 连接串（不改代码，只改连接配置）：# MySQL Connector/J 8.0 jdbc:mysql://192.168.7.33:3306/db?autoReconnect=true&failOverReadOnly=false&maxReconnects=3&initialTimeout=1 # 参数说明： # autoReconnect=true → 连接断开后自动重连 # failOverReadOnly=false → 重连后不设为只读（热备场景不需要） # maxReconnects=3 → 最多重试 3 次 # initialTimeout=1 → 首次重试间隔 1 秒对大多数业务，2-3 秒的短暂中断 + 连接池自动重连已经可以接受。8.3 方案二：加 HAProxy TCP 代理层（推荐，效果最好）在两台 MySQL 服务器上各部署一个 HAProxy（TCP mode），应用连 HAProxy 而不是直连 MySQL：应用 ──► VIP ──► HAProxy (:3307) ──► 本机 MySQL (:3306) │ └─► 对端 MySQL (:3306) [备胎] │ └─ Keepalived 管 VIP └─ HAProxy 管 MySQL 流量分发HAProxy 配置 (/etc/haproxy/haproxy.cfg)：global log /dev/log local0 maxconn 4096 defaults mode tcp # TCP 模式，不做 SQL 解析 timeout connect 3s timeout client 30s timeout server 30s log global frontend mysql_frontend bind 192.168.7.33:3306 # 监听 VIP 的 3306 端口 default_backend mysql_backend backend mysql_backend option tcp-check # TCP 检查：连接 3306 端口成功即认为健康 server db-a 127.0.0.1:3306 check port 3306 inter 1s fall 1 rise 2 server db-b 192.168.7.32:3306 check port 3306 inter 1s fall 1 rise 2 backup两台服务器上 HAProxy 配置几乎相同，只是 server 行里把自己写前面、对端写 backup。为什么这个方案好：HAProxy 持有应用连接，后端 MySQL 切换时应用连接不断开（HAProxy 内部重连）切换检测更快：inter 1s fall 1，1 秒完成不需要 Keepalived 的复杂 check 脚本——HAProxy 自己检测后端可以做到灰度切换：旧连接的查询跑完再切，不丢事务如果还担心 HAProxy 是单点：HAProxy 跟 MySQL 部署在同一台，MySQL 挂了 HAProxy 也没用，不需要额外 HA如果真的要求 HAProxy 也要高可用，可以用 Keepalived 保 HAProxy 的 VIP（就像现在保 MySQL 的 VIP 一样）代价：多部署一个 HAProxy（yum install 一条命令），多一个进程要管理。8.4 方案三：MySQL Router（MySQL 官方方案）MySQL 8.0 + InnoDB ReplicaSet 或 InnoDB Cluster 自带 MySQL Router：应用 ──► MySQL Router (:6446 r/w) ──► 主库 └─► MySQL Router (:6447 r/o) ──► 备库Router 自动检测主备切换，把读写流量指向新主库。但：需要 MySQL 8.0+需要额外部署 Router 进程对 InnoDB ReplicaSet 的支持比 InnoDB Cluster 弱对当前的"纯双主热备"架构来说，Router 有点重了。8.5 四种方案对比 中断时间架构改动额外组件适用场景当前方案（VIP 直连）6-8 秒无无可容忍短暂中断方案一（调优+JDBC重连）2-3 秒无（只改连接串）无大多数业务方案二（+HAProxy）1-2 秒，连接不断加代理层HAProxy要求零感知方案三（MySQL Router）秒级换架构MySQL RouterInnoDB Cluster 用户8.6 建议先做方案一——5 分钟改完，中断压到 2-3 秒。绝大多数业务都能接受如果业务方坚持"不能丢一个连接"，上方案二——加 HAProxy，应用连接跟后端 MySQL 解耦方案三留到将来如果要迁到 InnoDB Cluster 再考虑九、验证清单#测试项操作预期结果1初始状态两台都启动db-a 持有 VIP，read_only=OFF；db-b read_only=ON2主库宕机systemctl stop mysqld 在 db-a8 秒内 VIP 漂移到 db-b，db-b 的 read_only=OFF3主库恢复（不回切）systemctl start mysqld 在 db-adb-a read_only=ON，VIP 留在 db-b4新主库宕机systemctl stop mysqld 在 db-bVIP 漂移到 db-a，db-a 的 read_only=OFF5双向复制SHOW REPLICA STATUS\GIO/SQL 线程均为 Yes，延迟为 06脑裂防护检查两台 VIP同一时刻仅一台持有 VIP7脚本变量完整性cat /etc/keepalived/check_mysql.sh$?、$LOG 等变量未被展开8密码安全grep -r 'password' /etc/keepalived/*.sh无明文密码9notify 日志cat /var/log/keepalived_notify.log无 ERROR 字样十、故障排查速查# 查看 VIP 在哪台 ip addr show ens192 | grep 192.168.7.33 # Keepalived 实时日志 journalctl -u keepalived -f # 切换历史 tail -20 /var/log/keepalived_notify.log # 手动测试 check 脚本（含逐行调试） bash -x /etc/keepalived/check_mysql.sh; echo "exit code: $?" # 模拟 Keepalived 环境执行 notify（干净环境变量） env -i HOME=/root bash /etc/keepalived/notify_master.sh # 查看 MySQL 复制状态 mysql -e "SHOW REPLICA STATUS\G" 2>/dev/null | grep -E "Running|Seconds_Behind|Error" # 查看当前只读状态和 hostname mysql -e "SELECT @@hostname, @@read_only, @@super_read_only" # 验证 mysql_config_editor mysql_config_editor print --login-path=keepalived_check mysql --login-path=keepalived_check -e "SELECT 1" # 检查脚本内容（变量是否被展开） cat /etc/keepalived/check_mysql.sh | grep -E '\$\?|\$\(|\$LOG|\$READ_ONLY'十一、总结这套方案的核心思路只有三点：双主免拓扑切换——不管 VIP 在哪台，另一台本来就是它的从库，省去了 CHANGE MASTER 的麻烦复制 IO 线程感知——利用 MySQL 原生的复制状态判断主库是否存活，优雅地解决了"恢复后不回切"的问题，不需要 nopreempt，不需要 flag 文件最少依赖——整个方案只多了一个 Keepalived（几十万行稳定代码），没有引入任何新的单点部署时记住三条铁律：heredoc 加引号、notify 脚本设 HOME、检查脚本用 IO 线程不要用 read_only。
1. 前言Windows 10 桌面版（专业版/企业版）默认只允许一个活跃的远程桌面会话。当你尝试让第二个用户通过远程桌面连接时，要么会把当前用户挤下线，要么会直接锁屏。这是因为微软在 termsrv.dll 文件中设置了硬编码的单用户限制。要突破这个限制，实现多人同时远程登录、互不影响，最常见的方式是使用开源工具 RDPWrap。本文将完整讲解从系统准备、安装破解到验证配置的全过程，并给出替代方案。2. 系统要求在开始之前，请确认你的系统满足以下条件：项目要求操作系统版本Windows 10 专业版、企业版或教育版（家庭版不支持远程桌面主机功能）远程桌面权限已在“设置 → 系统 → 远程桌面”中开启独立用户账户在“设置 → 账户 → 家庭和其他用户”中，为每位远程用户创建独立的本地账户，并添加到 Remote Desktop Users 组中github开源地址：https://github.com/stascorp/rdpwrap/releases/tag/v1.6.23. 使用 RDPWrap 破解多用户限制RDPWrap 是一个开源项目，通过封装和修补系统文件 termsrv.dll，在不对原始文件直接修改的情况下，让 Windows 桌面版支持多个并发远程桌面会话。⚠️ 重要：RDPWrap 原项目多年来已停止更新，但社区通过维护更新的 rdpwrap.ini 配置文件，使其能持续支持新版本的系统。3.1 详细操作步骤第一步：下载并安装从以下任一资源获取必要文件：官方原版（v1.6.2）：虽然原作者已停更，但安装器仍可正常使用。社区维护版：sebaxakerhtc/rdpwrap，持续更新 rdpwrap.ini 文件，支持更多新版本。推荐：下载社区维护版，直接解压即可使用，省去手动替换配置文件的步骤。以管理员身份运行 install.bat。脚本会自动将程序部署到 C:\Program Files\RDP Wrapper，并安装所需服务。第二步：更新配置文件（最关键的一步）如果用的是官方原版，需要手动替换新的配置文件：停止服务：以管理员身份运行命令 net stop termservice从 sebaxakerhtc/rdpwrap.ini 仓库下载最新的 rdpwrap.ini用下载的文件覆盖 C:\Program Files\RDP Wrapper\rdpwrap.ini启动服务：以管理员身份运行命令 net start termservice如果用的是社区维护版，通常已包含较新的配置文件，可直接进入验证步骤。若不成功，仍可参照上述方法更新。第三步：配置本地组策略按 Win+R 打开运行，输入 gpedit.msc 并回车。定位到：计算机配置 → 管理模板 → Windows 组件 → 远程桌面服务 → 远程桌面会话主机 → 连接。双击 “将远程桌面服务用户限制到单独的远程桌面服务会话”，设置为 “已禁用”。双击 “限制连接的数量”，设置为 “已启用”，并在选项中填入允许的最大并发连接数（例如 5 或 999999）。3.2 安装RDPwrap补丁防火墙会进行拦截3.3 如何验证是否成功？运行 C:\Program Files\RDP Wrapper 目录下的 RDPConf.exe，检查状态面板。如果 所有条目都显示为绿色，说明破解成功。你也可以运行 RDPCheck.exe 进行直观测试：如果能弹出一个新的远程桌面窗口，且没有挤掉当前会话，就说明多用户同时登录功能已生效。4. 常见问题与故障排查4.1 运行 RDPConf.exe 后出现红色或黄色警告这通常是因为 rdpwrap.ini 配置与当前系统版本不兼容。解决方法：按 Win+R，输入 winver 并回车，查看系统详细版本和 内部版本号（OS Build）。访问 sebaxakerhtc/rdpwrap.ini 的 GitHub 仓库，查找是否有针对你内部版本号的更新配置文件。下载该文件，覆盖到 C:\Program Files\RDP Wrapper\rdpwrap.ini。重启远程桌面服务或重启电脑。4.2 重启或系统更新后失效原因：系统大版本更新（如从 22H2 升级到 24H2）会替换部分系统文件，导致 RDPWrap 失效。解决方法：重新运行安装和配置文件更新步骤。
在云原生时代，Docker 已成为应用打包、交付和运行的事实标准。本文将以 CentOS 7 系统为例，详细介绍如何利用阿里云镜像站在线安装 Docker CE、配置国内镜像加速源、安装 Docker Compose，并演示一个典型的多服务容器化部署流程。一、安装 Docker CE（社区版）1.1 安装必要系统工具首先需要安装 yum-utils，它提供了 yum-config-manager 工具，方便我们管理软件源。sudo yum install -y yum-utils1.2 添加阿里云 Docker 软件源使用阿里云提供的官方仓库地址，能显著提升下载速度和稳定性。yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo1.3 安装 Docker 及相关组件执行以下命令安装 Docker 引擎、CLI 工具、容器运行时以及构建插件和 Compose 插件。sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y注：docker-compose-plugin 是 Docker Compose V2 的插件形式，若习惯使用 docker-compose 命令，也可以单独安装传统版本的 Compose（见后文）。1.4 启动 Docker 服务sudo systemctl enable --now docker1.5 校验安装查看 Docker 版本信息，确认安装成功。docker version如果正常显示客户端和服务端版本号，说明 Docker 已正确安装并运行。二、配置国内镜像加速源（重要）由于网络原因，直接从 Docker Hub 拉取镜像可能非常缓慢。通过配置国内镜像加速器，可以大幅提升拉取速度。2.1 创建/编辑 daemon.jsonvim /etc/docker/daemon.json2.2 写入以下内容{ "registry-mirrors": [ "https://51mbg7lq.mirror.aliyuncs.com", "https://9cpn8tt6.mirror.aliyuncs.com", "https://registry.docker-cn.com", "https://a0f4d5484dea442caf5d1193f09abdd4.mirror.swr.myhuaweicloud.com", "https://do.nark.eu.org", "https://dc.j8.work", "https://docker.m.daocloud.io", "https://dockerproxy.com", "https://docker.mirrors.ustc.edu.cn", "https://docker.nju.edu.cn", "https://hub-mirror.c.163.com", "https://mirror.baidubce.com", "https://ccr.ccs.tencentyun.com" ] }说明：这里列出了多个国内常用镜像源（阿里云、华为云、中科大、南京大学、网易、百度、腾讯云等），按优先级排列。你可以根据实际网络情况增删。2.3 重新加载配置并重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker2.4 设置开机自启sudo systemctl enable docker完成上述步骤后，Docker 的基础环境就准备好了。三、安装 Docker Compose（可选）如果你的项目需要使用 docker-compose.yml 进行多容器编排，建议安装 Docker Compose。 若已通过 docker-compose-plugin 安装了 V2 版本，可以直接使用 docker compose 命令（注意中间没有横线）。但传统 docker-compose 命令在很多教程中仍被广泛使用，因此下面也提供独立安装方法。3.1 下载稳定版二进制文件以 v2.5.1 为例（你也可以到 GitHub Releases 查看最新版本号）：sudo curl -L "https://github.com/docker/compose/releases/download/v2.5.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose3.2 赋予执行权限sudo chmod +x /usr/local/bin/docker-compose3.3 添加到 PATH（可选）通常 /usr/local/bin 已经在 PATH 中，若需要手动添加：export PATH=$PATH:/usr/bin/docker-compose3.4 校验安装docker-compose version如果输出版本信息，说明安装成功。四、部署多服务应用（通用示例）在实际项目中，一个应用往往由多个服务组成（如数据库、缓存、消息队列、业务后端等）。下面以典型的 Web 应用 + MySQL + Redis 为例，演示如何使用 Docker Compose 进行容器化部署。假设你的项目目录结构如下：/your-project/docker/ ├── docker-compose.yml ├── app/ │ └── Dockerfile └── ...4.1 进入项目 docker 目录cd /your-project/docker4.2 构建所有镜像docker-compose build4.3 后台启动所有服务docker-compose up -d4.4 查看服务状态docker-compose ps4.5 单服务管理（推荐）当项目较大或需要调试单个服务时，可以分别操作：操作命令示例构建单个镜像（无缓存）docker-compose build --no-cache mysql启动单个服务（前台运行）docker-compose up mysql停止单个服务docker-compose stop mysql启动已停止的单个服务docker-compose start mysql查看单个服务实时日志docker-compose logs -f mysql提示：建议按依赖顺序依次启动：先启动数据库、缓存等基础服务，最后启动业务主服务。五、常见问题与优化建议权限问题：如果不想每次使用 docker 都加 sudo，可以将当前用户加入 docker 用户组：sudo usermod -aG docker $USER newgrp docker镜像拉取失败：检查 /etc/docker/daemon.json 中的镜像源是否可用，可只保留两三个响应最快的地址。服务启动顺序：可以在 docker-compose.yml 中使用 depends_on 定义依赖关系，但注意 depends_on 并不等待服务完全就绪，可能需要配合健康检查（healthcheck）使用。日志管理：长时间运行的服务可能产生大量日志，建议在 daemon.json 中配置日志轮转策略。
具体来说，需要在 mangle 表中标记外部进入的连接，并在 route 表中为这些连接创建一个专属的、指定了源IP和网关的路由，从而强制回程流量从正确的接口出去。⚙️ 详细配置步骤基础网络设置确保所有WAN口已经正确配置了IP地址、网关，并确保网络通畅。标记连接进入 IP -> Firewall -> Mangle 标签页，为从每个公网IP进入的连接打上“连接标记”。Chain: preroutingSrc. Address: 留空（或填写特定公网IP）Dst. Address: 填写你要映射的公网IP地址，子网掩码为 /32In. Interface: 选择该公网IP所在的物理接口（如 ether1）Action: mark connectionNew Connection Mark: 为这个连接起一个名字，例如 conn_public_ip_1Passthrough: yes（必须选“是”，让标记继续传递）为其他公网IP，重复此步骤。标记路由在同一个 Mangle 表中，创建规则，将上一步的“连接标记”转换为“路由标记”。Chain: outputConnection Mark: 选择上一步创建的标记名，例如 conn_public_ip_1Action: mark routingNew Routing Mark: 为路由表起一个名字，例如 route_public_ip_1Passthrough: no为其他公网IP，重复此步骤。创建专属路由表进入 IP -> Routes 标签页，为每个公网IP创建一个新的路由表。Dst. Address: 0.0.0.0/0（默认路由）Gateway: 选择该公网IP对应的网关地址Routing Mark: 选择上一步创建的“路由标记”，例如 route_public_ip_1为其他公网IP，重复此步骤。配置端口映射最后，创建端口映射（dstnat）规则。Chain: dstnatDst. Address: 填写要映射的公网IP地址Protocol: 选择要映射的协议（如 tcp）Dst. Port: 填写要映射的端口号Action: dst-natTo Addresses: 填写内网服务器的IP地址To Ports: 填写内网服务器的端口号📝 完整配置脚本示例假设你有两条线路，线路A的接口是ether1，IP为203.0.113.1，网关203.0.113.254；线路B的接口是ether2，IP为198.51.100.1，网关198.51.100.254。你想把内网192.168.1.100的80端口分别映射到这两个公网IP的80端口上。你可以在New Terminal中执行以下脚本：# --- 1. 标记连接 (Mangle) --- /ip firewall mangle add chain=prerouting dst-address=203.0.113.1 in-interface=ether1 action=mark-connection new-connection-mark=conn_lineA passthrough=yes /ip firewall mangle add chain=prerouting dst-address=198.51.100.1 in-interface=ether2 action=mark-connection new-connection-mark=conn_lineB passthrough=yes # --- 2. 标记路由 (Mangle) --- /ip firewall mangle add chain=output connection-mark=conn_lineA action=mark-routing new-routing-mark=route_lineA passthrough=no /ip firewall mangle add chain=output connection-mark=conn_lineB action=mark-routing new-routing-mark=route_lineB passthrough=no # --- 3. 创建策略路由 (Routes) --- /ip route add dst-address=0.0.0.0/0 gateway=203.0.113.254 routing-mark=route_lineA /ip route add dst-address=0.0.0.0/0 gateway=198.51.100.254 routing-mark=route_lineB # --- 4. 端口映射 (NAT) --- /ip firewall nat add chain=dstnat dst-address=203.0.113.1 protocol=tcp dst-port=80 action=dst-nat to-addresses=192.168.1.100 to-ports=80 /ip firewall nat add chain=dstnat dst-address=198.51.100.1 protocol=tcp dst-port=80 action=dst-nat to-addresses=192.168.1.100 to-ports=80 # --- 5. 配置回程源地址转换 (SRCNAT) --- /ip firewall nat add chain=srcnat out-interface=ether1 action=src-nat to-addresses=203.0.113.1 /ip firewall nat add chain=srcnat out-interface=ether2 action=src-nat to-addresses=198.51.100.1⚠️ 注意事项output vs prerouting：标记路由时，必须使用output链而非prerouting，因为对于发往本地服务或内网的回包，路由决策在output链进行，标记必须作用在这里才能生效。关于 UDP 协议：在处理 UDP 流量时（例如 WireGuard），上述基于 connection-mark 的方法可能偶尔失效。建议改用 IP -> Routing -> Rules 创建基于源地址的策略，或者在防火墙中增加对 UDP 协议的专门处理规则。src-nat vs masquerade：多个公网IP时，必须使用明确的 action=src-nat 并指定 to-addresses，不能使用 masquerade，否则路由器会错误地使用主接口IP发出回程流量。简化配置：如果你的公网IP是直接配置在WAN接口上的（如静态IP），可以创建更简洁的规则，例如：/ip firewall mangle add chain=input in-interface=ether1 action=mark-connection new-connection-mark=conn_lineA passthrough=yes /ip firewall mangle add chain=output connection-mark=conn_lineA action=mark-routing new-routing-mark=route_lineA passthrough=no /ip route add gateway=203.0.113.254 routing-mark=route_lineA此方法利用 input 链标记，同样有效。
无论你是用 ipmitool、sensors 还是 BMC 网页，传感器数据格式五花八门。本文带你一次搞懂！前言服务器硬件监控是运维的基本功。但当你敲下 ipmitool sensor list 或者 sensors 命令时，面对满屏的数字、英文缩写、竖线分隔符，是不是经常一脸懵？为什么有的行有 10 列，有的只有 6 列？那个 na 到底是不是故障？本文将系统地介绍 4 种最常见的传感器输出格式，逐一拆解每一列的真正含义，并教你 3 秒钟快速定位异常。格式一：ipmitool sensor list – 完整阈值格式（10 列）这是最标准、最完整的传感器输出，常见于华为、曙光、HPE 等主流服务器。输出示例+VCORE0 | 1.700 | Volts | ok | 1.000 | 1.100 | 1.200 | 2.500 | 2.600 | 2.700 PCH_Temp | 44.000 | degrees C | ok | -6.000 | -4.000 | -2.000 | 79.000 | 81.000 | 83.000 列拆解（从第 1 列到第 10 列）列号含义说明1传感器名称如 +VCORE0（CPU核心电压）、PCH_Temp（芯片组温度）2当前读数实时值3单位Volts、degrees C、RPM、Watts、Amps、percent、discrete（离散状态）4状态ok=正常，cr=临界，nc=非临界，na=无数据5LNR低不可恢复阈值 – 低于此值硬件可能损坏6LC低临界阈值 – 严重告警7LNC低非临界阈值 – 警告，性能可能下降8UNC高非临界阈值 – 警告，建议检查9UC高临界阈值 – 严重告警10UNR高不可恢复阈值 – 高于此值硬件可能损坏如何判断异常？状态列不是 ok → 立即处理。当前值 < LNC（第7列）或 > UNC（第8列） → 发出警告。当前值 < LC（第6列）或 > UC（第9列） → 严重告警。当前值 < LNR（第5列）或 > UNR（第10列） → 硬件面临损坏风险。注意：未设置的阈值会显示 na，表示该级阈值不存在。格式二：简化阈值格式（6 列）很多服务器（如浪潮 NF5270M3、部分 Supermicro）只输出 低阈值 + 高阈值，没有细分的 LNR/LC/LNC。输出示例CPU0_VTT | 0.990 | Volts | ok | 0.720 | 1.180 P1V1_PCH | 1.090 | Volts | ok | 0.810 | 1.240 DIMM_0 | 29.000 | degrees C | ok | na | 90.000 列拆解（6 列）列号含义1传感器名称2当前读数3单位4状态5低阈值（下限）6高阈值（上限）判断逻辑当前值 < 低阈值 或 > 高阈值 → 异常。若阈值显示 na，表示未设置该方向阈值（例如只有高阈值，没有低阈值）。这种格式简单直观，但缺少“严重程度”分级。建议结合厂商手册了解阈值含义。格式三：离散传感器格式（discrete）有些传感器不是连续的数值，而是状态标志。它们用 十六进制数 表示事件。输出示例CPU0_Status | 0x0 | discrete | 0x8080| na | na | na | na | na | na PSU_Redundant | 0x0 | discrete | 0x0180| na | na | na | na | na | na Fan1_Present | 0x0 | discrete | 0x0280| na | na | na | na | na | na 列含义当前值：通常 0x0 表示正常/无事件。非 0x0 可能表示故障或特定状态（如冗余丢失、设备不在位）。状态列（第4列）：对于离散传感器，这一列常显示 0x**** 格式的事件码，而不是 ok。需要查阅 IPMI OEM 手册才能解析每个 bit 的含义。运维建议大部分离散传感器当前值为 0x0 且状态也是 0x0 或 0x0080 时可视为正常。如果看到 0x01、0x02 等非零值，或状态码有明显变化，需要进一步排查。一个常见的例外：Pwr Unit Redund 显示 0x1200 可能表示电源冗余降级，虽然当前值为 0x0，但状态码给出了详细原因。格式四：lm-sensors 命令输出（Linux 常用）如果你安装了 lm-sensors 包，sensors 命令会提供更人性化的输出，常用于 PC 服务器或工作站。输出示例coretemp-isa-0000 Adapter: ISA adapter Package id 0: +45.0°C (high = +85.0°C, crit = +95.0°C) Core 0: +44.0°C (high = +85.0°C, crit = +95.0°C) Core 1: +45.0°C (high = +85.0°C, crit = +95.0°C) i350bb-pci-0500 Adapter: PCI adapter loc1: +55.0°C (high = +90.0°C, crit = +105.0°C)关键信息当前温度 + 高阈值 (high) + 临界阈值 (crit)超过 high → 警告；超过 crit → 临界报警。没有低阈值（温度通常不会过低）。这种格式非常直观，适合快速查看 CPU、主板、网卡温度。快速识别异常的三步法（适用所有格式）不管哪种格式，按以下步骤检查：扫状态 找 cr（critical）、nc（non-critical）或传感器的状态码非 0x0。 注意：某些简化格式没有状态列，直接看数值。比阈值温度：当前值 > 高阈值 → 过热。电压：当前值超出 [低阈值, 高阈值] → 电源问题。风扇：当前值 = 0 且应该存在风扇 → 停转。盯特殊传感器Margin / Thermal Margin：只要数值为负，就是过热（即使状态是 ok）。DTS（Digital Thermal Sensor）：越负越凉快，接近 0 或正值 → 过热。电源输出：有输入功率但输出功率/电压为 0 → 电源模块故障。真实案例对照案例1：完整阈值格式下的电压波动+12V | 11.900 | Volts | ok | 8.400 | 9.000 | 9.600 | 14.400 | 15.000 | 15.600当前 11.900V，下限 9.600V，上限 14.400V → 完全正常。案例2：简化格式下的内存温度DIMM_0 | 29.000 | degrees C | ok | na | 90.000只有高阈值 90℃，当前 29℃ → 正常。案例3：离散传感器异常PSU2_Status | 0x1 | discrete | 0x0180 | ...当前值 0x1（非零），可能表示 PSU2 故障或未插入。总结对比表格式类型列数典型命令特点完整阈值10ipmitool sensor list（华为/曙光）分级阈值，最详细简化阈值6ipmitool sensor list（浪潮/Supermicro）只有上下限，简单离散状态6~10ipmitool sensor list十六进制状态码，需查手册lm-sensors不定sensors人性化，适合日常查看本文内容基于 x86 架构服务器（Dell、HPE、华为、浪潮、曙光、Supermicro 等）通用实践编写。
在使用Docker容器的过程中，日志管理是一个容易被忽视但又至关重要的环节。默认情况下，Docker会使用json-file日志驱动，并将容器的标准输出（stdout）和标准错误（stderr）全部记录到宿主机上的JSON文件中。如果不加任何限制，这些日志文件会无休止地增长，最终占满磁盘空间，导致容器异常甚至整个宿主机服务瘫痪。本文将详细介绍如何通过修改Docker守护进程的配置文件，为所有容器统一设置日志轮转策略，并解释每个参数的实际含义。为什么需要限制容器日志？防止磁盘写满：一个频繁输出日志的容器，一天可能产生几十GB的日志文件。提升可维护性：单个巨大的日志文件难以用普通文本工具查看，也拖慢日志分析效率。降低I/O压力：轮转后的日志可以压缩或删除，减少对磁盘的持续写入。配置步骤1. 编辑Docker配置文件使用你熟悉的编辑器（如vim、nano）打开Docker的守护进程配置文件：sudo vim /etc/docker/daemon.json如果该文件不存在，可以新建一个。2. 添加日志相关参数在daemon.json中写入以下内容：{ "log-driver": "json-file", "log-opts": { "max-size": "500m", "max-file": "3" } }注意：如果文件中已有其他配置（如镜像加速器、 insecure registries等），只需将log-driver和log-opts作为顶层字段合并进去。例如：{ "registry-mirrors": ["https://your-mirror.com"], "log-driver": "json-file", "log-opts": { "max-size": "500m", "max-file": "3" } }3. 参数详解参数说明log-driver指定日志驱动。json-file是Docker默认驱动，也是我们想要配置的目标。max-size单个日志文件的最大大小。超过该值后会自动轮转（rotate）。示例中的500m表示500兆字节。支持单位：k(KB)、m(MB)、g(GB)。max-file最多保留的日志文件数量。轮转后会保留max-file个旧文件，超出部分将被删除。例如：当前日志container.log，轮转后变为container.log.1、container.log.2、container.log.3，再产生新日志时会删除最早的container.log.3。4. 重启Docker服务配置修改后，需要重新加载并重启Docker守护进程，使配置生效：sudo systemctl daemon-reload sudo systemctl restart docker或者在某些使用service命令的系统中：sudo service docker restart5. 验证配置是否生效可以通过以下命令查看Docker的当前配置，确认日志参数已加载：docker info | grep -A 5 "Logging Driver"期望输出类似：Logging Driver: json-file WARNING: No swap limit support进一步检查某个运行中容器的日志配置：docker inspect <容器名或ID> | grep -A 3 "LogConfig"输出中应显示："LogConfig": { "Type": "json-file", "Config": { "max-file": "3", "max-size": "500m" } }注意事项仅对新容器生效：修改daemon.json并重启Docker后，已经存在的容器不会自动应用新的日志限制。你需要删除并重新创建这些容器（例如docker rm后docker run），或者在创建容器时通过--log-opt参数单独指定。已有日志文件不会被清理：配置只控制未来产生的日志的轮转行为。宿主机上已经积累的旧日志文件需要手动删除，例如：sudo rm /var/lib/docker/containers/*/*.log替代方案：如果不想全局修改，也可以在运行单个容器时直接指定日志选项：docker run -d --log-opt max-size=500m --log-opt max-file=3 your-image日志驱动兼容性：json-file是最常用的驱动，但Docker还支持syslog、journald、fluentd等。如果使用其他驱动，需要确认它们是否支持max-size和max-file（部分驱动不支持）。
在 CentOS 6 运维中，你是否遇到过这样的场景：ntpdate 能正常同步时间，但 ntpq -p 却始终超时，NTP 服务明明在运行却“查无可查”？本文将通过一个真实案例，深入分析背后的原因，给出简洁有效的解决方案，并系统介绍 ntpd 的配置参数与典型场景。一、问题现象某 CentOS 6 服务器在配置 NTP 时间同步时表现出异常行为：[user@host ~]# ntpdate -q ntp-server.example.com server ntp-server.example.com, stratum 2, offset 6.870186, delay 0.03954 15 Apr 10:08:16 ntpdate[18825]: step time server ntp-server.example.com offset 6.870186 sec [user@host ~]# ntpq -p localhost: timed out, nothing received ***Request timed out观察结果：ntpdate -q 可以正常查询远程 NTP 服务器，并成功步进系统时间（偏移约 6.87 秒）。ntpq -p 却始终超时，无法获取本地 NTP 服务的任何状态信息。检查 NTP 服务状态，显示进程正在运行：[user@host ~]# service ntpd status ntpd (pid 11324) is running...服务进程存在，端口也在监听，为什么 ntpq 就是连不上？二、排查过程2.1 确认 NTP 服务是否正常监听[user@host ~]# netstat -ulnp | grep :123 udp 0 0 0.0.0.0:123 0.0.0.0:* 11324/ntpd udp 0 0 :::123 :::* 11324/ntpd输出显示 ntpd 已正确监听在所有接口的 UDP 123 端口，服务本身没有问题。2.2 检查防火墙规则ntpq 需要通过本地回环接口（lo，127.0.0.1）与本地 ntpd 进程通信。如果防火墙屏蔽了回环流量，就会导致通信失败。查看当前 iptables 配置：[user@host ~]# iptables -L -n Chain INPUT (policy DROP) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 10.0.0.0/8 0.0.0.0/0 ACCEPT all -- 192.168.0.0/16 0.0.0.0/0 ...（其他业务网段规则）关键发现：INPUT 链默认策略为 DROP。没有任何规则允许来自 127.0.0.1 或接口 lo 的流量。这意味着所有进入本机的数据包，如果不匹配已有连接或显式允许的源网段，都会被丢弃。而 ntpq 发起的本地连接正是这样一个“全新”的连接。2.3 为什么 ntpdate 可以工作，ntpq 却不行？ntpdate 直接向外部的 NTP 服务器发起 UDP 请求，远程服务器的响应包属于 RELATED,ESTABLISHED 状态，会被第一条 ACCEPT 规则放行，因此能正常完成查询和时间同步。ntpq 与本地 ntpd 的通信是全新的连接：源 IP 127.0.0.1，目的 IP 127.0.0.1，不属于任何已有连接，也不匹配任何显式 ACCEPT 规则，于是被默认 DROP 策略无情丢弃，客户端等待超时。三、解决方案添加一条规则，允许本地回环接口（lo）上的所有流量，并将其插入到 INPUT 链的最前面：iptables -I INPUT 1 -i lo -j ACCEPT验证规则已生效：[user@host ~]# iptables -L INPUT -n --line-number Chain INPUT (policy DROP) num target prot opt source destination 1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 /* lo */ 2 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ...持久化规则（重启不失效）CentOS 6 / RHEL 6 使用以下命令保存规则：service iptables save # 或者 iptables-save > /etc/sysconfig/iptables四、验证恢复再次执行 ntpq -p，成功获取同步状态：[user@host ~]# ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== *ntp-server.example.com .GPS. 2 u 10 64 1 0.039 6.870 0.001问题解决。五、问题总结现象可能原因解决方式ntpdate -q 正常，ntpq -p 超时防火墙阻止本地回环接口（lo）的流量iptables -I INPUT 1 -i lo -j ACCEPTNTP 服务进程存在但无法查询ntpq 依赖本地 socket 通信（127.0.0.1:123）确保 lo 接口不受防火墙限制ntpdate 可以同步但 ntpd 无法同步（另一种情况）ntpd 出站请求也可能被防火墙阻断检查 OUTPUT 链，允许 UDP 123 出站核心要点：ntpdate 是一次性客户端工具，不依赖本地 ntpd，其网络流量是“向外请求+回复”，容易被 RELATED,ESTABLISHED 规则放行。ntpq 需要与本地 ntpd 进程通过回环接口建立新的连接，如果防火墙对 INPUT 链有默认 DROP 策略且未放行 lo，就会失败。生产环境建议：始终为 lo 接口开放防火墙权限，这不仅是 NTP 工具的需要，许多系统管理工具（如 ntpq、chronyc、某些监控 agent 等）都依赖本地回环通信。六、延伸建议对于 CentOS 7 / RHEL 7 及更高版本（使用 firewalld） 默认情况下 firewalld 会放行回环接口，但如果自定义了严格的规则，可以执行：firewall-cmd --permanent --add-service=ntp firewall-cmd --reload或显式确保回环区域（trusted）允许所有流量。推荐使用 chrony 替代 ntpd 较新系统默认使用 chrony，其查询命令为 chronyc sources -v。同样依赖本地回环通信，防火墙问题表现类似。定期监控 NTP 同步状态 不要依赖 ntpdate（该工具已废弃），应使用 ntpq -p 或 chronyc sources 来检查同步健康度。容器 / 虚拟化环境特别注意 某些容器网络插件或虚拟化安全组可能对回环接口进行额外隔离，需要确认 lo 通信未被限制。七、ntpd 配置参数详解及典型案例理解了防火墙与回环接口的关系之后，另一个常见困惑是：ntpd 到底该如何配置才能稳定、安全地同步时间？/etc/ntp.conf 中的 restrict、server、fudge 等参数分别有什么含义？本章节将系统介绍 ntpd 的核心配置参数，并给出面向不同场景的配置案例。7.1 配置文件结构概览/etc/ntp.conf 主要包含以下几类指令：类别关键字作用访问控制restrict控制哪些主机可以查询/修改本机 NTP 服务时间源定义server / peer指定上游时间服务器（客户端模式或对等模式）本地时钟server 127.127.1.0 + fudge使用系统本地时钟作为兜底时间源运行参数driftfile、logfile、statsdir记录时钟漂移、日志、统计信息密钥认证keys、trustedkey、requestkey启用对称密钥认证（防止未授权时间同步）7.2 核心配置参数详解7.2.1 restrict – 访问控制语法：restrict [地址] [mask 子网掩码] [标志...]常用标志：ignore – 完全拒绝所有 NTP 请求nomodify – 拒绝 ntpq / ntpdc 的修改操作（如 ntpq -c "clock" 等），但允许查询notrap – 拒绝 ntrap 控制消息（一般用户不需要）nopeer – 拒绝建立对等关系（不主动与对方同步）noquery – 拒绝所有查询（ntpq 也受影响！）limited – 启用速率限制，防止放大攻击（常与 kod 配合）kod – 收到限制触发时发送 KoD 包重要提醒：noquery 会阻止 ntpq -p 等查询，如果希望本地 ntpq 能正常查看状态，要么对 127.0.0.1 单独放行（不加 noquery），要么不要在 restrict default 中使用 noquery。常见安全配置组合：# 默认拒绝一切修改、查询、对等、trap restrict default nomodify notrap nopeer noquery # 允许本机完全访问（用于 ntpq/ntpdc） restrict 127.0.0.1 restrict ::1 # 允许内网客户端查询（但不能修改） restrict 10.0.0.0 mask 255.0.0.0 nomodify notrap nopeer # 允许上游时间服务器（通常需要允许查询，以便同步） restrict ntp-server.example.com nomodify notrap nopeer7.2.2 server 与 peer – 时间源定义语法：server 地址 [选项...] peer 地址 [选项...]server：客户端模式，本机向上游请求时间，不会将自己的时间提供给上游。peer：对等模式，双方互相同步，通常用于同一网络中的冗余 NTP 节点。常用选项：iburst – 初始同步时快速发送 8 个包，大幅缩短首次同步时间（强烈推荐）prefer – 优先使用该时间源（当多个 server 时，该源权重更高）minpoll / maxpoll – 最小/最大轮询间隔（默认 6=64s，最大 10=1024s）version – 使用 NTP 版本（默认 4）示例：# 标准 pool 用法，推荐 iburst server 0.centos.pool.ntp.org iburst server 1.centos.pool.ntp.org iburst # 公司内部高精度时间源，优先使用 server ntp.corp.local prefer iburst # 对等节点（互为备份） peer 10.0.0.2 iburst7.2.3 driftfile – 时钟漂移文件driftfile /var/lib/ntp/driftntpd 会持续计算系统时钟与真实时间的漂移率（ppm），写入该文件。下次启动时读取，可更快校正时间。必须指定，否则 ntpd 可能无法正常启动。7.2.4 本地时钟（Local Clock）作为备用源当所有上游服务器均不可达时，可以使用本地系统时钟作为最后兜底，避免 NTP 服务完全失效。server 127.127.1.0 # 本地时钟的虚拟 IP fudge 127.127.1.0 stratum 10 # 设置层级为 10（较大，表示不可信）本地时钟的 stratum 通常设为 10 或更大，这样只有在其他更高优先级源（stratum 1~9）都失败时才会被选中。注意：如果配置了本地时钟且未降低其优先级，可能导致本机成为虚假的时间服务器。7.2.5 日志与统计logfile /var/log/ntp.log statsdir /var/log/ntpstats/ statistics loopstats peerstats clockstats开启后可记录时钟调整、对端统计等信息，便于排障。7.3 典型配置案例案例一：仅作为客户端（内网服务器同步）场景：内网多台服务器，需要同步至公司内部的一台 NTP 服务器 ntp.corp.local，同时提供自身查询状态的能力。配置 (/etc/ntp.conf)：driftfile /var/lib/ntp/drift # 访问控制 restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict ::1 # 上游时间源（公司内部） server ntp.corp.local iburst prefer # 本地时钟作为最后备胎 server 127.127.1.0 fudge 127.127.1.0 stratum 10 # 允许内网其他服务器查询本机（可选） restrict 192.168.0.0 mask 255.255.0.0 nomodify notrap nopeer # 日志 logfile /var/log/ntp.log案例二：搭建内网 NTP 服务器（为局域网提供时间服务）场景：一台服务器（10.0.0.1）能访问公网 pool，同时为内网其他主机提供时间服务。配置：driftfile /var/lib/ntp/drift # 严格控制访问 restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict 10.0.0.0 mask 255.255.255.0 nomodify notrap nopeer # 上游公网 pool server 0.pool.ntp.org iburst server 1.pool.ntp.org iburst server 2.pool.ntp.org iburst # 本地时钟（stratum 大，仅应急） server 127.127.1.0 fudge 127.127.1.0 stratum 10 # 开放本机 NTP 服务（默认已监听所有接口，无需额外配置）内网其他客户端配置：server 10.0.0.1 iburst restrict 10.0.0.1 nomodify notrap nopeer noquery # 可选案例三：高可靠对等模式（两台 NTP 服务器互备）场景：两台核心服务器 ntp-a (10.0.0.1) 和 ntp-b (10.0.0.2)，均与外部 pool 同步，同时互为对等节点。ntp-a 配置：driftfile /var/lib/ntp/drift restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict 10.0.0.2 mask 255.255.255.255 nomodify notrap server 0.pool.ntp.org iburst server 1.pool.ntp.org iburst peer 10.0.0.2 iburst # 对等关系 server 127.127.1.0 fudge 127.127.1.0 stratum 10ntp-b 配置：driftfile /var/lib/ntp/drift restrict default nomodify notrap nopeer noquery restrict 127.0.0.1 restrict 10.0.0.1 mask 255.255.255.255 nomodify notrap server 0.pool.ntp.org iburst server 1.pool.ntp.org iburst peer 10.0.0.1 iburst server 127.127.1.0 fudge 127.127.1.0 stratum 107.4 常见配置错误及排查错误现象可能原因解决方法ntpq -p 输出全是 "." 或 LOCAL上游服务器不可达，或 restrict 阻止了本机查询检查网络连通性；确认 restrict 127.0.0.1 存在且无 noquery时间始终无法同步，ntpstat 显示 unsynchronised上游时间源层级比自己高？或防火墙阻断了 UDP 123检查 iptables -L OUTPUT；查看 ntpq -p 中 reach 列是否为 0ntpd 启动后立刻退出driftfile 目录不可写，或 ntp.conf 语法错误检查 /var/lib/ntp 权限；运行 ntpd -c /etc/ntp.conf -n 测试配置同步周期很长（数分钟）默认轮询间隔逐渐增加可以设置 minpoll 4（16秒）加快频率，但会增加网络负担7.5 调试命令速查# 查看当前同步状态（详细） ntpq -p # 查看本地服务版本及系统信息 ntpq -c rv # 查看访问控制列表是否生效 ntpq -c "mrulist" # 强制立即同步（需要重启 ntpd 或等待轮询周期） ntpd -q -g # 前台同步一次后退出（类似 ntpdate） # 监控实时偏移 watch -n 1 ntpq -p7.6 小结ntpd 的配置看似复杂，但掌握 restrict、server、driftfile、fudge 四个核心要素即可应对绝大多数场景。对于生产环境，建议：始终保留 restrict 127.0.0.1，并确保无 noquery。所有 server 行加上 iburst，减少启动同步时间。配置本地时钟作为最后兜底（stratum 10）。防火墙务必放行 lo 接口（见前文第六章节）。定期使用 ntpq -p 或监控工具检查同步健康度。最后，如果你恰好遇到 ntpq 超时但 ntpdate 正常的“诡异”现象，不妨先看一眼防火墙是否放行了 lo —— 往往一条简单的 iptables -I INPUT 1 -i lo -j ACCEPT 就能让一切恢复正常。而要想让 NTP 服务长期稳定运行，一份合理的 ntp.conf 配置同样不可或缺。希望本文能帮助你彻底扫清 NTP 运维路上的这两大障碍。
概述默认模板监控项通过 Zabbix 客户端 + 各种模板可以实现以下监控：类别监控内容CPU使用率、负载、上下文切换等磁盘磁盘空间、I/O、inode 等内存内存使用、swap、缓存等网络网卡流量、丢包、错误等系统进程数、登录用户、运行时长等自定义监控的应用场景默认模板中没有需要的监控项特定业务指标自定义应用程序状态特定硬件设备模板中监控项过多精简监控项，减少资源消耗只关注关键指标自定义监控全流程┌─────────────────────────────────────────────────────────────┐ │ 客户端 │ │ 1. 通过命令/脚本取出对应的值 │ │ 2. 按 Zabbix 格式书写配置文件，创建键值对 │ │ 3. 重启客户端并测试键值 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 服务端 │ │ 1. 服务端测试键值是否可用 │ │ 2. Web 界面：键值与监控项关联 │ │ 3. Web 界面：配置触发器 │ │ 4. Web 界面：创建图形 │ └─────────────────────────────────────────────────────────────┘详细步骤步骤位置操作说明1客户端通过命令/脚本取出对应的值获取监控数据2客户端创建键值对配置文件键值对 key 用于获取客户端数据3客户端重启客户端并测试本地验证4服务端测试键值是否可用使用 zabbix_get5Web键值与监控项关联创建监控项6Web配置触发器设置告警条件7Web创建图形可视化展示实战案例案例一：监控 MySQL 3306 端口1. 确定监控命令# 检查 3306 端口是否存在 ss -lntpu | grep -w 3306 | wc -l # 返回 1 表示端口存在，返回 0 表示不存在2. 创建配置文件# 进入配置目录 cd /etc/zabbix/zabbix_agent2.d/ # 创建配置文件 cat > 3306.conf << 'EOF' # 格式：UserParameter=key,cmd # UserParameter=固定写法，表示要自定义键值 # key 表示键值名称，建议使用"单词.单词"格式 # cmd 表示命令或脚本 UserParameter=check.mysql,ss -lntpu | grep -w 3306 | wc -l EOF3. 重启客户端systemctl restart zabbix-agent24. 本地测试zabbix_agent2 -t check.mysql # 预期输出： # check.mysql [s|1]5. 服务端测试# 安装测试工具 yum install -y zabbix-get # 测试键值 zabbix_get -s 192.168.5.134 -k check.mysql # 预期输出：1 或 06. Web 界面配置创建监控项：进入 Configuration → Hosts → 选择主机 → Items点击 Create item填写信息：Name: MySQL 3306 Port StatusKey: check.mysqlType of information: Numeric (unsigned)Update interval: 1m创建触发器：进入 Triggers 选项卡点击 Create trigger填写信息：Name: MySQL 3306 port is downExpression: {Host:check.mysql.last()}=0Severity: High案例二：带参数的自定义监控目标检查指定用户是否存在异地登录。实现思路使用 lastlog 命令获取用户最后登录的 IP在服务端通过触发器进行对比判断命令选择# lastlog 可以显示所有用户的最后登录信息 lastlog | grep -w root | awk '{print $3}' # 输出示例：192.168.1.100创建带参数的配置cat > /etc/zabbix/zabbix_agent2.d/user_login.conf << 'EOF' # 注意：使用 $$3 而不是 $3，避免与 Zabbix 参数冲突 UserParameter=user.login.check[*],lastlog | grep -w "$1" | awk '{print $$3}' EOF测试# 本地测试 zabbix_agent2 -t user.login.check[root] # 服务端测试 zabbix_get -s 192.168.5.134 -k 'user.login.check[root]'Web 配置创建监控项：Key: user.login.check[root]Type: Character创建触发器：表达式：{Host:user.login.check[root].str(192.168.1.100)}=0 说明：如果返回值不是 192.168.1.100，则触发告警自定义模板为什么要使用模板实现监控项、触发器、图形的批量复用便于管理和维护支持多主机快速部署创建步骤1. 创建模板进入 Configuration → Templates点击 Create template填写信息：Template name: Template Custom MySQL MonitorGroups: Templates/Databases2. 添加监控项在模板中添加之前创建的监控项：Name: MySQL Port StatusKey: check.mysqlType: Zabbix agent3. 添加触发器Name: MySQL is not runningExpression: {Template Custom MySQL Monitor:check.mysql.last()}=04. 添加图形进入 Graphs 选项卡点击 Create graph选择监控项添加到图形5. 模板关联主机进入 Configuration → Hosts选择主机 → Templates 选项卡选择创建的模板 → Add → Update监控报警报警方式对比报警方式企业应用场景费用邮件个人邮箱、企业邮箱免费企业微信-告警应用需要企业微信免费钉钉与阿里云集成免费短信阿里云短信服务~0.045元/条电话紧急告警收费OneAlert第三方报警平台免费（有限制）/收费邮件告警配置1. 配置报警媒介类型进入 Administration → Media types → Email配置 SMTP 服务器信息：SMTP server: smtp.example.comSMTP server port: 587SMTP helo: example.comSMTP email: zabbix@example.comAuthentication: 用户名密码2. 配置用户报警媒介进入 Administration → Users → 选择用户进入 Media 选项卡点击 Add：Type: EmailSend to: user@example.com3. 配置动作进入 Configuration → Actions → Trigger actions创建或编辑动作配置 Operations 发送邮件微信告警配置1. 创建告警脚本创建 /usr/local/share/zabbix/alertscripts/wechat.py：#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Zabbix WeChat Alerts Scripts import requests import sys import os import json import logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s, %(filename)s, %(levelname)s, %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename=os.path.join('/tmp', 'wechat.log'), filemode='a' ) # 企业微信配置（需要修改） CORP_ID = 'wxd074861951c67ba6' APP_SECRET = 'QtraZrI936DZ0jZ3aSWTZ-lFVheAMgLmq3toM4B9U1A' AGENT_ID = 1 def get_access_token(): """获取 access_token""" url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={CORP_ID}&corpsecret={APP_SECRET}' resp = requests.get(url) return resp.json()['access_token'] def send_message(access_token, party_id, subject, message): """发送消息""" url = f'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}' data = { "toparty": party_id, "msgtype": "text", "agentid": AGENT_ID, "text": { "content": f"{subject}\n\n{message}" }, "safe": 0 } resp = requests.post(url, data=json.dumps(data)) return resp.json() if __name__ == '__main__': if len(sys.argv) < 4: print("Usage: wechat.py <party_id> <subject> <message>") sys.exit(1) party_id = sys.argv[1] subject = sys.argv[2] message = sys.argv[3] try: token = get_access_token() result = send_message(token, party_id, subject, message) logging.info(f'Sent to: {party_id}, Subject: {subject}, Message: {message}') print(json.dumps(result)) except Exception as e: logging.error(f'Error: {str(e)}') print(f'Error: {str(e)}') sys.exit(1)2. 安装依赖# 安装 Python3 和 pip yum install -y python3 python3-pip # 安装 requests 库 pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple requests3. 测试脚本cd /usr/local/share/zabbix/alertscripts/ python3 wechat.py "部门ID" "测试标题" "测试消息内容"4. 配置报警媒介类型进入 Administration → Media types → Create media type选择 Type: Script填写信息：Name: WeChatScript name: wechat.pyScript parameters:{ALERT.SENDTO} # 发给谁{ALERT.SUBJECT} # 报警标题{ALERT.MESSAGE} # 报警内容5. 配置告警消息模板故障消息模板：故障目前已经解决时间: {EVENT.RECOVERY.TIME} {EVENT.RECOVERY.DATE} 故障名称: {EVENT.NAME} 故障经历多久: {EVENT.DURATION} 故障主机: {HOST.NAME} 故障级别: {EVENT.SEVERITY} 故障ID: {EVENT.ID} {TRIGGER.URL}常用宏变量宏说明{ALERT.SENDTO}告警接收人{ALERT.SUBJECT}告警标题{ALERT.MESSAGE}告警内容{EVENT.NAME}事件名称{EVENT.TIME}事件发生时间{EVENT.DATE}事件发生日期{EVENT.DURATION}事件持续时间{EVENT.RECOVERY.TIME}恢复时间{EVENT.RECOVERY.DATE}恢复日期{HOST.NAME}主机名{EVENT.SEVERITY}严重程度{EVENT.ID}事件 ID常用内置键值参考键值说明agent.ping检查 Agent 是否存活system.cpu.utilCPU 使用率system.cpu.loadCPU 负载vm.memory.size内存大小/使用率vfs.fs.size文件系统大小/使用率net.if.in网卡入流量net.if.out网卡出流量proc.num进程数量system.uptime系统运行时间system.users.num登录用户数
概述Zabbix 支持两种主要的工作模式：被动模式（Passive） 和 主动模式（Active）。这两种模式是站在 Zabbix Agent 角度来说的工作模式。被动模式工作原理在被动模式下，Zabbix Agent 被动接受 Zabbix Server 周期性发送过来的数据收集指令：Zabbix Server 根据主机关联的模板中的监控项和数据采集间隔时间，周期性打开随机端口向 Zabbix Agent 服务器的 10050 端口发起 TCP 连接发送获取监控项数据的指令Zabbix Agent 接收到指令后采集数据并返回特点：Zabbix Server 发送什么指令，Zabbix Agent 就收集什么数据Zabbix Server 什么时候发送，Zabbix Agent 就什么时候采集Zabbix Server 不发送，Zabbix Agent 就一直不响应被动模式的优点✅ 配置简单，安装后即可使用✅ Zabbix 的默认工作模式✅ 无需额外配置被动模式的缺点❌ 会加大 Zabbix Server 的工作量❌ 在数百甚至数千台服务器的环境下，Zabbix Server 需要轮询向每个 Agent 发送数据采集指令❌ Zabbix Server 负载高时可能导致不能及时获取最新数据被动模式端口状态Zabbix Server (随机端口) → Zabbix Agent:10050Server 端会产生大量随机端口连接。被动模式配置Zabbix Agent 默认即为被动模式，只需配置 Server 参数：# /etc/zabbix/zabbix_agent2.conf Server=192.168.5.3主动模式工作原理在主动模式下，Zabbix Agent 主动向 Zabbix Server 的 10051 端口发起 TCP 连接请求：Zabbix Agent 主动连接到 Zabbix Server获取自己的监控项和数据采集间隔周期时间根据监控项主动采集数据将数据返回给 Zabbix Server特点：必须配置 ServerActive 指定 Zabbix Server 的 IP 或主机名Agent 主动获取监控项列表并定期上报数据不再需要 Server 向 Agent 发起连接请求主动模式的优点✅ 减轻 Zabbix Server 打开的本地随机端口和进程数✅ 在一定程度上减轻 Zabbix Server 的压力✅ 适合大规模监控环境✅ 支持 NAT/防火墙后的主机监控主动模式的缺点❌ 配置相对复杂❌ Zabbix 6.0 及以下版本主动模式主机可用性显示灰色（Zabbix 6.2+ 已修复）❌ 某些监控项不支持主动模式主动模式配置编辑 /etc/zabbix/zabbix_agent2.conf：PidFile=/var/run/zabbix/zabbix_agent2.pid LogFile=/var/log/zabbix/zabbix_agent2.log LogFileSize=0 Server=192.168.5.3 ServerActive=192.168.5.3 Hostname=192.168.5.134 Include=/etc/zabbix/zabbix_agent2.d/*.conf ControlSocket=/tmp/agent.sock Include=./zabbix_agent2.d/plugins.d/*.conf关键配置项：配置项说明Server被动模式服务端地址（建议同时配置，用于远程命令等）ServerActive主动模式服务端地址Hostname本机标识，必须与 Zabbix Server 配置的主机名一致版本注意事项重要：Zabbix 6.0 及以下版本使用主动模式时，主机可用性可能一直显示为灰色。建议升级到 Zabbix 6.2 或更高版本。模式对比特性被动模式主动模式连接方向Server → AgentAgent → ServerServer 端口随机端口10051Agent 端口10050随机端口配置复杂度简单较复杂Server 负载高低适用规模中小规模大规模NAT/防火墙支持受限良好监控项支持全部部分不支持Zabbix Proxy架构图Zabbix Agent → Zabbix Proxy → Zabbix Server ↓ Proxy DatabaseZabbix Proxy vs Zabbix Server功能Zabbix ProxyZabbix Server轻量级✅ 是相对重量级图形界面❌ 无✅ 带图形控制界面独立工作✅ 可以独立采集数据并存储✅ 数据采集、存储、分析、展示于一体易维护✅ 设置完成后基本无需管理维护也不难独立数据库保留少量最近数据保留指定时间内的所有数据报警通知❌ 不发送邮件通知✅ 支持邮件、短信等告警机制版本要求重要：Zabbix Proxy 的大版本必须和 Zabbix Server 版本一致，否则会导致兼容性问题。安装方式方式一：YUM 安装# 安装依赖 yum -y install OpenIPMI-libs fping libevent net-snmp-libs unixODBC pcre2 # 安装 Zabbix Proxy rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/6.0/rhel/7/x86_64/zabbix-proxy-mysql-6.0.38-release2.el7.x86_64.rpm方式二：编译安装# 创建用户 useradd zabbix -s /usr/sbin/nologin # 编译配置 cd /usr/local/src/zabbix-6.0.x ./configure --prefix=/usr/sbin/zabbix_proxy \ --enable-proxy --enable-agent \ --with-mysql --with-net-snmp \ --with-libcurl --with-libxml2 make install创建 Systemd 服务文件cat > /lib/systemd/system/zabbix-proxy.service << 'EOF' [Unit] Description=Zabbix Proxy After=syslog.target network.target mysqld.service [Service] Environment="CONFFILE=/etc/zabbix/zabbix_proxy.conf" EnvironmentFile=-/etc/sysconfig/zabbix-proxy Type=forking Restart=on-failure PIDFile=/run/zabbix/zabbix_proxy.pid KillMode=control-group ExecStart=/usr/sbin/zabbix_proxy -c $CONFFILE ExecStop=/bin/kill -SIGTERM $MAINPID RestartSec=10s [Install] WantedBy=multi-user.target EOF部署数据库# 创建数据库（主动模式） create database zabbix_proxy_active character set utf8 collate utf8_bin; # 创建数据库（被动模式） create database zabbix_proxy_passive character set utf8 collate utf8_bin; # 创建用户 grant all on zabbix_proxy_active.* to 'proxy'@'%' identified by 'zzjjhh123';导入数据库# 主动模式数据库导入 zcat /usr/share/doc/zabbix-proxy-mysql/schema.sql.gz | \ mysql -uproxy -p123456 -h172.31.0.104 zabbix_proxy_activeProxy 配置文件被动模式配置# /etc/zabbix/zabbix_proxy.conf # 0为主动，1为被动 ProxyMode=1 # Zabbix Server 服务器的地址或主机名 Server=172.31.0.101 # 代理服务器名称，需要与 Zabbix Server 添加代理时的 proxy name 一致 Hostname=magedu-jiege-proxy-passive # Zabbix Proxy 监听端口 ListenPort=10051 LogFile=/var/log/zabbix/zabbix_proxy.log # 允许 Zabbix Server 执行远程命令 EnableRemoteCommands=1 # 数据库配置 DBHost=172.31.0.104 DBName=zabbix_proxy_passive DBUser=proxy DBPassword=123456 DBPort=3306 # 数据保留时间（小时） ProxyLocalBuffer=720 ProxyOfflineBuffer=720 # 心跳检测间隔（秒） HeartBeatFrequency=60 # 从 Zabbix Server 获取监控项信息的间隔（秒） ConfigFrequency=5 # 数据发送的时间间隔（秒） DataSenderFrequency=5 # 启动的数据采集器数量 StartPollers=20 StartPingers=3 # Java Gateway 配置（如需监控 Java） JavaGateway=172.31.0.104 JavaGatewayPort=10052 StartJavaPollers=20 # 缓存配置 CacheSize=2G HistoryCacheSize=2G HistoryIndexCacheSize=128M # 超时设置 Timeout=30 LogSlowQueries=3000主动模式配置# 0为主动，1为被动 ProxyMode=0 # 其他配置与被动模式类似 Server=172.31.0.101 ServerPort=10051 Hostname=magedu-jiege-proxy-active ...启动 Proxysystemctl enable zabbix-proxy systemctl start zabbix-proxy systemctl status zabbix-proxy选择建议场景推荐模式监控主机 < 100 台被动模式监控主机 > 500 台主动模式跨地域/跨机房监控主动模式 + ProxyNAT/防火墙后主机主动模式高安全要求环境主动模式快速部署测试被动模式
服务端配置1. 重要文件和目录文件/目录说明/etc/zabbix/zabbix_server.conf服务端主配置文件/var/log/zabbix/zabbix_server.log服务端日志文件/usr/lib/systemd/system/zabbix-server-mysql.serviceSystemd 服务文件（编译安装需手动创建，yum 安装自动生成）/usr/local/share/zabbix/alertscripts/告警脚本目录（如微信推送脚本）2. 配置文件详解2.1 基本配置示例# 查看生效的配置项 grep '^[a-Z]' /etc/zabbix/zabbix_server.conf典型配置：ListenPort=10051 LogFile=/var/log/zabbix/zabbix_server.log DBHost=localhost DBName=zabbix DBUser=alick DBPassword=zzjjhh123 Timeout=4 LogSlowQueries=3000 StatsAllowedIP=127.0.0.12.2 配置项说明配置项说明默认值ListenPort服务端监听端口10051LogFile日志文件位置-DBHost数据库服务器地址localhostDBName数据库名称zabbixDBUser数据库用户名-DBPassword数据库密码-DBPort数据库端口3306Timeout监控项超时时间（秒）3LogSlowQueries慢查询日志阈值（毫秒）0（不记录）StatsAllowedIP允许查看统计信息的 IP127.0.0.1StartPollers轮询进程数5StartTrapperstrapper 进程数5StartDiscoverers自动发现进程数13. 服务端常用操作# 启动服务 systemctl start zabbix-server # 停止服务 systemctl stop zabbix-server # 重启服务 systemctl restart zabbix-server # 查看状态 systemctl status zabbix-server # 查看日志 tail -f /var/log/zabbix/zabbix_server.log # 测试配置文件 zabbix_server -T客户端配置1. 重要文件和目录文件/目录说明/etc/zabbix/zabbix_agent2.confAgent2 主配置文件/etc/zabbix/zabbix_agent2.d/子配置文件目录，用于自定义监控/usr/lib/systemd/system/zabbix-agent2.serviceSystemd 服务文件/var/log/zabbix/日志目录/var/run/zabbix/PID 文件目录2. 安装文件列表rpm -ql zabbix-agent2主要文件：/etc/logrotate.d/zabbix-agent2 /etc/zabbix/zabbix_agent2.conf /etc/zabbix/zabbix_agent2.d/ /etc/zabbix/zabbix_agent2.d/plugins.d/ /usr/lib/systemd/system/zabbix-agent2.service /usr/sbin/zabbix_agent2 /usr/share/doc/zabbix-agent2-6.0.38/ /var/log/zabbix /var/run/zabbix3. 配置文件详解3.1 基本配置示例# 查看生效的配置项 grep '^[a-Z]' /etc/zabbix/zabbix_agent2.conf典型配置：PidFile=/run/zabbix/zabbix_agent2.pid LogFile=/var/log/zabbix/zabbix_agent2.log LogFileSize=0 Server=127.0.0.1 ServerActive=127.0.0.1 Hostname=Zabbix server Include=/etc/zabbix/zabbix_agent2.d/*.conf PluginSocket=/run/zabbix/agent.plugin.sock ControlSocket=/run/zabbix/agent.sock Include=./zabbix_agent2.d/plugins.d/*.conf3.2 关键配置项说明配置项说明Server被动模式：允许连接的服务端 IP 地址，多个 IP 用逗号分隔ServerActive主动模式：服务端 IP 地址或主机名Hostname本机标识，必须与 Zabbix Server 配置的主机名一致ListenPort监听端口（默认 10050）ListenIP监听 IP 地址Include包含的其他配置文件4. 客户端常用操作# 启动服务 systemctl start zabbix-agent2 # 停止服务 systemctl stop zabbix-agent2 # 重启服务 systemctl restart zabbix-agent2 # 查看状态 systemctl status zabbix-agent2 # 查看日志 tail -f /var/log/zabbix/zabbix_agent2.log # 测试配置 zabbix_agent2 -t agent.ping # 查看版本 zabbix_agent2 --version监控任意主机流程整体流程安装客户端 → 修改配置文件 → Web 页面添加主机 → 关联模板 → 调试验证1. CentOS/RHEL 系统1.1 安装客户端# 方式一：直接安装 RPM 包 rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/6.0/rhel/7/x86_64/zabbix-agent2-6.0.3-1.el7.x86_64.rpm # 方式二：配置 YUM 源后安装 rpm -ivh https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/6.0/rhel/7/x86_64/zabbix-release-6.0-1.el7.noarch.rpm yum install -y zabbix-agent21.2 修改配置# 查看当前配置 sed 's#Server=127.0.0.1#Server=192.168.5.3#g' /etc/zabbix/zabbix_agent2.conf # 确认无误后执行修改 sed -i 's#Server=127.0.0.1#Server=192.168.5.3#g' /etc/zabbix/zabbix_agent2.conf sed -i 's#ServerActive=127.0.0.1#ServerActive=192.168.5.3#g' /etc/zabbix/zabbix_agent2.conf sed -i 's#Hostname=Zabbix server#Hostname=spug#g' /etc/zabbix/zabbix_agent2.conf1.3 启动服务systemctl enable zabbix-agent2 systemctl start zabbix-agent22. Debian/Ubuntu 系统2.1 安装客户端# 下载并安装源配置包 wget https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/6.0/debian/pool/main/z/zabbix-release/zabbix-release_6.0-3%2Bdebian10_all.deb dpkg -i zabbix-release_6.0-3+debian10_all.deb # 安装客户端 apt update apt install -y zabbix-agent2.2 修改配置sed -i 's#Server=127.0.0.1#Server=192.168.5.3#g' /etc/zabbix/zabbix_agentd.conf sed -i 's#ServerActive=127.0.0.1#ServerActive=192.168.5.3#g' /etc/zabbix/zabbix_agentd.conf sed -i 's#Hostname=Zabbix server#Hostname=bt#g' /etc/zabbix/zabbix_agentd.conf2.3 启动服务systemctl enable zabbix-agent systemctl start zabbix-agent3. Web 页面添加主机登录 Zabbix Web 界面进入 Configuration → Hosts点击 Create host填写主机信息：Host name: 与客户端配置中的 Hostname 一致Visible name: 显示名称（可选）Groups: 选择或创建主机组Interfaces: 添加 Agent 接口（IP 地址和端口）在 Templates 选项卡中关联模板点击 Add 保存常用命令服务端命令# 测试与客户端连接 zabbix_get -s <客户端IP> -p 10050 -k "system.cpu.load[percpu,avg1]" # 查看服务端版本 zabbix_server --version客户端命令# 测试键值 zabbix_agent2 -t <key> # 示例 zabbix_agent2 -t agent.ping zabbix_agent2 -t system.cpu.load # 前台运行（调试使用） zabbix_agent2 -f