社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

真正的生产力来了!Docker迁移部署两步搞定!

dotNET跨平台 • 2 月前 • 77 次点击  

前言

最近遇到了需要部署一套比较复杂的应用场景,刚好这套应用我在其他服务器部署过,为了节省折腾的时间,我打算直接把服务器上已有的搬过去。

PS:没想到这个过程比从头开始来耗费时间😂

好在是把一键迁移的脚本也搞出来了,以后遇到类似的情况就比较舒服了。

Docker 的一个典型优势场景就是可移植性

只需要把原服务器上的 应用相关目录 和 docker-compose.yml 文件 打包复制过去,在目标服务器上解压、部署即可。

本文记录一下 docker 迁移部署的过程。

打包原服务器的应用目录

需要找到 docker-compose 项目目录,一般包含:

  • docker-compose.yml
  • .env(如果有)
  • 其他挂载卷的本地目录,如 ./data./config./db 等

然后执行:

tar czvf myapp.tar.gz myapp/

复制

建议使用 scp 命令复制

这个是最方便的

scp myapp.tar.gz user@目标IP:/路径/

当然用 rsync 也可以,这个效率更高。但我习惯 scp 够用了。

迁移数据卷

如果  docker-compose.yml 中定义的 volumes 是 命名卷(named volumes),而不是绑定到主机目录(bind mount)。

例如:

volumes:
  oradata:
  dify_es01_data:

docker 通常是 /var/lib/docker/volumes/ 管理这些数据

数据卷会麻烦一些,需要导出和导入

导出卷数据

docker run --rm -v oradata:/data -v $(pwd):/backup alpine tar czf /backup/oradata.tar.gz -C /data .
docker run --rm -v dify_es01_data:/data -v $(pwd):/backup alpine tar czf /backup/dify_es01_data.tar.gz -C /data .

复制

继续使用 scp 导出




    
scp oradata.tar.gz dify_es01_data.tar.gz user@remote:/your/path/

创建空卷

在目标服务器创建空卷

docker volume create oradata
docker volume create dify_es01_data

导入数据

导入数据到卷

docker run --rm -v oradata:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/oradata.tar.gz"
docker run --rm -v dify_es01_data:/data -v $(pwd):/backup alpine sh -c "cd /data && tar xzf /backup/dify_es01_data.tar.gz"

解压&启动

tar xzvf myapp.tar.gz
cd myapp/
docker-compose up -d

一键迁移脚本

这么多步骤执行下来还是太麻烦

我让大模型爷爷帮忙设计了一个一键迁移脚本

在反复打磨之下,这个脚本体验还是非常不错的,一百多行的代码可以实现自动识别数据卷,自动打包成大文件夹并复制到目标服务器

有需要的同学可以试试

将以下文件保存为 docker-app-pack.sh

#!/bin/bash

# Docker Compose 应用打包脚本
set -e

# 简化日志函数
log() { echo"⏰ [$(date +'%H:%M:%S')$1" >&2; }
error() { echo"❌ [ERROR] $1" >&2; exit 1; }

# 检查 Docker
! docker info >/dev/null 2>&1 && error  "Docker 未运行"

# 发现项目相关的数据卷
find_project_volumes() {
    local app_dir="$1"
    local project_name=$(basename "$app_dir")
    
    log"🔍 搜索项目相关数据卷 (前缀: $project_name)"
    docker volume ls --format "{{.Name}}" | grep "^${project_name}[_-]" || true
}

# 导出数据卷
export_volume() {
    local volume="$1" backup_dir="$2"
    log"💾 导出数据卷: $volume"
    docker run --rm -v "$volume:/data" -v "$backup_dir:/backup" alpine \
        sh -c "cd /data && tar czf /backup/${volume}.tar.gz ."
}

# 打包应用目录
package_app() {
    local app_dir="$1" backup_dir="$2" app_name="$3"
    log"📦 打包应用目录: $app_dir"
    tar czf "${backup_dir}/${app_name}_app.tar.gz" -C "$(dirname "$app_dir")""$(basename "$app_dir")"
}

# 创建最终压缩包
create_package() {
    local backup_dir="$1" app_name="$2" output_dir="$3"
    log"🗜️ 创建最终压缩包"
    tar czf "${output_dir}/${app_name}.tar.gz" -C "$backup_dir" .
}

