V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
robking
V2EX  ›  问与答

服务器 备份

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

    最近遇到一个很困恼的问题,一直迟迟没有很好的解决方案,来问问无所不知的 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(compressed_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   ·   实用小工具   ·   5532 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 08:42 · PVG 16:42 · LAX 00:42 · JFK 03:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.