服务器 备份 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
robking
V2EX    问与答

服务器 备份

  •  
  •   robking 2023-06-28 23:56:36 +08:00 2938 次点击
    这是一个创建于 839 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近遇到一个很困恼的问题,一直迟迟没有很好的解决方案,来问问无所不知的 V 友们。

    就是关于服务器怎么备份,我的目的是实现自动化备份,容易恢复,打算通过 WebDav 备份到阿里云盘。

    我将服务器分为两大块数据

    一方面是 Docker 部署的服务数据,这里主要是一些文件的映射数据,还有就是数据卷,目前来说没找到好的方式备份这一块数据。求 V 友推荐脚本或者可视化工具!

    另一方面就是其他的非 Docker 数据,打算脚本定时备份到云盘,还没想好具体怎么做?

    另外几个小问题 备份的数据格式,压缩文件还是其他特定格式的? 关于个人图片和视频的备份?有没有推荐的,可以随时查看上传。稳定

    求万能的 V 友们给小弟支支招!分享一下大家是如何备份数据的!

    第 1 条附言    2023-06-29 14:57:14 +08:00
    万能的 V 友们,图片备份大家用的什么工具呀?可以随时访问的
    32 条回复    2023-07-03 17:23:19 +08:00
    mifar
        1
    mifar  
       2023-06-29 00:34:26 +08:00
    备份这种东西,花钱越多越省心。

    数据库可以用 XtraBackup 增量备份,然后定是脚本同步到 S3
    服务器应用数据库,如果是云服务器,可以用快照,如果不是的话,打包以后增量备份到 S3

    上面是穷逼玩法, 有钱的可以多搜索企业备份解决方案。
    robking
        2
    robking  
    OP
       2023-06-29 00:53:08 +08:00
    @mifar #1 不是企业 个人的一些小数据,博客等
    tool2d
        3
    tool2d  
       2023-06-29 01:21:55 +08:00 via Android
    阿里云盘会全盘扫描,感觉个人数据,就没必要全量网络备份吧。
    现在个人硬盘都很便宜,买个 nas 就搞定了。
    至于 web 服务器数据灾难备份,我都是写脚本批量化同步,走 ssh 协议。
    robking
        4
    robking  
    OP
       2023-06-29 01:41:50 +08:00
    @tool2d #3 硬盘的话每次都需要手动去备份感觉有点麻烦。大佬写脚本 是备份到本地还是其他云盘?
    tool2d
        5
    tool2d  
       2023-06-29 02:12:51 +08:00
    本地和云盘的代码都有,备份是个高频操作,最少要做到半自动化。

    覆盖文件时需要人工确认,误删误覆盖就不太好了。
    robking
        6
    robking  
    OP
       2023-06-29 02:38:28 +08:00
    @tool2d #5 云盘备份 也是通脚本方式吗?可以分享一下脚本吗?
    dann73580
        7
    dann73580  
       2023-06-29 04:04:26 +08:00
    我现在是基于 rclone 做存储后端。打包滚动备份。自己写的小脚本,还算稳定。
    woshinide300yuan
        8
    woshinide300yuan  
       2023-06-29 08:42:46 +08:00
    我这种小白,是宝塔+阿里云+OSS ,后台自动打包数据库+程序,走内网到 OSS ,几乎都是秒传。
    sss15
        9
    sss15  
       2023-06-29 08:58:09 +08:00
    阿里云的磁盘镜像,我是小白
    everyx
        10
    everyx  
       2023-06-29 09:24:36 +08:00
    文件全村对象存储,数据库定期备份,自荐一下自用的 mariadb 增量备份脚本 https://github.com/everyx/mariabackup.sh
    harvies
        11
    harvies  
       2023-06-29 09:33:09 +08:00 via Android   1
    ```python
    #!/usr/bin/env python3

    import os
    import shutil
    import subprocess
    import json
    from datetime import date,datetime

    # 默认配置文件路径
    CONFIG_FILE = "backup.json"

    # 用法函数
    def print_usage():
    print("Usage: {} [-c <config_file>]".format(os.path.basename(__file__)))
    print(" -c, --config-file specify the path of the configuration file (default: backup.json)")

    # 解析参数
    import argparse
    parser = argparse.ArgumentParser(description="Simple backup script")
    parser.add_argument("-c", "--config-file", metavar="CONFIG_FILE", type=str, default=CONFIG_FILE, help="specify the path of the configuration file (default: {})".format(CONFIG_FILE))
    args = parser.parse_args()

    # 加载配置文件
    if not os.path.isfile(args.config_file):
    print("Cannot find config file {}.".format(args.config_file))
    exit(1)

    with open(args.config_file) as f:
    cOnfig= json.load(f)

    compress_password = config.get("compress_password")
    rclone_transfers = config.get("rclone_transfers", 4)
    rclone_config_file = config.get("rclone_config_file")
    backup_dirs = config.get("backup_dirs")

    # 同步单个文件夹到备份路径下
    def backup_folder(src_path, backup_path, prefix, enabled, ignore_files, compress_rate, volume_size,temp_dir):
    # 如果备份文件夹未启用备份,直接返回备份成功
    if not enabled:
    print("Skipping backup for {} as it is not enabled.".format(src_path))
    return True

    # 压缩文件夹,生成 .7z 文件
    compressed_path = os.path.join(temp_dir,prefix, os.path.basename(src_path) + ".7z")
    print("Compressing {} to {} ...".format(src_path, compressed_path))
    compress_command = ["7z","-bb3", "a", "-p{}".format(compress_password), "-y", "-mhe=on", "-mx{}".format(compress_rate), "-v{}".format(volume_size)]
    for ignore_item in ignore_files:
    compress_command.append("-xr!" + ignore_item)
    compress_command.append(compressed_path)
    compress_command.append(src_path)
    print("Compressing with command: {}".format(" ".join(compress_command)))
    if subprocess.call(compress_command) != 0:
    print("Failed to compress {}.".format(src_path))
    return False

    # 分卷压缩后的文件名列表
    compressed_path_parts = compressed_path.split(".")
    compressed_path_parts[-1] = "7z"
    compressed_path_prefix = ".".join(compressed_path_parts)

    # 同步压缩后的文件夹到备份路径下
    dest_path = os.path.join(backup_path, prefix)
    print("Backing up {} to {} ...".format(os.path.dirname(comressed_path), dest_path))
    if subprocess.call(["rclone", "--config", rclone_config_file, "copy", "--transfers", str(rclone_transfers), "--progress", os.path.dirname(compressed_path), dest_path]) != 0:
    print("Backup failed for {}!".format(src_path))
    return False
    else:
    file_count_output = subprocess.check_output(["rclone", "--config", rclone_config_file, "ls", dest_path], universal_newlines=True)
    file_count = len(file_count_output.splitlines())
    print("Backup succeed. {} files has been backed up to {}".format(file_count, dest_path))

    print("Deteled temp folder for {}!".format(os.path.dirname(compressed_path)))
    shutil.rmtree(os.path.dirname(compressed_path))
    return True

    # 删除指定文件夹下的老备份
    def delete_old_backups(backup_dir, max_backup_count):
    # 获取备份文件的列表,并按照时间戳排序
    file_list_output = subprocess.check_output(["rclone", "--config", rclone_config_file, "lsf", backup_dir], universal_newlines=True)
    file_list = sorted(file_list_output.splitlines())

    # 计算需要删除的备份文件数量
    file_count = len(file_list)
    files_to_delete = file_count - max_backup_count

    # 如果要删除的文件数量小于等于 0 ,直接返回
    if files_to_delete <= 0:
    print("No old backups need to be deleted for {}.".format(backup_dir))
    return True

    # 删除最老的若干个备份文件
    oldest_files = file_list[:files_to_delete]
    for file_name in oldest_files:
    print("Deleting {} ...".format(file_name))
    if subprocess.call(["rclone", "--config", rclone_config_file, "purge", os.path.join(backup_dir, file_name)]) != 0:
    print("Failed to delete old backup {}.".format(file_name))
    return False

    print("Deleted {} old backups for {}.".format(files_to_delete, backup_dir))
    return True

    print("Backup script started at", datetime.today().strftime("%Y-%m-%d %H:%M:%S"))

    # 遍历要备份的文件夹并执行备份操作
    print("Starting backups...")
    for item in backup_dirs:
    dir_path = item.get("path")
    backup_path = item.get("backup_path", config["backup_path"])
    max_backup_count = item.get("max_backup_count", config["max_backup_count"])
    enabled = item.get("enabled", config["enabled"])
    ignore_files = item.get("ignore_files", config["ignore_files"])# 配置忽略文件
    compress_rate = item.get("compress_rate", config["compress_rate"]) # 配置压缩率
    volume_size = item.get("volume_size",config["volume_size"]) # 配置卷大小
    temp_dir = item.get("temp_dir",config["temp_dir"]) # 临时文件夹
    if not os.path.isdir(dir_path):
    print("Cannot find directory {}.".format(dir_path))
    continue
    if not enabled:
    print("Skipping backup for {} as it is not enabled.".format(dir_path))
    continue
    if subprocess.call(["rclone", "--config", rclone_config_file, "mkdir", os.path.join(backup_path, os.path.basename(dir_path))]) != 0:
    print("Failed to create backup storage path for {}!".format(dir_path))
    continue
    if not backup_folder(dir_path, os.path.join(backup_path, os.path.basename(dir_path)), datetime.today().strftime("%Y%m%d%H%M"), enabled, ignore_files, compress_rate, volume_size, temp_dir) or not delete_old_backups(os.path.join(backup_path, os.path.basename(dir_path)), max_backup_count):
    print("Failed to backup and delete old backups for {}.".format(dir_path))

    print("Backup script completed at", datetime.today().strftime("%Y-%m-%d %H:%M:%S"))
    ```

    ```json
    {
    "compress_password": "password", // 压缩密码
    "rclone_transfers": 4, // rclone 同步时的并发数
    "rclone_config_file": "/path/to/rclone/config/file", // rclone 的配置文件路径
    "backup_path": "remote:path/to/backup/folder", // 备份文件夹的远程路径
    "backup_dirs": [ // 要备份的文件夹列表
    {
    "path": "/path/to/backup/folder1", // 要备份的文件夹路径
    "backup_path": "remote:path/to/backup/folder1", // 备份文件夹的远程路径,如果不指定,则使用默认的 backup_path
    "max_backup_count": 5, // 保留的备份文件数量,超过这个数量的备份文件将被删除,如果不指定,则默认为 3
    "enabled": true, // 是否启用备份,如果为 false ,则跳过备份,如果不指定,则默认为 true
    "ignore_files": ["*.log", "cache"], // 要忽略的文件列表,支持通配符,如果不指定,则备份所有文件
    "compress_rate": 7 // 压缩率,范围为 0-9 ,0 表示不压缩,9 表示最高压缩率,如果不指定,则默认为 5
    },
    {
    "path": "/path/to/backup/folder2", // 要备份的文件夹路径
    "backup_path": "remote:path/to/backup/folder2", // 备份文件夹的远程路径,如果不指定,则使用默认的 backup_path
    "max_backup_count": 3, // 保留的备份文件数量,超过这个数量的备份文件将被删除,如果不指定,则默认为 3
    "enabled": false // 是否启用备份,如果为 false ,则跳过备份,如果不指定,则默认为 true
    }
    ]
    }
    {
    "backup_path": "remote:/backup", // 备份存储路径,需要使用 rclone 支持的远程存储方式
    "max_backup_count": 7, // 每个备份文件夹最多保留的备份数量
    "enabled": true, // 全局开关,控制是否启用备份
    "ignore_files": ["*.log", "*.tmp"], // 需要忽略备份的文件,支持通配符
    "compress_password": "123456", // 压缩密码,需要与备份脚本中的一致
    "rclone_transfers": 4, // rclone 备份时的并发传输数,默认为 4
    "rclone_config_file": "/path/to/rclone.conf", // rclone 配置文件路径
    "backup_dirs": [ // 需要备份的文件夹列表
    {
    "path": "/path/to/backup/folder1", // 文件夹路径
    "backup_path": "remote:/backup/folder1", // 备份存储路径,若不指定则使用全局备份路径
    "max_backup_count": 30, // 最多保留的备份数量,若不指定则使用全局值
    "enabled": true, // 是否启用备份,若不指定则使用全局值
    "ignore_files": ["*.log"], // 忽略备份的文件,若不指定则使用全局值
    "compress_rate": 5, // 压缩率,取值范围为 0 到 9 ,默认为 5
    "volume_size": "1024m" // 分卷大小,默认为 1024m
    },
    {
    "path": "/path/to/backup/folder2",
    "enabled": false // 禁用备份,其他值将使用全局值
    }
    ]
    }
    ```
    harvies
        12
    harvies  
       2023-06-29 09:37:04 +08:00 via Android
    @harvies 加密压缩备份通过 rclone 同步到 oss ,增量备份,针对数据量小可以用这个脚本。数据量大,如几百 g 、上 T 建议直接同步到 nas
    harvies
        13
    harvies  
       2023-06-29 09:40:01 +08:00 via Android
    @harvies 增量备份 打错了,是每次全量备份,可设置保留历史份数
    datocp
        14
    datocp  
       2023-06-29 09:48:16 +08:00
    两地三中心。。。哪有这么有钱。

    windows mssql 数据库,用的 7zip 压缩能节省 16 倍空间占用
    for %%X in (*.bak) do (echo "%%X"
    "c:\Program Files\7-Zip\7z.exe" a "%%X.7z" -p123 -mhe "%%X" -sdel)

    使用 SyncBack_NI_ZH 用 windows 共享同步到另外一台电脑上。

    ==================
    vps
    使用 tar 直接备份,cron 定期执行只保留最近的 72 小时备份。
    cat wbackup.sh
    #!/bin/sh
    cd /bak/wekan
    /mongodb/bin/mongodump -h 127.0.0.1:27019 -d wekan -o /bak/wekan
    tar -czvf /bak/wekan/wekan$(date +%Y%m%d).tar.gz ./wekan
    now="`date +%s` - 259200" #72hour
    now=`expr $now`
    >/tmp/wekan.date;ls /bak/wekan/*.gz -lu| awk '{print $9}'>>/tmp/wekan.date
    for i in $(cat /tmp/wekan.date); do time=`date +%s -r $i`;
    if [ "$time" -lt "$now" ];then echo $i;
    rm -rf $i;
    fi;done
    rm -rf /bak/wekan/wekan
    taygetus
        15
    taygetus  
       2023-06-29 09:55:53 +08:00
    我的方案是
    alist 挂载阿里云盘
    docker 映射目录通过 docker:linuxserver/duplicati 加密备份到 alist.webdav
    不敏感的数据通过 docker:tickstep/aliyunpan-sync 自动备份到阿里云盘
    npe
        16
    npe  
       2023-06-29 09:59:00 +08:00
    云服务器都有快照功能
    robking
        17
    robking  
    OP
       2023-06-29 10:39:13 +08:00
    @npe #16 快照的话,服务器过期了数据还是没有了吧
    wdssmq
        18
    wdssmq  
       2023-06-29 10:46:47 +08:00
    想问下备份时需要考虑文件占用么? Docker 要不要先停掉?
    robking
        19
    robking  
    OP
       2023-06-29 10:48:43 +08:00
    @taygetus #15 用了一下 duplicati 感觉不是很好用诶
    robking
        20
    robking  
    OP
       2023-06-29 10:49:42 +08:00
    @harvies #11 谢谢大佬
    robking
        21
    robking  
    OP
       2023-06-29 10:51:25 +08:00
    @dann73580 #7 rclone 没用过诶,类似于 WebDav 这种吗?绑定国外的云存储稳定吗?
    robking
        22
    robking  
    OP
       2023-06-29 10:52:11 +08:00
    @sss15 #9 我也是小白,磁盘镜像万一 服务器过期了怎么办
    robking
        23
    robking  
    OP
       2023-06-29 10:53:15 +08:00
    @woshinide300yuan #8 这里的内网是什么意思 大佬指教一下
    robking
        24
    robking  
    OP
       2023-06-29 10:53:57 +08:00
    @datocp #14 谢谢老哥
    woshinide300yuan
        25
    woshinide300yuan  
       2023-06-29 11:32:41 +08:00
    @robking 宝塔面板有备份插件,配置时可以填写阿里云 OSS 的内网节点 URL ,前提是主机和 OSS 是同一个地区,你不能北京主机,OSS 是深圳的,那就是外网走宽带了。
    都是深圳的话,备份插件里填写内网地址,再写上 key 什么的。就秒备份,最浪费时间的可能就是打包了。传输很快很快很快~~~
    robking
        26
    robking  
    OP
       2023-06-29 12:32:43 +08:00
    @woshinide300yuan #25 我现在没有用宝塔了,用的 1panel 面板
    YGHMXFAL
        27
    YGHMXFAL  
       2023-06-29 13:15:36 +08:00
    备份工具和策略都好说,你们存储到哪个云端?
    YGHMXFAL
        28
    YGHMXFAL  
       2023-06-29 13:18:08 +08:00
    @YGHMXFAL 汗,点错了

    隔壁又有人的加密备份数据被天翼云盘毙了,我自己也经历过被百度盘毙了加密压缩包

    所以墙内哪个云端允许存储加密数据吗?墙外平台倒是选择多,但是网络不是总可达,万一急用呢
    robking
        29
    robking  
    OP
       2023-06-29 14:47:19 +08:00
    @YGHMXFAL #27 老哥备份工具用的什么呀?
    YGHMXFAL
        30
    YGHMXFAL  
       2023-06-29 15:23:59 +08:00
    @robking restic+rclone,随便糊一个脚本就行了
    whitehack
        31
    whitehack  
       2023-06-29 18:06:51 +08:00
    我有个 mysql 数据库. 写了个定时脚本. 每 10 个小时 用 rsync 备份数据到其它服务器.
    sss15
        32
    sss15  
       2023-07-03 17:23:19 +08:00
    @robking #22 只要你磁盘镜像没过期,随时可以通过磁盘镜像恢复一台一模一样的服务器,有点像以前用的 ghost 磁盘恢复功能
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1034 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 18:42 PVG 02:42 LAX 11:42 JFK 14:42
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86