Docker 数据卷怕丢?Ubuntu 一键备份神脚本:单卷 / 全卷都能搞,进度实时看得见

在 Docker 生态里,数据卷(Volume)是 “数据的最后防线”—— 它独立于容器生命周期,专门存储数据库数据、应用配置、用户上传文件等核心持久化数据。容器删了能重建,镜像丢了能重拉,但数据卷里的内容没了,很可能意味着业务数据永久丢失。

本文针对 Ubuntu 系统,从 “单个数据卷精准备份”“多个数据卷批量操作” 到 “全量数据卷自动化备份”,拆解数据卷备份的核心逻辑(关键是用临时容器安全挂载),尤其提供一套带Docker 服务检查、进度可视化、备份验证的全量备份脚本,连随机 ID 的匿名卷都能完美处理。

一、先搞懂:数据卷备份为什么不能直接 “复制文件”?

新手常犯的错是直接去 /var/lib/docker/volumes/ 复制数据卷文件 —— 这是 Docker 的内部存储路径,直接操作可能导致数据损坏(比如容器正在写入数据时),且跨系统迁移时兼容性差。

正确逻辑:通过轻量临时容器(如alpine)挂载数据卷和宿主机备份目录,在容器内用tar打包数据 —— 既避免干扰 Docker 内部存储,又能保证数据一致性,还兼容所有 Docker 环境。

前置准备:3 步搭好备份环境

  1. 检查 Docker 服务状态数据卷操作依赖 Docker 服务,先确认服务正常:

    # 查看服务状态,显示active(running)即为正常
    sudo systemctl status docker
    # 若未运行,启动服务
    sudo systemctl start docker
    
  2. 安装进度显示工具pv备份时用pv实时看进度(速度、百分比、剩余时间),避免 “黑屏等半天,不知道成没成”:

    sudo apt update && sudo apt install -y pv
    
  3. 查看数据卷列表(明确备份目标)docker volume ls列出所有数据卷,包括命名卷(如mysql_data)和匿名卷(随机 ID,如7ef48a99...):

    # 列出所有数据卷,显示名称和驱动
    sudo docker volume ls --format "名称:{{.Name}} | 驱动:{{.Driver}}"
    

二、场景 1:单个数据卷备份 —— 精准定位,安全高效

以备份命名卷mysql_data(存储 MySQL 数据)或匿名卷7ef48a99...为例,核心是 “临时容器挂载 + 压缩打包 + 进度显示”。

步骤 1:创建备份目录(建议放外接硬盘)

# 创建备份目录(路径可自定义,如外接硬盘 /media/szsy/Elements/volume_backups)
sudo mkdir -p /home/szsy/volume_backups

步骤 2:执行单个数据卷备份(支持匿名卷)

案例 A:备份命名卷mysql_data
# 格式:
# sudo docker run --rm -v 数据卷名:/source -v 宿主机备份目录:/dest alpine sh -c "tar -zcvf /dest/备份文件名.tar.gz -C /source . | pv -s \$(du -sb /source | awk '{print \$1}')"

# 实际命令(带进度显示)
sudo docker run --rm \
  -v mysql_data:/source \  # 挂载数据卷到临时容器的/source
  -v /home/szsy/volume_backups:/dest \  # 挂载宿主机备份目录到/dest
  alpine \
  sh -c "tar -zcvf - -C /source . | pv -s \$(du -sb /source | awk '{print \$1}') > /dest/mysql_data_backup_$(date +%Y%m%d).tar.gz"
案例 B:备份匿名卷7ef48a99...

匿名卷用随机 ID 命名,只需把 “数据卷名” 换成卷 ID,文件名加 ID 前缀避免混淆:

# 匿名卷ID:7ef48a99e9642d8dbe5a7530df52f37c8e3ea5e44b6f2e0b1d2da150eb0e309a
VOLUME_ID="7ef48a99e9642d8dbe5a7530df52f37c8e3ea5e44b6f2e0b1d2da150eb0e309a"
# 取ID前8位当文件名前缀,避免过长
VOLUME_SHORT_ID=$(echo $VOLUME_ID | cut -c 1-8)

