FunctionGraph 定时任务实战:每天凌晨自动清理 OBS 过期临时文件
很多业务都会在对象存储里产生临时文件:图片处理中间结果、导出报表、上传分片残留、日志归档……如果不定期清理,桶会越长越大,既多花存储费用,列对象时也越来越慢。本文用华为云 FunctionGraph 加 OBS,实战搭建一个“每天凌晨 2 点自动清理过期临时文件”的定时任务:不用买服务器、不用常驻进程,全部用 Serverless 完成。
一、方案总览
链路很简单:TIMER 定时触发器 → FunctionGraph Python 函数 → OBS Python SDK 遍历并删除过期对象。
- 触发器:FunctionGraph 自带的 TIMER 类型,按 Cron 表达式每天触发一次。
- 函数:Python 3.9,入口 handler(event, context),只删除指定前缀(如 temp/)下超过保留天数的文件。
- 权限:函数通过 IAM 委托访问 OBS,不在代码里写 AK/SK。
- 成本:按执行次数和运行时长计费,凌晨跑一次清理通常只有几分钱量级,函数本身没有闲置费用。
为什么不用 OBS 生命周期规则?生命周期规则也能过期删除,但它是桶级别、按整个前缀生效的粗粒度策略;需要“只删 temp/ 下超过 7 天的文件、同时记录删除清单、失败时告警”这种精细逻辑时,函数更灵活。两者也可以叠加使用。
二、准备工作
- 开通 FunctionGraph 服务:进入华为云控制台,搜索 FunctionGraph,开通即可,无需预购资源。
- 准备 OBS 桶:确认桶名、所在区域,例如华北-北京四 cn-north-4;临时文件统一放在 temp/ 前缀下。
- 约定保留策略:本文示例保留 7 天,即删除最后修改时间超过 7 天的对象;保留天数、桶名、前缀全部用环境变量传入,方便不同环境复用。
- 准备 Python 环境:本地安装 Python 3.9,函数代码依赖 OBS Python SDK(esdk-obs-python)。
三、编写清理函数
核心逻辑是三步:分页列出对象、按最后修改时间判断是否过期、批量删除。
# cleanup_obs_temp.py
import os
import time
from obs import ObsClient
def handler(event, context):
logger = context.getLogger()
ak = os.environ.get("HUAWEI_AK")
sk = os.environ.get("HUAWEI_SK")
server = os.environ.get("OBS_ENDPOINT", "https://obs.cn-north-4.myhuaweicloud.com")
bucket = os.environ.get("OBS_BUCKET")
prefix = os.environ.get("OBS_PREFIX", "temp/")
retain_days = int(os.environ.get("RETAIN_DAYS", "7"))
client = ObsClient(access_key_id=ak, secret_access_key=sk, server=server)
cutoff = time.time() - retain_days * 86400
marker = ""
deleted, scanned = 0, 0
while True:
resp = client.listObjects(bucket, prefix=prefix, marker=marker, max_keys=1000)
for obj in resp.body.contents:
scanned += 1
if obj["lastModified"] < cutoff:
client.deleteObject(bucket, obj["key"])
deleted += 1
logger.info("deleted: %s", obj["key"])
if not resp.body.is_truncated:
break
marker = resp.body.next_marker
logger.info("scanned %d, deleted %d", scanned, deleted)
return {"scanned": scanned, "deleted": deleted}
几点说明:
- 入口签名固定为 handler(event, context),event 是触发器传入的事件,TIMER 触发时内容为定时配置;context 提供日志等运行时上下文。
- listObjects 单次最多返回 1000 个对象,必须用 marker 分页,否则大桶会漏删。
- 删除前用 lastModified 判断是否过期,且只处理 temp/ 前缀,避免误删业务数据。
- 配置全部走环境变量,不硬编码桶名和密钥。
四、打包与创建函数
FunctionGraph 的 Python 运行时不预装 esdk-obs-python,需要把 SDK 打进部署包:本地执行 pip install esdk-obs-python -t .,把 obs 包目录和 cleanup_obs_temp.py 放在同一层级打成 zip 上传。
控制台创建函数步骤:
- FunctionGraph 控制台 → 函数列表 → 创建函数 → 自定义创建。
- 运行时选 Python3.9,函数名 obs-temp-cleanup,入口写 cleanup_obs_temp.handler。
- 上传 zip 部署包;内存 128MB 起步,超时时间设为 60 秒(默认只有 3 秒,大桶清理一定会超时)。
- 在“环境变量”里配置 OBS_BUCKET、OBS_PREFIX=temp/、RETAIN_DAYS=7,以及 OBS_ENDPOINT。
- 保存后在控制台手动执行一次,看日志输出是否正常列出和删除文件。
五、创建定时触发器
- 进入函数详情 → 触发器 → 创建触发器,类型选 TIMER。
- 定时规则选 Cron 表达式。FunctionGraph 用 Quartz Cron,6 个字段:秒 分 时 日 月 周。每天凌晨 2 点(北京时间)对应的 UTC 时间是前一天 18:00,所以表达式是 0 0 18 * * ?。
- 触发器名称建议用 daily-2am-cleanup 这种“频率-时间-用途”格式,一眼能看出用途。
- 建议先用禁用状态创建触发器,手动验证函数无误后再启用。
时区是最容易踩的坑
FunctionGraph 的 TIMER 触发器默认使用 UTC 时间,不是北京时间。直接写 0 0 2 * * ?,实际是北京时间上午 10 点执行。换算关系:北京时间凌晨 2:00 等于 UTC 前一天 18:00;北京时间凌晨 0:00 等于 UTC 前一天 16:00;北京时间上午 8:00 等于 UTC 当天 0:00。
另外注意 Quartz 的 ? 规则:日(day-of-month)和周(day-of-week)不能同时为 *,其中一个必须用 ?。写 0 0 18 * * * 会报 InvalidParameter 错误,正确写法是 0 0 18 * * ?。
六、权限与安全
- 优先用 IAM 委托:在函数详情 → 委托中配置,让函数以委托身份访问 OBS,代码和环境变量里都不出现 AK/SK。
- 最小权限:委托只授予 OBS 桶的读写权限,不要给账号级通配权限;不同环境用不同委托。
- 密钥轮换:如果暂时用 AK/SK 走环境变量,要定期轮换,并尽快迁移到委托方式。
- 误删防护:生产桶先在测试桶验证;首次上线前把删除动作改成只记录日志的演练模式,确认清单无误后再真正删除。
七、验证流程
- 控制台查看触发器:函数详情 → 触发器,应看到 daily-2am-cleanup,状态为 ACTIVE。
- 手动测试:在控制台手动执行一次函数,日志应输出本次扫描和删除的数量。
- 等一天看自动执行:第二天凌晨 2 点后查看函数执行历史,确认被 TIMER 自动触发。
- OBS 侧确认:去 OBS 控制台看桶里 temp/ 前缀下超过 7 天的文件是否已被删除。
八、容易踩的坑清单
- 超时时间太短:默认 3 秒基本不够用,设 60 秒;文件量巨大时调到 300 秒(最大 900 秒)或分批处理。
- 忘记分页:单次列对象上限 1000,不分页会漏删。
- 重复触发:TIMER 偶尔重复执行,删除操作本身是幂等的,删不存在的对象不会报错;但如果加了发通知等逻辑,要注意去重。
- 冷启动:函数首次执行有几秒冷启动延迟,凌晨清理场景对延迟不敏感,可以忽略。
- 静默失败:一定要记日志并配告警。在函数里用 context.getLogger().info() 记录每次清理的文件数和文件名,再在云监控 CES 上配一个告警,函数执行失败时通知你,否则定时任务静默失败可能很久才发现。
九、成本与扩展
函数按请求次数和执行时长计费,每天跑一次、每次几十秒的量级,成本很低;没有常驻服务器,也就没有闲置费用。跑通之后,还可以顺着这个模式扩展:把清理结果写到日志或发到消息通知;给多个桶配置多组触发器;或者把“定期巡检、报表生成、数据归档”这类轻量自动化都收敛到同一套定时任务框架里。
结尾
定时清理这件事本身不复杂,真正的价值在于“不用人惦记”:规则定好之后,临时文件每天凌晨自己消失,存储费用和运维负担都降下来。如果你也在用 OBS 存临时文件,不妨按上面的步骤花半小时搭一个,第二天早上看一眼执行日志,就能确认它已经开始工作了。
- 点赞
- 收藏
- 关注作者
评论(0)