
一、需求概述
基于宝塔面板完成以下核心需求:
- 定时执行Python脚本,无需人工干预;
- 针对按「年月」命名的目录(如2026-03),自动备份上个月的目录(当前2026-04则备份2026-03);
- 支持5G级大目录备份,通过分片上传解决大文件传输超时、内存溢出问题;
- 备份目标为阿里云OSS,支持断点续传、上传进度监控,保障备份可靠性。
二、实现步骤
步骤1:前置环境准备
1.1 安装Python依赖
登录宝塔面板→进入「SSH终端」,执行以下命令安装依赖:
bash
pip3 install oss2 python-dotenv
1.2 阿里云OSS配置
- 登录阿里云OSS控制台,创建Bucket(建议私有权限,地域选择与服务器就近区域);
- 创建RAM子账号,授予以下OSS权限(最小权限原则):
oss:PutObjectoss:InitiateMultipartUploadoss:UploadPartoss:CompleteMultipartUploadoss:AbortMultipartUpload
- 记录关键信息:
AccessKey ID、AccessKey Secret、Bucket名称、Endpoint(如oss-cn-beijing.aliyuncs.com)。
步骤2:编写备份脚本(支持大文件分片)
2.1 创建OSS配置文件(安全存储密钥)
在服务器创建/www/oss_config.env文件,写入以下内容(替换为实际信息):
ini
# 阿里云OSS配置
OSS_ACCESS_KEY_ID=你的RAM账号AccessKey ID
OSS_ACCESS_KEY_SECRET=你的RAM账号AccessKey Secret
OSS_BUCKET_NAME=你的Bucket名称
OSS_ENDPOINT=你的OSS地域节点(如oss-cn-hangzhou.aliyuncs.com)
2.2 编写核心备份脚本
创建/www/backup_to_oss.py文件,完整代码如下:
python
import os
import time
import tarfile
import oss2
import math
from datetime import datetime, timedelta
from dotenv import load_dotenv
from oss2 import determine_part_size, MultipartUploader, ObjectIterator
# 加载环境变量(避免密钥硬编码)
load_dotenv('/www/oss_config.env')
# ====================== 核心配置项(按需修改)======================
# 阿里云OSS基础配置
OSS_ACCESS_KEY_ID = os.getenv('OSS_ACCESS_KEY_ID')
OSS_ACCESS_KEY_SECRET = os.getenv('OSS_ACCESS_KEY_SECRET')
OSS_BUCKET_NAME = os.getenv('OSS_BUCKET_NAME')
OSS_ENDPOINT = os.getenv('OSS_ENDPOINT')
# 待备份目录映射(本地目录前缀: OSS存储前缀)
BACKUP_DIRS = {
"/data/logs": "/backup/logs", # 示例1:本地日志目录
"/data/business": "/backup/business" # 示例2:业务数据目录(可新增)
}
# 临时压缩包存储目录(确保磁盘空间≥待备份目录大小)
TMP_DIR = "/www/tmp"
# 大文件分片上传配置
PART_SIZE = 100 * 1024 * 1024 # 分片大小:100MB/片(阿里云要求100KB~5GB)
UPLOAD_THREADS = 5 # 并发上传线程数(带宽充足可设8-10)
OSS_TIMEOUT = (30, 300) # 连接超时30s,读取超时300s(大文件需加长)
# ==========================================================================
def get_last_month():
"""计算上个月的年月,返回格式:YYYY-MM(如2026-03)"""
today = datetime.today()
first_day_of_this_month = datetime(today.year, today.month, 1)
last_day_of_last_month = first_day_of_this_month - timedelta(days=1)
return last_day_of_last_month.strftime("%Y-%m")
def compress_large_dir(source_dir, output_file, chunk_size=10*1024*1024):
"""
流式压缩大目录(避免内存溢出)
:param source_dir: 待压缩目录路径
:param output_file: 压缩包输出路径
:param chunk_size: 流式写入块大小(默认10MB)
"""
if not os.path.exists(source_dir):
raise FileNotFoundError(f"待备份目录不存在:{source_dir}")
# 确保临时目录存在
os.makedirs(os.path.dirname(output_file), exist_ok=True)
# 流式压缩(逐文件添加,不一次性加载)
with tarfile.open(output_file, "w:gz", bufsize=chunk_size) as tar:
for root, dirs, files in os.walk(source_dir):
for file in files:
file_path = os.path.join(root, file)
# 保留相对路径,便于OSS解压后还原目录结构
tar.add(file_path, arcname=os.path.relpath(file_path, source_dir))
# 返回压缩包大小,用于后续分片计算
file_size = os.path.getsize(output_file)
print(f"✅ 压缩完成:{output_file} | 大小:{file_size/1024/1024:.2f} MB")
return file_size
def upload_large_file_to_oss(local_file, oss_path):
"""
分片上传大文件至OSS(支持断点续传、进度监控)
:param local_file: 本地压缩包路径
:param oss_path: OSS存储路径(如/backup/logs/2026-03.tar.gz)
"""
# 初始化OSS客户端(配置超时)
auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
bucket = oss2.Bucket(
auth, OSS_ENDPOINT, OSS_BUCKET_NAME,
connect_timeout=OSS_TIMEOUT[0],
read_timeout=OSS_TIMEOUT[1]
)
# 跳过已存在的文件(避免重复上传)
if bucket.object_exists(oss_path):
print(f"ℹ️ OSS已存在文件:{oss_path},跳过上传")
return
# 获取文件大小,自动确定分片大小(兼容阿里云限制)
file_size = os.path.getsize(local_file)
part_size = determine_part_size(file_size, preferred_size=PART_SIZE)
total_parts = math.ceil(file_size / part_size)
print(f"📤 开始分片上传:总大小 {file_size/1024/1024:.2f} MB | 分片大小 {part_size/1024/1024:.2f} MB | 总片数 {total_parts}")
# 初始化分片上传任务
upload_id = bucket.init_multipart_upload(oss_path).upload_id
try:
# 创建分片上传器(支持多线程、断点续传)
uploader = MultipartUploader(
bucket, oss_path, upload_id,
part_size=part_size,
num_threads=UPLOAD_THREADS
)
# 上传进度回调函数
def progress_callback(consumed, total):
progress = (consumed / total) * 100
print(f"\r🔄 上传进度:{progress:.2f}% ({consumed/1024/1024:.2f}MB/{total/1024/1024:.2f}MB)", end="")
# 执行上传
uploader.upload_file(local_file, progress_callback=progress_callback)
print("\n🔗 合并分片...")
# 完成分片上传(合并所有分片)
bucket.complete_multipart_upload(oss_path, upload_id, uploader.uploaded_parts)
print(f"✅ 上传成功:OSS路径 -> {oss_path}")
except Exception as e:
# 上传失败,清理OSS临时分片
bucket.abort_multipart_upload(oss_path, upload_id)
raise Exception(f"❌ 分片上传失败:{str(e)}")
def clean_expired_oss_parts(days=7):
"""清理OSS中过期的未完成分片(避免占用存储空间)"""
auth = oss2.Auth(OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET)
bucket = oss2.Bucket(auth, OSS_ENDPOINT, OSS_BUCKET_NAME)
expired_time = datetime.now() - timedelta(days=days)
for upload in ObjectIterator(oss2.ListMultipartUploads(bucket)):
if upload.initiated_time < expired_time:
bucket.abort_multipart_upload(upload.key, upload.upload_id)
print(f"🧹 清理过期分片任务:{upload.key}")
def main():
"""主执行逻辑"""
try:
# 1. 计算上个月年月
last_month = get_last_month()
print(f"========== 开始备份 {last_month} 目录 ==========")
# 2. 遍历所有待备份目录
for local_prefix, oss_prefix in BACKUP_DIRS.items():
# 拼接完整路径
source_dir = os.path.join(local_prefix, last_month)
compress_filename = f"{last_month}_{os.path.basename(local_prefix)}.tar.gz"
tmp_compress_file = os.path.join(TMP_DIR, compress_filename)
oss_file_path = os.path.join(oss_prefix, compress_filename)
try:
# 3. 流式压缩目录
compress_large_dir(source_dir, tmp_compress_file)
# 4. 分片上传至OSS
upload_large_file_to_oss(tmp_compress_file, oss_file_path)
# 5. 删除本地临时压缩包(可选,节省磁盘空间)
os.remove(tmp_compress_file)
print(f"🗑️ 删除本地临时文件:{tmp_compress_file}\n")
except Exception as e:
print(f"❌ 备份 {source_dir} 失败:{str(e)}\n")
continue
# 可选:清理OSS过期分片(建议每月执行一次)
# clean_expired_oss_parts(days=7)
print("========== 所有目录备份完成 ==========")
except Exception as e:
print(f"❌ 脚本执行失败:{str(e)}")
raise # 抛出异常,便于宝塔日志捕获
if __name__ == "__main__":
main()
步骤3:宝塔面板配置定时任务
- 登录宝塔面板,点击左侧「计划任务」;
- 点击「添加任务」,按以下参数配置:
- 任务类型:脚本任务;
- 任务名称:自定义(如「每月备份上月目录至OSS」);
- 执行周期:按月执行,选择「每月1日」,执行时间建议凌晨(如00:30);
- 脚本内容:
python3 /www/backup_to_oss.py; - 可选:开启「发送邮件提醒」,接收执行结果(成功/失败);
- 点击「添加」,完成定时任务配置。
步骤4:测试验证
- 手动测试 :SSH终端执行
python3 /www/backup_to_oss.py,检查是否正常压缩、上传; - 日志验证:宝塔计划任务列表→点击任务右侧「日志」,查看执行记录;
- OSS验证:登录阿里云OSS控制台,检查目标Bucket是否存在备份文件。
三、关键说明
- 权限保障 :确保
/www/tmp目录有读写权限,RAM账号权限完整; - 资源要求:服务器磁盘空间需≥待备份目录大小(5G+),带宽建议≥10Mbps;
- 参数调优 :
- 大带宽服务器可将
UPLOAD_THREADS调至8-10,提升上传速度; - 低内存服务器可将
PART_SIZE调至50MB,降低内存占用;
- 大带宽服务器可将
- 异常处理:脚本内置错误捕获,失败时会输出详细日志,便于定位问题。
总结
- 核心能力:通过Python脚本实现「自动计算上月目录+流式压缩+OSS分片上传」,宝塔定时任务保障自动化执行;
- 大文件适配:分片上传解决5G级文件传输超时/内存溢出问题,支持断点续传和进度监控;
- 可靠性保障:内置文件存在性检查、过期分片清理、异常捕获,确保备份过程稳定。
@漏刻有时