vps 备份docker到nas
添加 acls 规则
进入后台的 ACL 页面,添加一条规则,明确允许 VPS 访问 NAS 的 SMB 服务端口 (445
)。
// 规则: 允许 VPS 访问 NAS 的 SMB 445 端口 { "action": "accept", "src": ["tag:vps"], "dst": ["tag:nas:445"] },
Vps 上挂载 nas 共享文件夹
此步骤的目的是让 VPS 可以像读写本地文件夹一样操作 NAS 上的共享目录。
安装 SMB/CIFS 工具 (在 VPS 上执行):
sudo apt update sudo apt install cifs-utils
创建本地挂载点 (在 VPS 上执行):
sudo mkdir -p /mnt/nas_backup
创建凭证文件 (在 VPS 上执行,推荐): 为了安全,我们将 NAS 的用户名和密码存放在一个受保护的文件中。
# 创建并编辑文件 sudo nano /etc/nas-credentials # 在文件中写入以下内容并保存 # username=您NAS的用户名 # password=您NAS的密码 # 锁定文件权限,确保只有 root 能读取 sudo chmod 600 /etc/nas-credentials
- **执行挂载 (在 VPS 上执行):
- 通过
tailscale status
命令获取您 NAS 的 Tailscale IP (例如100.x.x.x
)。 确认您 NAS 上的共享文件夹名称 (例如
vps_ryzen
)。sudo mount -t cifs //100.x.x.x/vps_ryzen /mnt/nas_backup -o credentials=/etc/nas-credentials
- 验证挂载: 运行
df -h
,如果看到/mnt/nas_backup
的条目,说明挂载成功。
部署备份脚本
这是执行备份任务的核心。
最终备份脚本 (backup_to_nas.sh
):
#!/bin/bash
# =================================================================
# Docker 数据卷与指定文件夹备份脚本 - 备份至 NAS (全功能版)
#
# 功能:
# 1. 为每一次备份任务创建一个带时间戳的独立文件夹。
# 2. 同时支持备份 Docker 数据卷和服务器上的任意文件夹。
# 3. 将所有备份文件 (.tar.gz) 存入该次任务的独立文件夹中。
# 4. 自动清理指定天数前的旧备份文件夹。
# 5. 提供详细的日志输出和错误检查。
# =================================================================
# --- 可配置部分 ---
# 1. 备份文件存放的根目录 (请确保这是您挂载的 NAS 目录)
BACKUP_ROOT_DIR="/mnt/nas_backup"
# 2. 需要备份的 Docker 卷名称列表 (用空格隔开)
# 留空则跳过: VOLUMES_TO_BACKUP=""
VOLUMES_TO_BACKUP="typecho_mariadb_data typecho_caddy_data"
# 3. 【新增】需要备份的文件夹绝对路径列表 (用空格隔开)
# - 请确保路径是绝对路径 (以 / 开头)。
# - 留空则跳过: DIRS_TO_BACKUP=""
# - 示例: DIRS_TO_BACKUP="/etc/nginx /var/www/my_website"
DIRS_TO_BACKUP="/root/typecho"
# 4. 备份文件夹保留天数 (例如,保留最近7天的备份)
RETENTION_DAYS=7
# --- 脚本核心逻辑 (一般无需修改) ---
# 设置时区
export TZ='Asia/Shanghai'
# 本次备份任务的唯一时间戳
DATE=$(date +%Y%m%d_%H%M%S)
# 为本次备份创建一个独立的、带时间戳的子目录
CURRENT_BACKUP_DIR="${BACKUP_ROOT_DIR}/${DATE}"
echo "====================================="
echo "备份任务开始于: $(date)"
echo "====================================="
echo "本次所有备份文件将保存至: ${CURRENT_BACKUP_DIR}"
# 确保本次备份的独立目录存在
mkdir -p ${CURRENT_BACKUP_DIR}
# --- 1. 备份 Docker 数据卷 ---
if [ -n "${VOLUMES_TO_BACKUP}" ]; then
echo ""
echo "--- 开始处理 Docker 数据卷 ---"
for VOLUME in ${VOLUMES_TO_BACKUP}; do
echo "-------------------------------------"
echo ">> 正在处理卷: ${VOLUME}"
if ! docker volume inspect ${VOLUME} > /dev/null 2>&1; then
echo " [错误] 卷 '${VOLUME}' 不存在,已跳过。"
continue
fi
BACKUP_FILENAME="${VOLUME}.tar.gz"
BACKUP_FULL_PATH="${CURRENT_BACKUP_DIR}/${BACKUP_FILENAME}"
echo " 备份文件将保存为: ${BACKUP_FULL_PATH}"
docker run --rm \
-v "${VOLUME}:/source_data:ro" \
-v "${CURRENT_BACKUP_DIR}:/backup_target" \
alpine \
tar -czf "/backup_target/${BACKUP_FILENAME}" -C /source_data .
if [ $? -eq 0 ]; then
echo " [成功] 卷 '${VOLUME}' 已成功备份。"
else
echo " [错误] 备份卷 '${VOLUME}' 失败!"
fi
done
else
echo "未配置 Docker 数据卷备份,跳过此步骤。"
fi
# --- 2. 【新增】备份指定文件夹 ---
if [ -n "${DIRS_TO_BACKUP}" ]; then
echo ""
echo "--- 开始处理指定文件夹 ---"
for DIR_PATH in ${DIRS_TO_BACKUP}; do
echo "-------------------------------------"
echo ">> 正在处理文件夹: ${DIR_PATH}"
# 检查文件夹是否存在且可读
if [ ! -d "${DIR_PATH}" ]; then
echo " [错误] 文件夹 '${DIR_PATH}' 不存在,已跳过。"
continue
fi
if [ ! -r "${DIR_PATH}" ]; then
echo " [错误] 文件夹 '${DIR_PATH}' 没有读取权限,已跳过。"
continue
fi
# 从完整路径中提取基本名称作为文件名 (例如 /etc/nginx -> nginx)
# 并替换路径中的斜杠'/'为下划线'_',以创建更唯一的文件名 (例如 /home/user/app -> home_user_app)
DIR_BASENAME=$(echo "${DIR_PATH}" | sed 's|^/||; s|/$||; s|/|_|g')
BACKUP_FILENAME="${DIR_BASENAME}.tar.gz"
BACKUP_FULL_PATH="${CURRENT_BACKUP_DIR}/${BACKUP_FILENAME}"
echo " 备份文件将保存为: ${BACKUP_FULL_PATH}"
# 使用 tar 命令直接打包文件夹
# -C 选项可以在打包前切换到父目录,从而避免在压缩包中包含完整的绝对路径
# 例如,打包 /var/www/html,我们切换到 /var/www 然后打包 html 文件夹
PARENT_DIR=$(dirname "${DIR_PATH}")
TARGET_DIR=$(basename "${DIR_PATH}")
tar -czf "${BACKUP_FULL_PATH}" -C "${PARENT_DIR}" "${TARGET_DIR}"
if [ $? -eq 0 ]; then
echo " [成功] 文件夹 '${DIR_PATH}' 已成功备份。"
else
echo " [错误] 备份文件夹 '${DIR_PATH}' 失败!"
fi
done
else
echo "未配置文件夹备份,跳过此步骤。"
fi
# --- 3. 清理旧备份 ---
echo ""
echo "-------------------------------------"
echo ">> 开始清理旧的备份 (保留最近 ${RETENTION_DAYS} 天的备份文件夹)..."
# 查找并删除整个旧的备份文件夹
find "${BACKUP_ROOT_DIR}" -mindepth 1 -maxdepth 1 -type d -mtime +${RETENTION_DAYS} -print -exec rm -rf {} +
echo " 旧备份清理完毕。"
echo ""
echo "所有备份任务完成于: $(date)"
echo "====================================="
部署操作:
- 将上述代码保存到 VPS 的
/root/backup_to_nas.sh
文件中。 - 根据您自己的情况,修改
BACKUP_DIR
和VOLUMES_TO_BACKUP
变量。 - 授予执行权限:
chmod +x /root/backup_to_nas.sh
。 - 手动测试:运行
/root/backup_to_nas.sh
,并去 NAS 上检查是否生成了备份文件。
设置定时任务 (Cron)
编辑 Crontab:
crontab -e
添加任务: 在文件末尾添加以下一行,设置每天凌晨2点执行备份。
0 2 * * * /root/backup_to_nas.sh > /var/log/docker_backup.log 2>&1
- 保存并退出。系统会自动加载新任务。
在配置过程中,遇到并解决了一系列典型问题,记录如下:
问题: 运行脚本时报错
$: \r: command not found
。- 原因: 脚本文件是在 Windows 系统下编辑后上传到 Linux 的,包含了 Windows 风格的换行符 (
\r\n
)。 解决方案: 在 VPS 上使用
dos2unix
或sed
命令转换文件格式。# 方法一 (推荐) dos2unix /root/backup_to_nas.sh # 方法二 (通用) sed -i 's/\r$//' /root/backup_to_nas.sh
- 原因: 脚本文件是在 Windows 系统下编辑后上传到 Linux 的,包含了 Windows 风格的换行符 (
问题: 脚本提示
[错误] 卷 'mariadb_data' 不存在
。- 原因: 使用
docker-compose
时,它会自动为卷名加上项目名(即文件夹名)作为前缀。例如,项目文件夹叫typecho
,卷名叫mariadb_data
,那么实际的卷名是typecho_mariadb_data
。 - 解决方案: 运行
docker volume ls
命令查看确切的卷名,并更新到脚本的VOLUMES_TO_BACKUP
变量中。
- 原因: 使用
问题: 挂载 SMB 共享时报错
mount error(115): Operation now in progress
。- 原因: 连接超时。这通常是由于防火墙阻止了连接。在本次实践中,是 Tailscale 的 ACL 策略默认禁止了设备间的端口访问。
- 解决方案: 登录 Tailscale 后台,修改 ACL 规则,明确添上一条允许 VPS (
tag:vps
) 访问 NAS (tag:nas
) 的445
端口的规则。
问题: 备份成功,但在 NAS 上看不到文件。
- 原因: 脚本中的
BACKUP_DIR
路径填写错误。填写的是 NAS 上的路径 (/mnt/Storage1/vps_ryzen
),但脚本是在 VPS 上运行,它应该写入到 VPS 上的挂载点 (/mnt/nas_backup
)。 - 解决方案: 将脚本中的
BACKUP_DIR
修改为 VPS 上的正确挂载点路径。
- 原因: 脚本中的
评论区(暂无评论)