sudo docker run --rm \
  -v $VOLUME_ID:/source \
  -v /home/szsy/volume_backups:/dest \
  alpine \
  sh -c "tar -zcvf - -C /source . | pv -s \$(du -sb /source | awk '{print \$1}') > /dest/volume_${VOLUME_SHORT_ID}_backup_$(date +%Y%m%d).tar.gz"

步骤 3:验证单个备份

检查备份文件是否存在且大小合理(与数据卷实际大小相近,压缩后会更小):

ls -lh /home/szsy/volume_backups/
# 示例输出:-rw-r--r-- 1 root root 456M 11月  2 10:30 mysql_data_backup_20251102.tar.gz

三、场景 2:多个数据卷备份 —— 循环批量,一次搞定

若需要同时备份多个数据卷(如mysql_data+nginx_config+ 匿名卷4787e8cf...),用for循环批量执行备份逻辑,最后将所有备份文件打包成一个压缩包,方便传输。

步骤 1:创建多个数据卷备份脚本(backup_multi_volumes.sh

#!/bin/bash
# 多个Docker数据卷批量备份脚本:含进度显示、备份验证

# ===================== 配置参数(可自定义)=====================
# 待备份的多个数据卷(命名卷写名称,匿名卷写完整ID,空格分隔)
TARGET_VOLUMES="mysql_data nginx_config 4787e8cf30a31ea23794b63fde31e6be4fba7d209a5ee2113736b8a51f3238e4"
# 备份目录(存放单个卷备份和最终打包文件)
BACKUP_DIR="/home/szsy/volume_backups"
# 最终打包文件名(带日期)
PACKAGED_FILE="multi_volumes_backup_$(date +%Y%m%d).tar.gz"

# ===================== 核心函数:备份单个数据卷 =====================
backup_single_volume() {
    local volume=$1
    # 处理匿名卷:取ID前8位当前缀
    local volume_short=$(echo $volume | cut -c 1-8)
    local backup_file="${BACKUP_DIR}/volume_${volume_short}_backup_$(date +%Y%m%d).tar.gz"
    
    echo -e "\n===== 开始备份数据卷:$volume(简称:$volume_short) ====="
    # 执行备份(临时容器挂载+tar打包+pv进度)
    sudo docker run --rm \
      -v $volume:/source \
      -v $BACKUP_DIR:/dest \
      alpine \
      sh -c "tar -zcvf - -C /source . | pv -s \$(du -sb /source | awk '{print \$1}') > $backup_file"
    
    # 验证当前卷备份
    if [ -f "$backup_file" ] && [ -s "$backup_file" ]; then
        echo "✅ 数据卷 $volume_short 备份成功,文件:$backup_file"
        echo "   文件大小:$(ls -lh $backup_file | awk '{print $5}')"
        return 0
    else
        echo "❌ 数据卷 $volume_short 备份失败!"
        return 1
    fi
}

# ===================== 主流程 =====================
# 1. 创建备份目录
sudo mkdir -p $BACKUP_DIR

# 2. 循环备份每个数据卷
local failed_count=0
for vol in $TARGET_VOLUMES; do
    if ! backup_single_volume $vol; then
        failed_count=$((failed_count + 1))
    fi
done

# 3. 打包所有单个备份文件(带进度)
echo -e "\n===== 开始打包所有备份文件 ====="
local total_size=$(sudo du -sb $BACKUP_DIR/*.tar.gz | awk '{sum += $1} END {print sum}')
sudo tar -zcf - -C $BACKUP_DIR *.tar.gz | pv -s $total_size > $BACKUP_DIR/$PACKAGED_FILE

# 4. 验证打包结果
if [ -f "$BACKUP_DIR/$PACKAGED_FILE" ] && [ -s "$BACKUP_DIR/$PACKAGED_FILE" ]; then
    echo -e "\n🎉 多个数据卷备份完成!"
    echo "📁 最终打包文件:$BACKUP_DIR/$PACKAGED_FILE"
    echo "📊 打包大小:$(ls -lh $BACKUP_DIR/$PACKAGED_FILE | awk '{print $5}')"
    echo "⚠️  单个卷备份文件已保留在 $BACKUP_DIR,可按需删除"
else
    echo -e "\n❌ 打包失败!"
    exit 1
fi

# 5. 提示失败情况
if [ $failed_count -gt 0 ]; then
    echo -e "\n⚠️  注意:共 $failed_count 个数据卷备份失败,请查看上文错误信息"
    exit 1
fi

步骤 2:运行多个数据卷备份脚本

# 赋予执行权限
chmod +x backup_multi_volumes.sh
# 运行脚本(需sudo,因涉及Docker和文件写入)
sudo ./backup_multi_volumes.sh

四、场景 3:全部数据卷备份 —— 一键脚本,自动化兜底

当需要备份系统中所有数据卷(包括新增的匿名卷)时,手动列卷名效率低且易遗漏。以下提供一套 “全自动化脚本”,涵盖 “Docker 服务检查→卷列表获取→进度备份→打包验证” 全流程,连匿名卷都能智能命名。

1. 全量数据卷备份脚本(backup_all_volumes.sh

#!/bin/bash
# Ubuntu Docker全量数据卷备份脚本:含服务检查、进度可视化、备份验证
# 支持命名卷、匿名卷,自动清理临时文件

# ===================== 配置参数(可自定义)=====================
BACKUP_ROOT="/home/szsy/volume_backups"  # 备份根目录
TEMP_BACKUP_DIR="${BACKUP_ROOT}/temp_all_volumes"  # 临时备份目录(存单个卷备份)
FINAL_PACKAGE="${BACKUP_ROOT}/all_volumes_backup_$(date +%Y%m%d_%H%M%S).tar.gz"  # 最终打包文件
DATE_TAG=$(date +%Y%m%d)  # 日期标签

# ===================== 1. 检查Docker服务状态 =====================
check_docker() {
    echo -e "[1/6] 检查Docker服务状态..."
    if ! sudo systemctl is-active --quiet docker; then
        echo "Docker服务未运行,尝试启动..."
        if sudo systemctl start docker; then
            echo "Docker服务启动成功,等待3秒初始化..."
            sleep 3
        else
            echo "ERROR:Docker服务启动失败,请手动检查!"
            exit 1
        fi
    else
        echo "Docker服务已正常运行"
    fi
}

# ===================== 2. 检查pv进度工具 =====================
check_pv() {
    echo -e "\n[2/6] 检查进度显示工具pv..."
    if ! command -v pv &> /dev/null; then
        echo "ERROR:未安装pv工具!请先执行:sudo apt update && sudo apt install -y pv"
        exit 1
    else
        echo "pv工具已安装,支持进度可视化"
    fi
}

# ===================== 3. 获取所有有效数据卷 =====================
get_all_volumes() {
    echo -e "\n[3/6] 获取所有有效数据卷..."
    # 提取所有数据卷名称(排除无效卷,去重)
    ALL_VOLUMES=$(sudo docker volume ls --format "{{.Name}}" | grep -v -E "^$|<none>" | sort -u)
    
    if [ -z "$ALL_VOLUMES" ]; then
        echo "未找到有效数据卷,无需备份,脚本退出"
        exit 0
    fi
    
    # 显示待备份卷列表(区分命名卷和匿名卷)
    VOLUME_COUNT=$(echo "$ALL_VOLUMES" | wc -l)
    echo -e "\n共发现 $VOLUME_COUNT 个有效数据卷,即将备份:"
    echo "$ALL_VOLUMES" | while read -r vol; do
        # 匿名卷判断:长度>16且无特殊字符(命名卷通常较短且有意义)
        if [ ${#vol} -gt 16 ] && [[ $vol =~ ^[0-9a-f]+$ ]]; then
            echo "  - 匿名卷:$vol(简称:${vol:0:8})"
        else
            echo "  - 命名卷:$vol"
        fi
    done
}

# ===================== 4. 初始化临时备份目录 =====================
init_temp_dir() {
    echo -e "\n[4/6] 初始化临时备份目录..."
    # 删除旧临时目录(避免残留文件干扰)
    if [ -d "$TEMP_BACKUP_DIR" ]; then
        sudo rm -rf "$TEMP_BACKUP_DIR"
    fi
    # 创建新临时目录
    sudo mkdir -p "$TEMP_BACKUP_DIR"
    echo "临时备份目录已创建:$TEMP_BACKUP_DIR"
}

# ===================== 5. 批量备份所有数据卷(核心函数) =====================
backup_all_volumes() {
    echo -e "\n[5/6] 开始批量备份所有数据卷..."
    local failed_count=0
    
    for volume in $ALL_VOLUMES; do
        # 处理卷名称:命名卷用原名,匿名卷用前8位简称
        if [ ${#volume} -gt 16 ] && [[ $volume =~ ^[0-9a-f]+$ ]]; then
            vol_type="匿名卷"
            vol_short="${volume:0:8}"
        else
            vol_type="命名卷"
            vol_short="$volume"
        fi
        
        local backup_file="${TEMP_BACKUP_DIR}/volume_${vol_short}_backup_${DATE_TAG}.tar.gz"
        echo -e "\n===== 备份 $vol_type:$volume(简称:$vol_short) ====="
        
        # 执行备份:临时容器挂载+tar打包+pv进度(计算数据卷实际大小)
        sudo docker run --rm \
          -v $volume:/source \
          -v $TEMP_BACKUP_DIR:/dest \
          alpine \
          sh -c "tar -zcvf - -C /source . | pv -s \$(du -sb /source | awk '{print \$1}') > $backup_file"
        
        # 验证当前卷备份
        if [ -f "$backup_file" ] && [ -s "$backup_file" ]; then
            echo "✅ $vol_type $vol_short 备份成功,文件大小:$(ls -lh $backup_file | awk '{print $5}')"
        else
            echo "❌ $vol_type $vol_short 备份失败!"
            failed_count=$((failed_count + 1))
            # 删除失败的空文件
            sudo rm -f "$backup_file"
        fi
    done
    
    # 提示失败情况
    if [ $failed_count -gt 0 ]; then
        echo -e "\n⚠️  注意:共 $failed_count 个数据卷备份失败,请查看上文错误信息"
    else
        echo -e "\n✅ 所有数据卷备份完成,无失败案例"
    fi
}

# ===================== 6. 打包所有备份+验证+清理 =====================
package_and_verify() {
    echo -e "\n[6/6] 打包所有备份文件并验证..."
    # 计算临时目录总大小(用于pv进度百分比)
    local total_size=$(sudo du -sb "$TEMP_BACKUP_DIR" | awk '{print $1}')
    
    if [ $total_size -eq 0 ]; then
        echo "ERROR:临时目录无有效备份文件,打包终止!"
        exit 1
    fi
    
    # 打包临时目录(带进度)
    echo "正在打包,目标文件:$FINAL_PACKAGE"
    sudo tar -zcf - -C "$TEMP_BACKUP_DIR" . | pv -s "$total_size" -N "打包进度" > "$FINAL_PACKAGE"
    
    # 验证打包结果
    if [ -f "$FINAL_PACKAGE" ] && [ -s "$FINAL_PACKAGE" ]; then
        echo -e "\n🎉 全量数据卷备份成功!"
        echo "📁 最终备份文件:$FINAL_PACKAGE"
        echo "📊 文件大小:$(ls -lh "$FINAL_PACKAGE" | awk '{print $5}')"
        # 清理临时目录(释放空间)
        echo "🗑️  清理临时目录..."
        sudo rm -rf "$TEMP_BACKUP_DIR"
    else
        echo -e "\n❌ 打包失败!临时目录文件保留在:$TEMP_BACKUP_DIR"
        exit 1
    fi
}

# ===================== 主流程:按顺序执行 =====================
check_docker
check_pv
get_all_volumes
init_temp_dir
backup_all_volumes
package_and_verify

echo -e "\n📝 全量数据卷备份脚本执行完毕!建议定期测试恢复(参考下文恢复提示)"

2. 全量备份脚本使用步骤

步骤 1:保存脚本并赋予权限
# 1. 创建脚本文件
nano /home/szsy/docker_scripts/backup_all_volumes.sh

# 2. 粘贴上面的脚本内容,按Ctrl+O保存,Ctrl+X退出

# 3. 赋予执行权限
sudo chmod +x /home/szsy/docker_scripts/backup_all_volumes.sh
步骤 2:运行全量备份脚本
# 进入脚本目录
cd /home/szsy/docker_scripts/

# 运行脚本(必须加sudo,因涉及Docker和系统文件操作)
sudo ./backup_all_volumes.sh
步骤 3:查看脚本执行效果

脚本会按 6 个步骤自动执行,关键输出示例:

  • 识别出 “命名卷” 和 “匿名卷”,分别显示简称;
  • 每个卷备份时显示实时进度(如1.2GB 0:00:20 [60MB/s] [=====> ] 45%);
  • 最后生成一个压缩包(如all_volumes_backup_20251102_143000.tar.gz),并清理临时文件。

3. 数据卷恢复提示(关键!)

备份的最终目的是恢复,以恢复全量备份中的mysql_data卷为例:

# 1. 解压全量备份包到临时目录
sudo tar -zxvf /home/szsy/volume_backups/all_volumes_backup_20251102_143000.tar.gz -C /tmp/restore_volumes

# 2. 找到mysql_data的备份文件(假设文件名为volume_mysql_data_backup_20251102.tar.gz)
RESTORE_FILE="/tmp/restore_volumes/volume_mysql_data_backup_20251102.tar.gz"

# 3. 恢复到目标数据卷(若目标卷不存在,先创建:sudo docker volume create mysql_data)
sudo docker run --rm \
  -v mysql_data:/dest \  # 目标数据卷(恢复到这里)
  -v /tmp/restore_volumes:/source \  # 备份文件所在目录
  alpine \
  sh -c "tar -zxvf /source/volume_mysql_data_backup_20251102.tar.gz -C /dest"

# 4. 验证恢复:查看数据卷内容
sudo docker run --rm -v mysql_data:/source alpine ls -lh /source

五、避坑指南:这 4 个细节决定数据卷备份成败

  1. 数据一致性:先停容器再备份(核心!)若数据卷被运行中的容器使用(如 MySQL 正在写入数据),直接备份可能导致数据损坏(内存数据未刷盘)。正确操作:

    # 1. 停止使用数据卷的容器(替换为你的容器名)
    sudo docker stop mysql_container
    # 2. 执行数据卷备份(同上)
    # 3. 备份完成后重启容器
    sudo docker start mysql_container
    

    若无法停止服务(生产环境),可先用应用层工具备份数据(如mysqldump导出 SQL),再备份数据卷。

  2. 备份路径选对:避免空间不足数据卷可能包含大量数据(如数据库文件、视频文件),建议将BACKUP_ROOT配置为外接硬盘路径(如/media/szsy/Elements/volume_backups),用df -h检查剩余空间:

    df -h /media/szsy/Elements/  # 查看外接硬盘剩余空间
    
  3. 定期清理旧备份:避免空间爆炸全量备份文件可能占用几十 GB 甚至上百 GB,建议在脚本中添加 “清理 30 天前旧备份” 的逻辑(添加到package_and_verify函数末尾):

    # 清理30天前的备份文件(仅保留最近30天)
    find $BACKUP_ROOT -name "all_volumes_backup_*.tar.gz" -type f -mtime +30 -delete
    echo "已清理30天前的旧备份文件"
    
  4. 权限问题:始终用 sudo 执行脚本Docker 数据卷和系统文件操作需要管理员权限,省略sudo会导致 “Permission denied” 错误,务必用sudo ./backup_all_volumes.sh运行。

总结

数据卷是 Docker 中 “最值钱” 的部分,备份逻辑的核心是 “通过临时容器安全挂载”,而非直接操作内部文件。本文提供的方案覆盖了从 “单个卷精准备份” 到 “全量卷自动化备份” 的所有场景,尤其全量脚本解决了 “匿名卷识别”“进度可视化”“备份验证” 三大痛点。

建议将全量备份脚本加入crontab定时任务(如每周一凌晨 2 点执行),搭配数据卷恢复测试(每月 1 次),真正做到 “数据卷万无一失”。记住:容器和镜像丢了能重建,但数据卷丢了,可能就再也找不回来了。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值