# 上传到服务器
upload_file() {
    local file="$1" server="$2"
    [[ -z "$server" ]] && return 0
    log"🚀 上传到服务器: $server"
    scp " $file""$server"
}

# 主函数
main() {
    echo"🐳 === Docker Compose 应用打包工具 === 🐳"
    echo
    
    # 获取输入
    read -p "📁 应用目录路径: " app_dir
    [[ ! -d "$app_dir" ]] && error "目录不存在: $app_dir"
    
    app_name=$(basename "$app_dir")
    read -p "📦 应用名称 [$app_name]: " input_name
    [[ -n "$input_name" ]] && app_name="$input_name"
    
    # 自动发现数据卷
    auto_volumes=($(find_project_volumes "$app_dir"))
    echo"💾 发现数据卷:"
    if [[ ${#auto_volumes[@]} -eq 0 || (${#auto_volumes[@]} -eq 1 && -z "${auto_volumes[0]}") ]]; then
        echo"   └── 无"
    else
        for vol in"${auto_volumes[@]}"do
            [[ -n "$vol" ]] && echo"   └── $vol"
        done
    fi
    
    read -p "➕ 额外数据卷 (空格分隔): " extra_volumes
    volumes=(${auto_volumes[@]}$extra_volumes)
    
    read -p "🌐 上传服务器 (user@host:/path): " server
    
    # 显示摘要
    echo
    echo"📋 === 操作摘要 ==="
    echo"📂 应用目录: $app_dir"
    echo"📦 输出文件: ${app_name}.tar.gz"
    echo"💾 数据卷:"
    if [[ ${#volumes[@]} -eq 0 || (${#volumes[@]} -eq 1 && -z "${volumes[0]}") ]]; then
        echo"   └── 无"
    else
        for vol in" ${volumes[@]}"do
            [[ -n "$vol" ]] && echo"   └── $vol"
        done
    fi
    echo"🌐 上传服务器: ${server:-无}"
    echo
    
    read -p "✅ 确认执行? (y/N): " confirm
    [[ "$confirm" != [yY] ]] && exit 0
    
    # 执行备份
    backup_dir="/tmp/backup_$$"
    mkdir -p "$backup_dir"
    trap"rm -rf '$backup_dir'" EXIT
    
    # 打包应用
    package_app "$app_dir""$backup_dir""$app_name"
    
    # 导出数据卷
    for vol in"${volumes[@]}"do
        [[ -n "$vol" ]] && export_volume "$vol""$backup_dir"
    done
    
    # 创建最终包
    output_file="${app_name}.tar.gz"
    create_package "$backup_dir""$app_name""$(pwd)"
    
    # 上传
    upload_file "$output_file""$server"
    
    echo
    echo"🎉 === 备份完成! ==="
    echo"📁 文件: $output_file"
    echo"📏 大小: $(du -h "$output_file" | cut -f1)"
    echo"✨ 备份成功完成! ✨"
}

# 运行主函数
main "$@"

解包脚本

对应的有解包脚本,docker-app-unpack.sh

#!/bin/bash

set -e

# 简化日志函数
log() { echo"⏰ [$(date +'%H:%M:%S')$1" >&2; }
error() { echo"❌ [ERROR] $1" >&2; exit 1; }

# 检查 Docker
check_docker() {
    log"🔍 检查 Docker 服务..."
    ! docker info >/dev/null 2>&1 && error "Docker 未运行或无法访问"
    log"✅ Docker 服务运行正常。"
}

# 解压主包
unpack_package() {
    local package_file="$1" temp_dir="$2"
    log"📦 解压主包: $package_file 到 $temp_dir"
    tar xzf "$package_file" -C "$temp_dir"
}

# 导入应用目录
import_app() {
    local app_tar_file="$1" target_dir="$2"
    log"📂 导入应用目录: $app_tar_file 到 $target_dir"
    mkdir -p "$target_dir"
    tar xzf "$app_tar_file" -C "$target_dir " --strip-components=1
}

# 导入数据卷
import_volume() {
    local volume_tar_file="$1" volume_name="$2"
    log"💾 导入数据卷: $volume_name (来自 $volume_tar_file)"

    if docker volume inspect "$volume_name" >/dev/null 2>&1; then
        read -p "数据卷 '$volume_name' 已存在。是否覆盖? (y/N): " confirm_overwrite
        if [[ "$confirm_overwrite" != [yY] ]]; then
            log"跳过数据卷 '$volume_name' 的导入。"
            return 0
        fi
        log"删除现有数据卷 '$volume_name'..."
        docker volume rm "$volume_name" >/dev/null
    fi

    log"创建数据卷 '$volume_name'..."
    docker volume create "$volume_name" >/dev/null

    log"导入数据到数据卷 '$volume_name'..."
    docker run --rm -v "$volume_name:/data" -v "$(dirname "$volume_tar_file"):/backup" alpine \
        sh -c "tar xzf /backup/$(basename "$volume_tar_file") -C /data"
    log"✅ 数据卷 '$volume_name' 导入成功。"
}

# 主函数
main() {
    echo"🐳 === Docker Compose 应用解包工具 === 🐳"
    echo

    check_docker

    read -p "📦 请输入待解包的 .tar.gz 文件路径: " package_file
    [[ ! -f "$package_file" ]] && error "文件不存在: $package_file"

    # 创建临时目录
    local temp_dir
    temp_dir="$(mktemp -d -t docker-unpack-XXXXXX)"
    log"创建临时目录: $temp_dir"
    trap"log '清理临时目录: $temp_dir'; rm -rf '$temp_dir'" EXIT

    unpack_package "$package_file""$temp_dir"

    echo
    echo"📋 === 解包摘要 ==="
    echo"📦 源文件: $package_file"
    echo"📁 临时解压目录: $temp_dir"

    # 查找应用目录包
    local app_tar_found=false
    for f in"$temp_dir"/*_app.tar.gz; do
        if [[ -f "$f" ]]; then
            app_tar_file="$f"
            app_tar_found=true
            break
        fi
    done

    if ! $app_tar_foundthen
        error "在解压包中未找到应用目录文件 (*_app.tar.gz)。"
    fi

    local default_app_dir="$(pwd)/$(basename "${app_tar_file%_app.tar.gz}")"
    read -p "📂 请输入应用目录解压目标路径 [$default_app_dir]: " target_app_dir
    [[ -z "$target_app_dir" ]] && target_app_dir="$default_app_dir"

    echo"应用目录将解压到: $target_app_dir"

    # 查找数据卷包
    local volume_tar_files=("$temp_dir"/*.tar.gz)
    # 过滤掉应用目录包
    volume_tar_files=( "${volume_tar_files[@]/$app_tar_file}" )

    echo"💾 发现数据卷包:"
    if [[ ${#volume_tar_files[@]} -eq 0 || (${#volume_tar_files[@]} -eq 1 && -z "${volume_tar_files[0]}") ]]; then
        echo"   └── 无"
    else
        for vol_file in"${volume_tar_files[@]}"do
            [[ -n "$vol_file" ]] && echo"   └── $(basename "$vol_file")"
        done
    fi
    echo

    read -p "✅ 确认执行? (y/N): " confirm
    [[ "$confirm" != [yY] ]] &&  exit 0

    # 执行解包和导入
    import_app "$app_tar_file""$target_app_dir"

    for vol_file in"${volume_tar_files[@]}"do
        if [[ -f "$vol_file" ]]; then
            volume_name="$(basename "${vol_file%.tar.gz}")"
            import_volume "$vol_file""$volume_name"
        fi
    done

    echo
    echo"🎉 === 解包完成! ==="
    echo"✨ 应用和数据卷已成功导入! ✨"
}

# 运行主函数
main "$@"

运行后大概是这样:

ubuntu@VM-0-3-ubuntu:~/apps-docker$ ./docker-app-unpack.sh
🐳 === Docker Compose 应用解包工具 === 🐳

⏰ [17:18:17] 🔍 检查 Docker 服务...
⏰ [17:18:17] ✅ Docker 服务运行正常。
📦 请输入待解包的 .tar.gz 文件路径: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz
⏰ [17:18:23] 创建临时目录: /tmp/docker-unpack-q47OnW
⏰ [17:18:23] 📦 解压主包: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz 到 /tmp/docker-unpack-q47OnW

📋 === 解包摘要 ===
📦 源文件: /home/ubuntu/apps-docker/zammad-docker-compose.tar.gz
📁 临时解压目录: /tmp/docker-unpack-q47OnW
📂 请输入应用目录解压目标路径 [/home/ubuntu/apps-docker/zammad-docker-compose]:
应用目录将解压到: /home/ubuntu/apps-docker/zammad-docker-compose
💾 发现数据卷包:
   └── zammad-docker-compose_elasticsearch-data.tar.gz
   └── zammad-docker-compose_postgresql-data.tar.gz
   └── zammad-docker-compose_redis-data.tar.gz
   └── zammad-docker-compose_zammad-backup.tar.gz
   └── zammad-docker-compose_zammad-storage.tar.gz

✅ 确认执行? (y/N): y
⏰ [17:18:33] 📂 导入应用目录: /tmp/docker-unpack-q47OnW/zammad-docker-compose_app.tar.gz 到 /home/ubuntu/apps-docker/zammad-docker-compose
⏰ [17:18:33] 💾 导入数据卷: zammad-docker-compose_elasticsearch-data (来自 /tmp/docker-unpack-q47OnW/zammad-docker-compose_elasticsearch-data.tar.gz)
数据卷 'zammad-docker-compose_elasticsearch-data' 已存在。是否覆盖? (y/N): y
⏰ [17:18:37] 删除现有数据卷 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] 创建数据卷 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] 导入数据到数据卷 'zammad-docker-compose_elasticsearch-data'...
⏰ [17:18:37] ✅ 数据卷 'zammad-docker-compose_elasticsearch-data' 导入成功。
⏰ [17:18:37] 💾 导入数据卷: zammad-docker-compose_postgresql-data (来自 /tmp/docker-unpack-q47OnW/zammad-docker-compose_postgresql-data.tar.gz)
数据卷 'zammad-docker-compose_postgresql-data' 已存在。是否覆盖? (y/N): y
⏰ [17:18:38] 删除现有数据卷 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:38] 创建数据卷 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:38] 导入数据到数据卷 'zammad-docker-compose_postgresql-data'...
⏰ [17:18:41] ✅ 数据卷 'zammad-docker-compose_postgresql-data' 导入成功。
⏰ [17:18:41] 💾 导入数据卷: zammad-docker-compose_redis-data (来自 /tmp/docker-unpack-q47OnW/zammad-docker-compose_redis-data.tar.gz)
数据卷 'zammad-docker-compose_redis-data' 已存在。是否覆盖? (y/N): y
⏰ [17:18:41] 删除现有数据卷 'zammad-docker-compose_redis-data'...
⏰ [17:18:41] 创建数据卷 'zammad-docker-compose_redis-data'...
⏰ [17:18:41] 导入数据到数据卷 'zammad-docker-compose_redis-data'...
⏰ [17:18:42] ✅ 数据卷 'zammad-docker-compose_redis-data' 导入成功。
⏰ [17:18:42] 💾 导入数据卷: zammad-docker-compose_zammad-backup (来自 /tmp/docker-unpack-q47OnW/zammad-docker-compose_zammad-backup.tar.gz)
数据卷 'zammad-docker-compose_zammad-backup' 已存在。是否覆盖? (y/N): y
⏰ [17:18:47] 删除现有数据卷 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:47] 创建数据卷 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:47] 导入数据到数据卷 'zammad-docker-compose_zammad-backup'...
⏰ [17:18:48] ✅ 数据卷 'zammad-docker-compose_zammad-backup' 导入成功。
⏰ [17:18:48] 💾 导入数据卷: zammad-docker-compose_zammad-storage (来自 /tmp/docker-unpack-q47OnW/zammad-docker-compose_zammad-storage.tar.gz)
数据卷 'zammad-docker-compose_zammad-storage' 已存在。是否覆盖? (y/N): y
⏰ [17:18:49] 删除现有数据卷 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:49] 创建数据卷 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:49] 导入数据到数据卷 'zammad-docker-compose_zammad-storage'...
⏰ [17:18:50] ✅ 数据卷 'zammad-docker-compose_zammad-storage' 导入成功。

🎉 === 解包完成! ===
✨ 应用和数据卷已成功导入! ✨
⏰ [17:18:50] 清理临时目录: /tmp/docker-unpack-q47OnW

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/183875
 
77 次点击