如何配置云服务器日志的清理与归档流程规范?
摘要:# EC2*ECS日志清理与归档配置规范 > 标准化日志生命周期管理:压缩 - 归档(S3) - 清理,防止磁盘打满导致服务不可用。 ## 目录 - [架构概览](#架构概览) - [清理策略说明](#清理策略说明)
# EC2*ECS日志清理与归档配置规范
> 标准化日志生命周期管理:压缩 - 归档(S3) - 清理,防止磁盘打满导致服务不可用。
---
## 目录
- [架构概览](#架构概览)
- [清理策略说明](#清理策略说明)
- [配置参数规范](#配置参数规范)
- [优化脚本(可配置版)](#优化脚本可配置版)
- [部署与运维](#部署与运维)
- [监控与告警](#监控与告警)
- [原始脚本分析与改进点](#原始脚本分析与改进点)
---
## 架构概览
```
日志文件 (.log)
|
+-- 正常流程(磁盘 < 普通阈值)
| --> 压缩 (.log.gz) --> 可选上传 S3 --> 删除原始 .log
|
+-- 普通清理(磁盘 > 普通阈值)
| --> 删除最旧的 .gz 文件(分批删除,直到低于阈值)
|
+-- 紧急清理(磁盘 > 紧急阈值,需开启 ENABLE_EMERGENCY_CLEANUP)
--> 激进删除:.gz -> 旧 .log -> 最旧日志文件(保护活跃日志)
```
### 生命周期
```
写入中 (.log) 保留(活跃日志,受 KEEP_FILES 保护)
| 60 分钟后
已轮转 (.20260410.log) 压缩为 .gz -> 删除原始文件
| 磁盘超阈值时
压缩文件 (.gz) 按时间从旧到新依次删除
| 可选
S3 归档 长期保留,按 S3 lifecycle 策略自动过期
```
---
## 清理策略说明
### 三级清理机制
| 级别 | 触发条件 | 开关 | 行为 | 风险等级 |
|----------------|----------------------------------|-----------------------------------|------------------------------------------------|----------|
| **压缩归档** | 每轮循环 | 始终开启 | 压缩轮转日志,可选上传 S3,删除原始文件 | 低 |
| **普通清理** | 磁盘 > `THRESHOLD` | 始终开启 | 分批删除最旧 `.gz` 文件,直到低于阈值 | 中 |
| **紧急清理** | 磁盘 > `EMERGENCY_THRESHOLD` | **`ENABLE_EMERGENCY_CLEANUP`** | 激进删除所有可清理日志(保护 `KEEP_FILES`) | 高 |
> **紧急清理开关说明:** `ENABLE_EMERGENCY_CLEANUP=1` 开启(默认),`=0` 关闭。
> 关闭后磁盘即使超过紧急阈值也仅执行普通清理 + 发送告警,不会激进删除文件。
> 适用于希望人工介入而非自动处理的场景。
### 文件保护机制
`KEEP_FILES` 列表中的文件在**任何清理级别**下都不会被删除,确保正在写入的活跃日志不被中断:
```bash
KEEP_FILES_CSV="rapidtrade_server.log,exchange.log,rapidtrade_order.log"
```
### S3 归档路径结构
```
s3://{bucket}/{server-ip}/{relative-path}.gz
示例:
s3://app-logs-prod/10.0.1.23/logs/trading-gateway/trading-gateway.20260410.1.log.gz
```
---
## 配置参数规范
### 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|------------------------------|------------|-----------|---------------------------------------------------|
| `LOG_DIR` | 路径 | `/data/` | 日志根目录 |
| `THRESHOLD` | 百分比 | `70` | 普通清理触发阈值 |
| `EMERGENCY_THRESHOLD` | 百分比 | `85` | 紧急清理触发阈值 |
| `ENABLE_EMERGENCY_CLEANUP` | 0/1 | `1` | **紧急清理开关**,0=关闭(仅告警不删除),1=开启 |
| `DELETE_BATCH` | 整数 | `20` | 每批删除文件数 |
| `SLEEP_INTERVAL` | 秒 | `60` | 主循环间隔 |
| `COMPRESS_AGE_MIN` | 分钟 | `60` | 多久前的轮转日志才压缩 |
| `ENABLE_S3_UPLOAD` | 0/1 | `0` | S3 上传开关 |
| `AWS_BUCKET_NAME` | 字符串 | - | S3 存储桶名称 |
| `AWS_REGION` | 字符串 | - | AWS 区域 |
| `KEEP_FILES_CSV` | 逗号分隔 | - | 受保护的活跃日志文件名 |
| `LOG_ENV` | 字符串 | `prod` | 环境标识 |
| `DRY_RUN` | 0/1 | `0` | 试运行模式,只打印不执行 |
| `ENABLE_NOTIFICATION` | 0/1 | `0` | 是否发送告警通知 |
| `NOTIFY_WEBHOOK_URL` | URL | - | 飞书/Slack webhook 地址 |
### 各环境推荐值
| 参数 | prod | uat/sit | dev |
|------------------------------|--------|---------|-------|
| `THRESHOLD` | `70` | `75` | `80` |
| `EMERGENCY_THRESHOLD` | `85` | `90` | `90` |
| `ENABLE_EMERGENCY_CLEANUP` | `1` | `1` | `0` |
| `SLEEP_INTERVAL` | `60` | `120` | `300` |
| `COMPRESS_AGE_MIN` | `60` | `30` | `30` |
| `ENABLE_S3_UPLOAD` | `1` | `0` | `0` |
| `DELETE_BATCH` | `20` | `50` | `50` |
---
## 优化脚本(可配置版)
相比原始脚本的**核心改进**:
| 改进项 | 说明 |
|------------------------|------------------------------------------------------------|
| 外部配置文件 | 参数与脚本分离,一份脚本适配所有环境 |
| 紧急清理独立开关 | `ENABLE_EMERGENCY_CLEANUP` 控制是否执行激进删除 |
| `set -euo pipefail` | 严格模式,避免静默失败 |
| flock 文件锁 | 防止 cron 重复调度导致并发执行 |
| dry-run 模式 | 上线前可安全验证逻辑 |
| 消除 `eval` | 使用数组传参,避免命令注入风险 |
| 告警通知 | 紧急清理或关闭紧急清理时自动发送告警 |
| 凭据外置 | AWS 凭据从环境变量或 IAM Role 获取,不硬编码 |
### 配置文件
```bash
# /etc/log-cleaner/config.env
# ============================================================
# 日志清理配置 - 按环境修改
# ============================================================
# ---- 基础配置 ----
LOG_DIR="/data/"
LOG_ENV="prod"
SLEEP_INTERVAL=60
DRY_RUN=0
# ---- 磁盘阈值 ----
THRESHOLD=70
EMERGENCY_THRESHOLD=85
# ---- 紧急清理开关 ----
# 1=开启:磁盘超过 EMERGENCY_THRESHOLD 时自动激进删除
# 0=关闭:仅发送告警,不执行激进删除(需人工介入)
ENABLE_EMERGENCY_CLEANUP=1
# ---- 压缩与清理 ----
COMPRESS_AGE_MIN=60
DELETE_BATCH=20
# ---- S3 归档 ----
ENABLE_S3_UPLOAD=1
AWS_BUCKET_NAME="app-logs-prod"
AWS_REGION="ap-northeast-1"
# AWS 凭据通过 IAM Role 或 EnvironmentFile 注入,不在此配置
# ---- 保护文件(逗号分隔) ----
KEEP_FILES_CSV="rapidtrade_server.log,exchange.log,rapidtrade_order.log"
# ---- 通知 ----
ENABLE_NOTIFICATION=0
# NOTIFY_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
```
### 脚本主体
```bash
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# 日志清理与归档脚本(通用版)
# 用途:压缩轮转日志 -> 可选上传 S3 -> 磁盘超阈值时自动清理
# 部署:systemd service 或 cron
# ============================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${LOG_CLEANER_CONFIG:-/etc/log-cleaner/config.env}"
LOCK_FILE="/var/run/log-cleaner.lock"
# ---- 加载配置 ----
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck source=/dev/null
source "$CONFIG_FILE"
else
echo "FATAL: 配置文件不存在: $CONFIG_FILE" >&2
exit 1
fi
# ---- 解析 KEEP_FILES ----
IFS=',' read -ra KEEP_FILES <<< "${KEEP_FILES_CSV:-}"
# ---- 默认值 ----
LOG_DIR="${LOG_DIR:-/data/}"
THRESHOLD="${THRESHOLD:-70}"
EMERGENCY_THRESHOLD="${EMERGENCY_THRESHOLD:-85}"
ENABLE_EMERGENCY_CLEANUP="${ENABLE_EMERGENCY_CLEANUP:-1}"
DELETE_BATCH="${DELETE_BATCH:-20}"
SLEEP_INTERVAL="${SLEEP_INTERVAL:-60}"
COMPRESS_AGE_MIN="${COMPRESS_AGE_MIN:-60}"
ENABLE_S3_UPLOAD="${ENABLE_S3_UPLOAD:-0}"
DRY_RUN="${DRY_RUN:-0}"
ENABLE_NOTIFICATION="${ENABLE_NOTIFICATION:-0}"
LOG_ENV="${LOG_ENV:-unknown}"
SERVER_IP=$(hostname -I | awk '{print $1}')
# ---- 文件锁(防止并发执行) ----
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
echo "WARN: 另一个实例正在运行,退出" >&2
exit 0
fi
# ---- 日志函数 ----
log() {
local level="$1"; shift
echo "$(date '+%Y-%m-%d %H:%M:%S') [$level] [env=$LOG_ENV] $*"
}
# ---- dry-run 包装 ----
safe_rm() {
if [[ "$DRY_RUN" -eq 1 ]]; then
log "DRY-RUN" "would rm: $*"
else
rm -f "$@"
fi
}
safe_gzip() {
local src="$1" dst="$2"
if [[ "$DRY_RUN" -eq 1 ]]; then
log "DRY-RUN" "would gzip: $src -> $dst"
else
gzip -c "$src" > "$dst"
fi
}
# ---- 磁盘使用率 ----
check_disk_usage() {
df "$LOG_DIR" | awk 'NR==2 {gsub("%", "", $5); print $5}'
}
# ---- AWS CLI 检测 ----
if [[ "$ENABLE_S3_UPLOAD" -eq 1 ]]; then
if ! command -v aws &>/dev/null; then
log "WARN" "AWS CLI 不可用,禁用 S3 上传"
ENABLE_S3_UPLOAD=0
fi
fi
# ---- 构建 find 排除参数(数组,安全传参) ----
build_exclude_args() {
local args=()
for keep_file in "${KEEP_FILES[@]}"; do
[[ -z "$keep_file" ]] && continue
args+=("!" "-name" "$keep_file" "!" "-name" "${keep_file}.gz")
done
printf '%s\n' "${args[@]}"
}
# ---- 告警通知 ----
send_alert() {
local message="$1"
if [[ "$ENABLE_NOTIFICATION" -ne 1 ]]; then return 0; fi
if [[ -z "${NOTIFY_WEBHOOK_URL:-}" ]]; then return 0; fi
local payload="{\"msg_type\":\"text\",\"content\":{\"text\":\"[Log-Cleaner] [${LOG_ENV}] [${SERVER_IP}] ${message}\"}}"
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$payload" >/dev/null 2>&1 || true
}
# ---- 上传 S3 ----
upload_to_s3() {
local gz_file="$1"
local relative_path="${gz_file#"$LOG_DIR"}"
local s3_path="s3://${AWS_BUCKET_NAME}/${SERVER_IP}/${relative_path}"
if [[ "$ENABLE_S3_UPLOAD" -ne 1 ]]; then
log "INFO" "S3 上传已禁用,跳过: $gz_file"
return 0
fi
log "INFO" "上传到 S3: $s3_path"
if [[ "$DRY_RUN" -eq 1 ]]; then
log "DRY-RUN" "would upload: $gz_file -> $s3_path"
return 0
fi
if ! aws s3 cp "$gz_file" "$s3_path" --region "$AWS_REGION" --quiet; then
log "ERROR" "上传失败: $gz_file"
return 1
fi
return 0
}
# ---- 压缩轮转日志 ----
compress_rotated_logs() {
log "INFO" "查找 ${COMPRESS_AGE_MIN} 分钟前的轮转日志..."
mapfile -t exclude_args < <(build_exclude_args)
local -a log_files=()
while IFS= read -r -d '' line; do
log_files+=("${line#* }")
done < <(
find "$LOG_DIR" -type f -name "*.log" \
-mmin +"$COMPRESS_AGE_MIN" \
"${exclude_args[@]}" \
-regextype posix-extended \
-regex '.*\.[0-9]+\.log$' \
! -name "*.current.log" \
-printf "%T@ %p\0" 2>/dev/null \
| sort -z -n
)
if [[ ${#log_files[@]} -eq 0 ]]; then
log "INFO" "没有需要压缩的日志文件"
return
fi
log "INFO" "找到 ${#log_files[@]} 个待压缩文件"
for file in "${log_files[@]}"; do
local gz_file="${file}.gz"
log "INFO" "压缩: $file"
if ! safe_gzip "$file" "$gz_file"; then
log "ERROR" "压缩失败: $file"
continue
fi
upload_to_s3 "$gz_file" || continue
log "INFO" "删除原始文件: $file"
safe_rm "$file"
done
}
# ---- 普通清理(删除最旧 .gz) ----
normal_cleanup() {
local usage
usage=$(check_disk_usage)
while (( usage > THRESHOLD )); do
log "WARN" "磁盘 ${usage}% 超过阈值 ${THRESHOLD}%,删除最旧的 ${DELETE_BATCH} 个 .gz 文件"
mapfile -t exclude_args < <(build_exclude_args)
local -a old_gz=()
while IFS= read -r -d '' line; do
old_gz+=("${line#* }")
done < <(
find "$LOG_DIR" -type f -name "*.log.gz" \
"${exclude_args[@]}" \
-printf "%T@ %p\0" 2>/dev/null \
| sort -z -n | head -z -n "$DELETE_BATCH"
)
if [[ ${#old_gz[@]} -eq 0 ]]; then
log "WARN" "没有可删除的 .gz 文件,无法进一步降低磁盘使用率"
break
fi
for f in "${old_gz[@]}"; do
log "WARN" "删除: $f"
safe_rm "$f"
done
sleep 2
usage=$(check_disk_usage)
log "INFO" "清理后磁盘使用率: ${usage}%"
done
}
# ---- 紧急清理 ----
emergency_cleanup() {
local usage="$1"
# === 紧急清理开关检查 ===
if [[ "$ENABLE_EMERGENCY_CLEANUP" -ne 1 ]]; then
log "CRIT" "磁盘 ${usage}% 超过紧急阈值 ${EMERGENCY_THRESHOLD}%,但紧急清理已关闭 (ENABLE_EMERGENCY_CLEANUP=0)"
log "CRIT" "仅执行普通清理 + 发送告警,需人工介入处理"
send_alert "磁盘使用率 ${usage}% 超紧急阈值 ${EMERGENCY_THRESHOLD}%,紧急清理已关闭,需人工介入!"
# 回退到普通清理
normal_cleanup
return
fi
log "CRIT" "磁盘 ${usage}% 超过紧急阈值 ${EMERGENCY_THRESHOLD}%,执行紧急清理"
send_alert "磁盘使用率 ${usage}% 超紧急阈值 ${EMERGENCY_THRESHOLD}%,正在执行紧急清理"
mapfile -t exclude_args < <(build_exclude_args)
# 第一步:删除所有 .gz 文件
log "CRIT" "[Step 1/3] 删除所有压缩日志 (.log.gz)"
while IFS= read -r -d '' line; do
local f="${line#* }"
log "CRIT" "紧急删除: $f"
safe_rm "$f"
done < <(
find "$LOG_DIR" -type f -name "*.log.gz" \
"${exclude_args[@]}" \
-printf "%T@ %p\0" 2>/dev/null | sort -z -n
)
sleep 2
usage=$(check_disk_usage)
if (( usage <= EMERGENCY_THRESHOLD )); then
log "CRIT" "Step 1 后磁盘 ${usage}%,已低于阈值,跳过后续步骤"
return
fi
# 第二步:删除 10 分钟前的旧 .log 文件
log "CRIT" "[Step 2/3] 删除 10 分钟前的旧日志 (.log)"
while IFS= read -r -d '' line; do
local f="${line#* }"
log "CRIT" "紧急删除: $f"
safe_rm "$f"
done < <(
find "$LOG_DIR" -type f -name "*.log" -mmin +10 \
"${exclude_args[@]}" \
-printf "%T@ %p\0" 2>/dev/null | sort -z -n
)
sleep 2
usage=$(check_disk_usage)
if (( usage <= EMERGENCY_THRESHOLD )); then
log "CRIT" "Step 2 后磁盘 ${usage}%,已低于阈值,跳过后续步骤"
return
fi
# 第三步:删除所有日志变体文件(最多 50 个最旧的)
log "CRIT" "[Step 3/3] 删除最旧的日志变体文件 (.log-*, .log.*)"
while IFS= read -r -d '' line; do
local f="${line#* }"
log "CRIT" "紧急删除: $f"
safe_rm "$f"
done < <(
find "$LOG_DIR" -type f \
\( -name "*.log" -o -name "*.log-*" -o -name "*.log.gz" -o -name "*.log.*" \) \
"${exclude_args[@]}" \
-printf "%T@ %p\0" 2>/dev/null \
| sort -z -n | head -z -n 50
)
sleep 5
local new_usage
new_usage=$(check_disk_usage)
log "CRIT" "紧急清理完成,磁盘使用率: ${new_usage}%"
if (( new_usage > EMERGENCY_THRESHOLD )); then
log "CRIT" "清理后仍高于阈值,需要人工检查"
log "CRIT" "占用最大的日志文件:"
find "$LOG_DIR" -type f \( -name "*.log" -o -name "*.log.gz" \) \
-printf "%s %p\n" 2>/dev/null | sort -rn | head -20 | \
awk '{printf " %8.1fM %s\n", $1/1048576, $2}'
send_alert "紧急清理后磁盘仍达 ${new_usage}%,需要人工介入"
fi
}
# ---- 优雅退出 ----
cleanup() {
log "INFO" "收到退出信号,清理中..."
flock -u 200
exit 0
}
trap cleanup SIGTERM SIGINT
# ============================================================
# 主循环
# ============================================================
log "INFO" "日志清理服务启动"
log "INFO" " dir=$LOG_DIR threshold=$THRESHOLD% emergency=$EMERGENCY_THRESHOLD%"
log "INFO" " emergency_cleanup=$ENABLE_EMERGENCY_CLEANUP s3=$ENABLE_S3_UPLOAD dry_run=$DRY_RUN"
while true; do
usage=$(check_disk_usage)
log "INFO" "当前磁盘使用率: ${usage}%"
if (( usage > EMERGENCY_THRESHOLD )); then
emergency_cleanup "$usage"
else
compress_rotated_logs
normal_cleanup
fi
log "INFO" "等待 ${SLEEP_INTERVAL}s..."
sleep "$SLEEP_INTERVAL"
done
```
---
## 部署与运维
### 目录结构
```
/etc/log-cleaner/
config.env # 配置文件(按环境区分)
log-cleaner.sh # 脚本主体(所有环境共用)
```
### systemd 部署(推荐)
```ini
# /etc/systemd/system/log-cleaner.service
[Unit]
Description=Log Cleaner and Archiver
After=network.target
[Service]
Type=simple
ExecStart=/etc/log-cleaner/log-cleaner.sh
Restart=on-failure
RestartSec=10s
User=root
# AWS 凭据通过 IAM Role 注入
# 如必须使用 AK/SK,通过 EnvironmentFile 注入:
# EnvironmentFile=/etc/log-cleaner/aws-credentials.env
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now log-cleaner
sudo journalctl -u log-cleaner -f # 查看日志
```
### cron 部署(备选)
```bash
# 不建议用 cron,因为主循环自带 sleep。如果仍要用 cron:
# 需移除脚本中的 while true 循环,改为单次执行
*/2 * * * * flock -n /var/run/log-cleaner.lock /etc/log-cleaner/log-cleaner.sh >> /var/log/log-cleaner.log 2>&1
```
### 首次上线流程
```bash
# 1. 部署配置和脚本
sudo mkdir -p /etc/log-cleaner
sudo cp config.env /etc/log-cleaner/
sudo cp log-cleaner.sh /etc/log-cleaner/
sudo chmod +x /etc/log-cleaner/log-cleaner.sh
# 2. dry-run 验证(不会实际删除任何文件)
sudo DRY_RUN=1 LOG_CLEANER_CONFIG=/etc/log-cleaner/config.env /etc/log-cleaner/log-cleaner.sh
# 观察输出,确认匹配的文件符合预期,Ctrl+C 停止
# 3. 正式启动
sudo systemctl start log-cleaner
sudo journalctl -u log-cleaner -f
```
### 运行时动态调整
紧急清理开关支持**运行时热切换**,无需重启服务:
```bash
# 关闭紧急清理(不重启服务)
sudo sed -i 's/^ENABLE_EMERGENCY_CLEANUP=1/ENABLE_EMERGENCY_CLEANUP=0/' /etc/log-cleaner/config.env
# 脚本下一轮循环会自动读取新值(需脚本支持 reload)
# 或者重启服务使配置立即生效
sudo systemctl restart log-cleaner
```
### S3 Lifecycle 策略(推荐)
归档到 S3 的日志建议配置自动过期策略:
```json
{
"Rules": [
{
"ID": "log-archive-lifecycle",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}
]
}
```
```bash
aws s3api put-bucket-lifecycle-configuration \
--bucket app-logs-prod \
--lifecycle-configuration file://lifecycle.json
```
---
## 监控与告警
### Prometheus 指标(通过 node_exporter textfile)
```bash
# 在主循环末尾追加(可选)
METRICS_FILE="/var/lib/node_exporter/textfile/log_cleaner.prom"
cat > "$METRICS_FILE" <<PROM
# HELP log_cleaner_disk_usage_percent Current disk usage
# TYPE log_cleaner_disk_usage_percent gauge
log_cleaner_disk_usage_percent{mount="$LOG_DIR",env="$LOG_ENV"} $usage
# HELP log_cleaner_last_run_timestamp Last run time
# TYPE log_cleaner_last_run_timestamp gauge
log_cleaner_last_run_timestamp{env="$LOG_ENV"} $(date +%s)
# HELP log_cleaner_emergency_enabled Emergency cleanup switch
# TYPE log_cleaner_emergency_enabled gauge
log_cleaner_emergency_enabled{env="$LOG_ENV"} $ENABLE_EMERGENCY_CLEANUP
PROM
```
### 建议告警规则
```yaml
groups:
- name: log-cleaner
rules:
- alert: DiskUsageHigh
expr: log_cleaner_disk_usage_percent > 80
for: 5m
labels:
severity: warning
annotations:
summary: "磁盘使用率 {{ $value }}% 超过 80%"
- alert: DiskUsageCritical
expr: log_cleaner_disk_usage_percent > 90
for: 2m
labels:
severity: critical
annotations:
summary: "磁盘使用率 {{ $value }}% 超过 90%,可能影响服务"
- alert: LogCleanerStale
expr: time() - log_cleaner_last_run_timestamp > 300
for: 5m
labels:
severity: warning
annotations:
summary: "日志清理服务超过 5 分钟未运行"
- alert: EmergencyCleanupDisabled
expr: log_cleaner_emergency_enabled == 0 and log_cleaner_disk_usage_percent > 85
for: 1m
labels:
severity: critical
annotations:
summary: "紧急清理已关闭但磁盘超 85%,需立即人工介入"
```
---
## 原始脚本分析与改进点
### 原始脚本存在的问题
| 问题 | 风险等级 | 优化方案 |
|----------------------------------------|------------|----------------------------------------------|
| AWS AK/SK 硬编码在脚本中 | **严重** | 使用 IAM Role 或 EnvironmentFile 注入 |
| 缺少 `set -euo pipefail` | 高 | 添加严格模式,避免静默失败 |
| 使用 `eval` 拼接 find 命令 | 高 | 改用数组传参,消除命令注入风险 |
| 无紧急清理独立开关 | 中 | 添加 `ENABLE_EMERGENCY_CLEANUP` 开关 |
| 无文件锁,可能并发执行 | 中 | 添加 `flock` 锁 |
| 无 dry-run 模式 | 中 | 添加 `DRY_RUN` 开关 |
| 参数全部硬编码在脚本内 | 中 | 抽取为外部配置文件 |
| 紧急清理无告警通知 | 中 | 添加飞书/Slack webhook 通知 |
| 无优雅退出处理 | 低 | 添加 `trap` 信号处理 |
| 紧急清理不区分步骤是否已解决问题 | 低 | 每步后检查磁盘,低于阈值则提前退出 |
### 版本差异对照
| 特性 | V1(基础版) | V2(增强版) | 本文档(优化版) |
|------------------|----------------|--------------------|----------------------------------|
| 磁盘阈值 | 单级(75%) | 双级(70%+85%) | 双级(可配置) |
| 紧急清理开关 | 无 | 无(始终开启) | **`ENABLE_EMERGENCY_CLEANUP`** |
| 文件保护 | 无 | `KEEP_FILES` 数组 | `KEEP_FILES`(外部配置) |
| 紧急清理 | 无 | 三步激进清理 | 三步 + 每步检查 + 告警 |
| 文件匹配 | 简单 grep | find + eval | find + 数组参数(安全) |
| S3 上传 | 默认开启 | 默认关闭 | 可配置 |
| 凭据管理 | 硬编码 | 硬编码 | IAM Role / EnvFile |
| 并发保护 | 无 | 无 | flock 文件锁 |
| 试运行 | 无 | 无 | DRY_RUN 模式 |
| 信号处理 | 无 | 无 | trap SIGTERM/SIGINT |
| 配置方式 | 脚本内 | 脚本内 | 外部 config.env |
