一条删除命令,可能不是几秒钟的误操作,而是持续十几个小时、最终影响数十TB核心数据的生产事故。最高人民检察院公布的这起案件中,北京一名90后算法工程师王某为了给自己承接的私活腾出服务器空间,删除了公司AI游戏部门的全部核心数据,数据量达到89TB,命令持续执行了17小时。
这起事件的技术教训并不只是“不要执行危险命令”。更值得工程团队关注的是:数据迁移通知没有形成可验证的闭环,生产数据和个人任务可能共享基础设施,高危删除操作缺少权限、审批、分区和实时告警等多层约束。
“收到”不等于迁移完成
在公司架构调整期间,相关部门三次通知全员将数据从A集群迁移到B集群,王某都回复了“收到”,但并没有执行迁移。这个细节说明,很多团队把通知回执误当成了任务完成状态。
数据迁移应该具备明确的状态机,而不是停留在聊天窗口里:
待迁移:确认数据范围、负责人、目标集群和截止时间。迁移中:记录实际执行人、命令、开始时间和进度。已复制:校验文件数量、字节数和校验和。已切换:业务已经读取B集群数据,并观察一段时间。可清理:经过负责人和数据所有者批准,才允许清理A集群。
只要没有完成校验和业务切换,就不能把旧集群标记为可删除。特别是“已复制”和“可清理”之间,必须保留人工审批或自动化策略,避免一次错误判断直接造成不可逆损失。
89TB事故暴露了哪些工程缺口
1. 生产数据与个人任务缺少隔离
王某为了给私活腾服务器空间而删除公司数据,至少说明相关环境之间存在可被滥用的资源边界。无论私活是否发生在公司设备上,核心问题都是同一套基础设施可能同时承载了不同目的的数据和任务。
更稳妥的做法是:
- 公司生产集群只允许挂载公司批准的数据盘。
- 个人开发环境使用独立账号、独立命名空间和独立存储配额。
- 训练数据、模型产物、日志和临时文件使用不同存储策略。
- 禁止普通工程账号直接访问跨部门的根目录或对象存储桶。
- 通过标签、项目编号和租户ID区分数据归属,而不是依赖目录名称。
目录名叫 old、backup 或 tmp,并不意味着其中的数据可以删除。删除资格应该来自资产系统、数据所有者和审批记录。
2. 高危命令没有被设计成“难以误用”
Linux 中的递归删除非常强大。路径变量为空、通配符展开范围过大、挂载点判断错误,都可能让一次看似局部的清理变成大范围删除。即使操作者有合理目的,也不应该让生产环境中的高危操作只依赖人的注意力。
可以通过以下措施降低风险:
- 生产环境默认禁止普通账号执行递归删除。
- 对挂载点设置只读策略,清理操作使用专用服务账号。
- 对删除命令增加审批单、工单号和二次确认。
- 对大规模删除设置速率限制和数量阈值。
- 监控删除速率、受影响路径、操作者和命令来源。
- 一旦删除量超过阈值,自动暂停任务并通知值班人员。
这里的重点不是简单地“把 rm 禁掉”。团队需要把删除变成一个可审计的业务动作,而不是任何拥有 shell 权限的人都能直接完成的文件系统动作。
一个更安全的数据迁移与清理流程
下面的脚本是一个可以改造的最小示例。它不会直接删除数据,而是要求目标路径位于允许的挂载点,并且必须显式提供审批标记。示例中的路径、阈值和审批文件都是假设值,接入生产环境前应替换为公司实际的资产系统、审批系统和监控接口。
先用 rsync 做迁移预演和实际复制:
#!/usr/bin/env bash
set -Eeuo pipefail
SRC="/data/cluster-a/project-123"
DST="/data/cluster-b/project-123"
# 先观察差异,不修改任何文件
rsync -aHAXn --delete --itemize-changes "$SRC/" "$DST/"
read -r -p "预演结果已确认,输入 MIGRATE 才开始复制: " answer
if [[ "$answer" != "MIGRATE" ]]; then
echo "migration cancelled"
exit 1
fi
rsync -aHAX --info=progress2 "$SRC/" "$DST/"
# 记录基础校验信息,生产环境可替换成对象存储清单或专用校验服务
find "$SRC" -type f -printf '%P\n' | sort > /tmp/source.files
find "$DST" -type f -printf '%P\n' | sort > /tmp/target.files
sha256sum /tmp/source.files /tmp/target.files
diff -u /tmp/source.files /tmp/target.files
echo "copy verified; old data is not yet eligible for deletion"
这个示例有意把“复制完成”和“允许删除”分成两个阶段。实际系统中还应该增加文件内容校验、业务读写切换验证、备份确认和负责人审批。rsync --delete 本身也属于高风险选项,必须先执行 dry run,并确认源路径和目标路径没有写反。
下面是一个更保守的清理包装脚本。它只展示保护逻辑,默认不会执行删除:
#!/usr/bin/env bash
set -Eeuo pipefail
TARGET="${1:-}"
APPROVAL_FILE="/etc/data-cleanup/approved/project-123"
ALLOWED_PREFIX="/data/cluster-a/project-123"
MAX_FILES=1000
if [[ -z "$TARGET" || "$TARGET" != "$ALLOWED_PREFIX"/* ]]; then
echo "refusing: target is outside the approved prefix" >&2
exit 2
fi
if [[ ! -f "$APPROVAL_FILE" ]]; then
echo "refusing: approval file is missing" >&2
exit 3
fi
file_count="$(find -- "$TARGET" -type f -printf '.' | wc -c)"
if (( file_count > MAX_FILES )); then
echo "refusing: $file_count files exceed limit $MAX_FILES" >&2
exit 4
fi
echo "review target: $TARGET"
echo "file count: $file_count"
find -- "$TARGET" -maxdepth 2 -type f -print | sed -n '1,20p'
read -r -p "输入 APPROVED 才执行清理: " answer
if [[ "$answer" != "APPROVED" ]]; then
echo "cleanup cancelled"
exit 0
fi
# 生产环境应由专用清理服务执行,并把操作者、工单号和结果写入审计系统。
find -- "$TARGET" -type f -print -delete
这个脚本仍然不能替代正式的权限控制。攻击者或误操作用户如果能修改脚本、审批文件或挂载点,保护就会失效。因此,真正的防线应该位于多个层次:身份权限、存储策略、审批服务、执行代理、审计日志和监控告警。
“跑了17小时”意味着什么
删除任务持续17小时,意味着系统可能缺少实时的异常行为检测,也可能没有针对大规模删除设置自动熔断。对AI和数据团队来说,文件数量、总字节数和删除速率都应该是可观测指标。
可以为删除操作设置简单的告警规则:
# 伪配置:字段名称需要根据实际监控系统调整
alerts:
- name: abnormal-data-delete
condition: deleted_bytes > 100GB within 10m
action:
- pause_cleanup_job
- page_oncall
- require_owner_approval
- name: long-running-delete
condition: delete_duration > 30m
action:
- pause_cleanup_job
- capture_process_and_audit_log
告警不能只发到一个容易被忽略的群聊。暂停任务、通知值班人员、保留进程和命令审计信息,才有机会在事故扩大前介入。对关键数据,还可以采用对象存储版本控制、不可变备份、跨区域副本或带保留期的回收站机制,给人为错误留下恢复窗口。
团队可以立刻检查的清单
- 数据迁移工单是否能显示真实进度,而不是只记录“已读”或“收到”。
- A集群和B集群的数据是否有文件清单、字节数和校验结果。
- “已迁移”和“可删除”是否是两个独立状态。
- 普通工程账号是否能访问其他部门的生产数据。
- 大规模删除是否需要审批、二次确认和工单号。
- 删除任务是否有速率、数量和总容量阈值。
- 监控系统能否在几分钟内发现异常删除并暂停任务。
- 关键数据是否存在可验证、可恢复的备份。
- 离职、转岗和架构调整时,权限是否会同步收回或重新核验。
这起案件的核心教训,是把“人应该小心”升级为“系统必须让错误难以发生,让异常能够被及时发现”。对于AI团队,数据往往比代码更难重建。迁移、清理和权限变更都应该留下可验证的证据链,并且把不可逆操作拆成多个有边界、可暂停、可恢复的步骤。