📌 背景描述

在日常运维中,发现一台 CentOS 7 生产服务器的系统负载异常,CPU 和内存占用持续偏高。通过 ps 命令排查发现,系统内积压了数以百计的 freshclam 和 freshclam-sleep 进程,部分进程的运行时间(etime)甚至长达 40 到 80 天。

由于服务器承载着等保(网络安全等级保护)合规的防病毒要求,必须彻底解决此问题,并重新构建一套合规、稳定且低资源的定时查杀机制。


🔍 第一阶段:紧急止血与根因分析

1. 紧急清理僵尸进程

首先清理堆积的更新进程,恢复服务器正常运行:

sudo killall freshclam
sudo pkill -f "freshclam-sleep"

2. 排查堆积根源

清理后发现,即使重启了 clamav-freshclam 服务,手动执行更新依然失败,日志报 403 Forbidden 错误。

根本原因有两个:

  1. 版本 EOL:服务器上使用的是 ClamAV 0.103.11,该版本已于 2025 年 9 月停止支持(EOL),官方 CDN 彻底拦截了旧版本的更新请求。
  2. 双重触发:系统中存在 /etc/cron.d/clamav-update,每 3 小时触发一次 freshclam-sleep,与 systemd 服务产生冲突,导致网络卡顿时不断重试并堆积。

3. 为什么没有直接升级到新版?

尝试升级至 ClamAV 1.0.9+(COPR/Atomicorp 源),但发现 CentOS 7 的 glibc 版本过低,强行安装新版会导致依赖缺失及运行时报错。

最终决策:采用**“重装 EPEL 兼容版本 + 配置 Microsoft 镜像源”**的方案,绕过官方封锁,恢复病毒库更新。


🛠️ 第二阶段:修复病毒库更新与清理冲突

1. 重新安装 ClamAV 及组件

sudo yum makecache fast
sudo yum install -y clamav clamav-update clamav-lib clamav-filesystem clamd

2. 配置 Microsoft 镜像源绕过封锁

编辑 /etc/freshclam.conf,在文件末尾添加微软提供的私有镜像地址:

sudo tee -a /etc/freshclam.conf << 'EOF'
PrivateMirror https://packages.microsoft.com/clamav/
EOF

手动验证更新是否成功:

sudo rm -f /var/lib/clamav/freshclam.dat
sudo freshclam --verbose

日志显示 Database updated 或 bytecode.cvd updated,且不再出现 403,说明镜像生效。

3. 移除旧的 Cron 任务(根治堆积)

删除导致进程雪崩的旧版定时任务,让 systemd 成为唯一的更新入口:

sudo mv /etc/cron.d/clamav-update /etc/cron.d/clamav-update.bak
sudo systemctl restart clamav-freshclam

🛡️ 第三阶段:等保合规定时扫描配置

为了满足等保对“定期查杀”的要求,同时避免影响生产业务,采用 “每日增量 + 每周全量” 的策略。通过 systemd timer 配合 flock 锁,以及 logrotate 控制磁盘占用。

1. 创建扫描脚本

创建 /usr/local/sbin/clamav-scan.sh,内置防重叠锁,优先使用 clamdscan 降低资源占用:

sudo tee /usr/local/sbin/clamav-scan.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="/var/log/clamav"
mkdir -p "$LOG_DIR"

# 根据你的生产环境调整扫描目标
DAILY_TARGETS="/var/www /home /srv"
WEEKLY_TARGETS="/"
EXCLUDES=(--exclude-dir='^/(proc|sys|dev|run)' --exclude-dir='node_modules' --exclude-dir='.git')

MODE="${1:-daily}"

# 使用 flock 加锁,防止任务重叠
LOCK_FILE="/var/run/clamav_scan.lock"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
    echo "$(date): Previous scan still running, skipping this '$MODE' run." >> "$LOG_DIR/scan-skip.log"
    exit 0
fi

if [ "$MODE" = "weekly" ]; then
    TARGETS="$WEEKLY_TARGETS"
    LOG_FILE="$LOG_DIR/scan-weekly-$(date +%F).log"
else
    TARGETS="$DAILY_TARGETS"
    LOG_FILE="$LOG_DIR/scan-daily-$(date +%F).log"
fi

# 优先使用 clamdscan(速度快,占用低),降级为 clamscan
if systemctl is-active --quiet clamd@scan; then
    SCANNER="clamdscan --fdpass"
else
    SCANNER="clamscan"
fi

$SCANNER -ri --multiscan --log="$LOG_FILE" "${EXCLUDES[@]}" "$TARGETS" || true

if grep -q "FOUND" "$LOG_FILE"; then
    if command -v mail &>/dev/null; then
        mail -s "[$(hostname)] ClamAV malware found in $MODE scan" root < "$LOG_FILE"
    fi
fi
EOF

sudo chmod +x /usr/local/sbin/clamav-scan.sh

2. 配置 Systemd 定时任务

每日增量扫描(凌晨 2:30)

sudo tee /etc/systemd/system/clamav-scan-daily.service << 'EOF'
[Unit]
Description=Daily ClamAV Incremental Scan
After=clamd@scan.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/clamav-scan.sh daily
EOF

sudo tee /etc/systemd/system/clamav-scan-daily.timer << 'EOF'
[Unit]
Description=Run daily ClamAV incremental scan

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

每周全量扫描(周日凌晨 3:00)

sudo tee /etc/systemd/system/clamav-scan-weekly.service << 'EOF'
[Unit]
Description=Weekly ClamAV Full Scan
After=clamd@scan.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/clamav-scan.sh weekly
EOF

sudo tee /etc/systemd/system/clamav-scan-weekly.timer << 'EOF'
[Unit]
Description=Run weekly ClamAV full scan

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

3. 日志轮转(防磁盘爆满)

创建 /etc/logrotate.d/clamav-scan,仅保留最近 4 周的扫描日志:

sudo tee /etc/logrotate.d/clamav-scan << 'EOF'
/var/log/clamav/scan-*.log {
    weekly
    rotate 4
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

/var/log/clamav/scan-skip.log {
    monthly
    rotate 3
    compress
    missingok
    notifempty
    copytruncate
}
EOF

4. 启用并验证定时器

sudo systemctl daemon-reload
sudo systemctl enable --now clamav-scan-daily.timer
sudo systemctl enable --now clamav-scan-weekly.timer

# 验证状态
systemctl list-timers --all | grep clamav-scan

⚠️ 避坑记录(Troubleshooting)

在配置 systemd 服务时,遇到了 Failed to execute operation: Bad message 报错。通过 systemd-analyze verify <file> 排查,发现以下两个常见坑点:

  1. 缺失 Section:由于直接复制命令,文件缺少了 [Unit]、[Service] 等节声明,必须补全。
  2. BOM 头问题:如果是通过某些编辑器写入的文件,可能会包含不可见的 BOM 头,使用 sed -i '1s/^\xEF\xBB\xBF//' <file> 即可去除。

📊 总结

通过上述配置,服务器实现了:

目标 实现方式 效果
合规性 每日增量与每周全量扫描 满足等保审计要求,日志保留1个月
稳定性 flock 和 systemd oneshot 双重锁 彻底杜绝了扫描进程重叠堆积的风险
低开销 用 clamdscan 替代 clamscan + logrotate 大幅降低 CPU/内存峰值,限制磁盘占用