1. 项目概述与核心价值

最近在给几个客户做现场技术支持,环境挺有意思的,有在老旧工厂车间里用X86工控机的,也有在移动巡检终端上用ARM开发板的,共同点就一个: 没网 ,或者网络环境差到让你怀疑人生。客户的需求也很明确,要把一套基于微服务的应用系统跑起来,这套系统依赖了十多个Docker容器,通过docker-compose编排。这种“离线部署Docker全家桶”的需求,在工业物联网、边缘计算、涉密环境或者海外项目现场越来越常见。网上教程不少,但要么只讲X86,要么只提ARM,能把两者打通、把离线包制作、架构适配、依赖排查这些坑一次性讲明白的,真不多。今天我就结合最近踩过的坑,把从零开始制作一个同时兼容X86_64和ARM64架构的Docker与Docker Compose离线部署包的全过程,以及背后的原理掰开揉碎了讲清楚。无论你手里是Intel的服务器还是树莓派,看完这篇,应该都能搞定。

2. 离线部署的核心思路与方案选型

离线部署,说白了就是“把需要从互联网下载的东西,提前准备好,带到现场去安装”。但Docker的离线部署比普通软件复杂,因为它涉及多层依赖:Docker引擎本身、docker-compose工具、以及最重要的——各种架构的容器镜像。

2.1 为什么离线部署这么麻烦?

Docker的设计哲学是“一次构建,随处运行”,但这个“随处”有个前提:能连上Docker Hub或某个镜像仓库。在离线环境,这个前提不复存在。你需要自己扮演这个仓库的角色。麻烦主要来自三点:

  1. 多重依赖 :Docker CE(社区版)在Linux上的安装依赖一堆包( containerd.io , docker-ce-cli 等),这些包之间还有版本约束。
  2. 架构差异 :X86_64和ARM64是两种完全不同的CPU指令集架构。一个为X86编译的二进制程序或镜像,无法在ARM机器上直接运行,反之亦然。必须准备对应架构的版本。
  3. 镜像搬运 :容器镜像本质上是多层只读文件的集合(tar包)。离线环境下,你需要把所有用到的镜像及其所有依赖层,从有网环境“拉取”并“保存”出来,再到离线环境“加载”进去。

2.2 主流方案对比与我们的选择

面对这些麻烦,通常有几种思路:

方案 核心操作 优点 缺点 适用场景
全手动下载deb/rpm包 从镜像站手动下载Docker所有组件的deb/rpm包及其依赖包。 最直接,对网络理解要求低。 1. 依赖解析繁琐,易遗漏。
2. 需分别准备X86和ARM版本,工作量大。
3. 难以应对复杂依赖链。
单一架构、Docker版本固定的极简环境。
使用 apt-offline / yumdownloader 在有网机器模拟离线环境,用工具生成依赖包清单并批量下载。 能相对自动地解决系统包依赖。 1. 配置复杂,需要搭建本地模拟环境。
2. 对跨架构支持不友好。
3. 不解决Docker镜像问题。
熟悉系统包管理工具链的运维人员,用于部署基础软件。
制作内网镜像仓库 搭建私有Docker Registry(如Harbor),将所有镜像推送进去,离线环境指向该仓库。 最规范,最接近在线体验,便于管理。 1. 前期搭建和镜像同步工作量大。
2. 离线机器仍需能解析仓库域名或IP。
3. 对于一次性或临时性任务,显得笨重。
有稳定内网环境、需要持续进行镜像分发和管理的企业。
镜像保存为tar + 二进制部署 将Docker引擎和docker-compose以二进制方式安装,将所需镜像保存为tar文件。 灵活轻量 ,无需复杂服务,架构隔离清晰,非常适合混合架构环境。 1. 二进制文件需区分架构。
2. 镜像加载命令需手动执行。
临时性、移动性、多架构混合的离线部署场景。

结合我们开头提到的场景(多架构、一次性或临时部署), 第四种方案“镜像保存为tar + 二进制部署” 是最佳选择。它就像准备一个“绿色软件包”:把Docker主程序、docker-compose工具、以及所有需要的容器镜像打包在一起,带到目标机器上解压、安装、加载即可。架构界限清晰,X86的包和ARM的包分开准备,逻辑简单,不易出错。

3. 离线资源包制作全流程

接下来,我们就在一台 有互联网连接 的“打包机”上,制作这个离线资源包。假设我们的目标环境是Linux(Ubuntu/CentOS均可,命令略有差异,本文以Ubuntu为例)。

3.1 环境准备与工具确认

首先,确保打包机本身已经安装了Docker和docker-compose。这很好验证:

# 检查Docker
docker --version
# 检查docker-compose
docker-compose --version

如果没有,请先用在线方式安装它们,这是后续所有操作的基础。

我们需要为两个目标架构准备资源。虽然打包机可能是X86的,但我们可以利用Docker的特性来获取ARM架构的镜像。资源包最终目录结构规划如下:

offline-docker-package/
├── README.md
├── install.sh
├── binaries/
│   ├── x86_64/
│   │   ├── docker-<version>.tgz
│   │   └── docker-compose-<version>
│   └── aarch64/ (即ARM64)
│       ├── docker-<version>.tgz
│       └── docker-compose-<version>
└── images/
    ├── x86_64-images.tar
    └── aarch64-images.tar

3.2 第一步:获取多架构Docker引擎二进制文件

Docker官方提供了静态编译的二进制包,非常适合离线部署。我们需要分别下载X86_64和ARM64版本。

  1. 确定版本 :访问 Docker GitHub Release 页面,选择一个稳定的版本,例如 24.0.7 。记住版本号。
  2. 下载二进制包
    # 创建目录
    mkdir -p offline-docker-package/binaries/{x86_64,aarch64}
    cd offline-docker-package
    
    # 下载X86_64版本
    wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz -O binaries/x86_64/docker.tgz
    
    # 下载ARM64版本 (注意链接中的aarch64)
    wget https://download.docker.com/linux/static/stable/aarch64/docker-24.0.7.tgz -O binaries/aarch64/docker.tgz
    

    注意 aarch64 就是ARM64架构在Linux中的标准称谓。务必从官方渠道下载,确保文件完整性。

3.3 第二步:获取多架构docker-compose二进制文件

docker-compose是一个独立的Python二进制文件(或Go编译的单文件)。从v2开始,官方也提供了多架构的二进制发布。

  1. 确定版本 :访问 docker-compose GitHub Release ,例如选择 v2.24.5
  2. 下载二进制文件
    # 下载X86_64版本
    wget https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64 -O binaries/x86_64/docker-compose
    # 下载ARM64版本
    wget https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-aarch64 -O binaries/aarch64/docker-compose
    
    # 赋予执行权限(很重要!)
    chmod +x binaries/x86_64/docker-compose binaries/aarch64/docker-compose
    

3.4 第三步:准备多架构的Docker镜像

这是最核心也最容易出错的一步。我们的应用 docker-compose.yml 里可能声明了像 nginx:alpine , mysql:8.0 , redis:7 这样的镜像。默认情况下, docker pull 拉取的是与当前机器架构匹配的镜像。如何在X86打包机上获取ARM镜像呢?

答案是使用Docker的** buildx **工具,它可以构建和拉取多平台镜像。

  1. 启用并检查buildx

    # 确保buildx可用
    docker buildx version
    # 如果没有,一般安装较新版本的Docker都会自带。
    # 创建一个新的builder实例(支持多架构)
    docker buildx create --name multi-arch-builder --use
    docker buildx inspect --bootstrap
    

    inspect 命令输出中看到 linux/amd64, linux/arm64 等平台,即表示成功。

  2. 拉取多架构镜像清单 :我们并不需要真的在X86机上运行ARM镜像,只需要把镜像的“文件层”拉取到本地仓库。这可以通过 --platform 参数和 docker pull --all-tags 变通实现,但更规范的方式是使用 docker buildx imagetools 或直接 pull 时指定平台。

    # 方法:为每个需要的镜像,分别拉取不同架构的版本
    # 拉取X86架构的镜像(默认就是,所以正常pull)
    docker pull nginx:alpine
    docker pull mysql:8.0
    docker pull redis:7-alpine
    
    # 拉取ARM64架构的镜像,关键参数:--platform=linux/arm64
    docker pull --platform=linux/arm64 nginx:alpine
    docker pull --platform=linux/arm64 mysql:8.0
    docker pull --platform=linux/arm64 redis:7-alpine
    

    执行后,使用 docker images --digests 查看,你会看到同一个镜像标签(如 nginx:alpine )出现了两次,但它们的 DIGEST IMAGE ID 不同,这就是不同架构的镜像。

    实操心得 :有些镜像可能没有提供你所需架构的版本。拉取时如果报错“no matching manifest for linux/arm64 in the manifest list”,说明该镜像官方不支持ARM64。这时你需要寻找替代镜像(例如,很多软件提供了 -arm64v8 标签),或者自己用 buildx 构建。

  3. 保存镜像为tar文件 :我们需要把不同架构的镜像分别打包。

    # 创建镜像目录
    mkdir -p offline-docker-package/images
    
    # 保存X86架构的镜像
    docker save -o offline-docker-package/images/x86_64-images.tar \
        nginx:alpine \
        mysql:8.0 \
        redis:7-alpine
    
    # 保存ARM64架构的镜像
    # 注意:我们需要先筛选出ARM64的镜像ID
    # 一个小技巧:通过`docker image inspect --format='{{.Id}} {{.Architecture}}' <image_name>`查看架构
    # 假设我们查得ARM版nginx的ID是 sha256:abc123...
    # 更稳妥的方法是打上标签区分:
    docker tag nginx:alpine nginx:alpine-arm64
    docker tag mysql:8.0 mysql:8.0-arm64
    docker tag redis:7-alpine redis:7-alpine-arm64
    # 然后保存带标签的镜像
    docker save -o offline-docker-package/images/aarch64-images.tar \
        nginx:alpine-arm64 \
        mysql:8.0-arm64 \
        redis:7-alpine-arm64
    

    现在, images 目录下就有了两个巨大的tar文件,分别包含了对应架构的镜像层。

3.5 第四步:编写自动化安装脚本

为了让离线部署更简单,我们写一个 install.sh 脚本,让它自动检测架构、安装二进制文件、加载镜像。

#!/bin/bash
# offline-docker-package/install.sh

set -e # 遇到错误立即退出

echo "开始离线安装Docker与Docker Compose..."

# 1. 检测系统架构
ARCH=$(uname -m)
case "$ARCH" in
    x86_64|amd64)
        TARGET_ARCH="x86_64"
        ;;
    aarch64|arm64)
        TARGET_ARCH="aarch64"
        ;;
    *)
        echo "不支持的架构: $ARCH"
        exit 1
        ;;
esac
echo "检测到系统架构为: $TARGET_ARCH"

BIN_DIR="./binaries/$TARGET_ARCH"
IMAGE_TAR="./images/${TARGET_ARCH}-images.tar"

# 2. 检查必要文件是否存在
if [[ ! -d "$BIN_DIR" ]]; then
    echo "错误:未找到对应架构($TARGET_ARCH)的二进制文件目录。"
    exit 1
fi
if [[ ! -f "$IMAGE_TAR" ]]; then
    echo "警告:未找到对应架构的镜像包 $IMAGE_TAR,跳过镜像加载。"
fi

# 3. 安装Docker二进制文件
echo "正在安装Docker引擎..."
tar -xzf "$BIN_DIR/docker.tgz" -C /tmp
sudo cp /tmp/docker/* /usr/bin/
sudo chmod +x /usr/bin/docker*

# 4. 创建Docker系统服务(适用于systemd系统)
if command -v systemctl &> /dev/null; then
    echo "配置Docker系统服务..."
    sudo groupadd docker 2>/dev/null || true
    # 这里需要准备docker.service文件,我们可以将其嵌入脚本,或者单独放在package里
    # 简单起见,我们使用从官方包提取的service文件。假设我们有一个预制的docker.service文件在package根目录
    if [[ -f ./docker.service ]]; then
        sudo cp ./docker.service /etc/systemd/system/
        sudo systemctl daemon-reload
        sudo systemctl enable docker
        echo "Docker服务已配置为开机自启。"
    else
        echo "未找到docker.service文件,请手动配置服务。"
    fi
fi

# 5. 安装docker-compose
echo "正在安装Docker Compose..."
sudo cp "$BIN_DIR/docker-compose" /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# 6. 加载Docker镜像
if [[ -f "$IMAGE_TAR" ]]; then
    echo "正在加载Docker镜像..."
    sudo docker load -i "$IMAGE_TAR"
    echo "镜像加载完成。"
fi

# 7. 验证安装
echo "验证安装..."
docker --version
docker-compose --version
echo "离线安装完成!请将当前用户加入'docker'组以非root运行docker命令:"
echo "  sudo usermod -aG docker \$USER"
echo "然后注销并重新登录。"

这个脚本做了几件关键事:架构检测、文件拷贝、服务注册、镜像加载。注意, docker.service 文件需要你提前从一台在线安装的相同发行版机器上拷贝一份(位于 /lib/systemd/system/docker.service ),并放入离线包根目录。

4. 离线环境部署实操与问题排查

现在,我们将制作好的 offline-docker-package 目录整个拷贝到目标离线机器上(用U盘、内网共享都可以)。

4.1 部署步骤

  1. 传输并解压 :将整个包放到目标机器,例如 /opt/ 下。
  2. 执行安装
    cd /opt/offline-docker-package
    # 给脚本执行权限
    chmod +x install.sh
    # 执行安装,使用sudo
    sudo ./install.sh
    
  3. 配置用户组 (安装脚本最后已提示):
    sudo usermod -aG docker $USER
    
    然后必须注销当前用户,重新登录 ,才能使组权限生效。
  4. 验证与使用
    # 验证安装
    docker version
    docker-compose version
    # 查看已加载的镜像
    docker images
    # 进入你的应用目录,使用docker-compose启动服务
    cd /path/to/your-app
    docker-compose up -d
    

4.2 常见问题与排查技巧实录

即使准备充分,离线环境部署仍可能遇到各种问题。下面是我踩过坑后总结的排查清单:

问题现象 可能原因 排查步骤与解决方案
执行 docker ps 报错: Cannot connect to the Docker daemon 1. Docker服务未启动。
2. 当前用户不在 docker 组。
3. 环境变量 DOCKER_HOST 设置错误。
1. sudo systemctl status docker 检查服务状态。未启动则 sudo systemctl start docker
2. groups $USER 查看是否在 docker 组。不在则用 usermod 添加并 重新登录
3. echo $DOCKER_HOST 检查,离线环境通常不应设置此变量。
docker load 镜像时报错: open /var/lib/docker/tmp/...: no space left on device 磁盘空间不足,尤其是 /var/lib/docker 所在分区。 1. df -h 查看磁盘使用情况。
2. Docker默认存储目录是 /var/lib/docker 。如果空间不足,可以清理无用镜像或修改Docker数据目录(需在服务启动前配置 /etc/docker/daemon.json 中的 data-root )。
加载镜像后, docker run 时报错: exec format error 架构不匹配! 这是最典型的多架构问题。在ARM机器上加载了X86的镜像,或者反之。 1. docker image inspect <image_name> | grep Architecture 确认镜像架构。
2. uname -m 确认机器架构。
3. 解决方案 :确保加载了正确架构的镜像包。在打包阶段务必严格区分 x86_64-images.tar aarch64-images.tar
docker-compose up 报错: image for service XXX not found 虽然镜像已加载,但 docker-compose.yml 中使用的镜像标签与本地加载的镜像标签对不上。 1. docker images 查看本地已有镜像的 REPOSITORY:TAG
2. 对比 docker-compose.yml 文件中 image: 字段指定的名称。
3. 解决方案 :修改 docker-compose.yml 中的镜像名,使其与本地镜像名一致。或者在保存/加载镜像时,使用与 docker-compose.yml 中一致的标签。
系统重启后Docker服务无法启动 1. 服务未设置开机自启。
2. 内核模块或系统依赖问题(如iptables、cgroup)。
3. 二进制文件损坏或路径错误。
1. sudo systemctl enable docker 确保启用。
2. 检查内核版本是否支持Docker(需3.10以上)。对于较旧系统,可能需要升级内核或使用旧版Docker。
3. 使用 which docker which dockerd 检查命令路径,确保二进制文件已正确安装到 /usr/bin/
在ARM机器上,docker-compose命令执行慢或报错 可能误用了X86版本的 docker-compose 二进制文件。 1. file /usr/local/bin/docker-compose 查看文件信息,确认是ARM可执行文件。
2. 确保安装脚本正确拷贝了 binaries/aarch64/docker-compose 文件。

独家避坑技巧 :在制作离线包时,可以在打包机上用 qemu-user-static 这个“神器”提前验证ARM镜像。安装它( sudo apt-get install qemu-user-static )并注册到Docker( docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ),之后你就可以在X86机器上直接 docker run --platform=linux/arm64 your-arm-image 来模拟运行ARM容器,提前发现应用在ARM架构下的兼容性问题,比如某些依赖库是否存在。

5. 进阶:构建真正跨架构的Compose项目

上面的方法解决了基础部署,但每次都要手动区分两个镜像包,还是有些麻烦。对于更复杂的项目,我们可以利用Docker Buildx和镜像清单(Manifest)来创建 单镜像多架构支持

原理是:为同一个镜像标签(如 myapp:v1 )创建包含 linux/amd64 linux/arm64 两个架构镜像的“清单列表”。当用户 docker pull myapp:v1 时,Docker会自动根据当前机器架构拉取对应的镜像层。

在有网打包机上的操作:

# 前提:已安装并配置好docker buildx,并创建了multi-arch-builder

# 1. 为你的应用构建多架构镜像(假设你有Dockerfile)
docker buildx build --platform linux/amd64,linux/arm64 -t myregistry.local/myapp:v1 --push .

# 如果你只是整合现有公共镜像,可以创建多架构清单
# 2. 拉取各架构镜像
docker pull --platform=linux/amd64 nginx:alpine
docker pull --platform=linux/arm64 nginx:alpine

# 3. 为它们打上带架构后缀的标签
docker tag nginx:alpine myregistry.local/nginx:alpine-amd64
docker tag nginx:alpine myregistry.local/nginx:alpine-arm64

# 4. 推送到你的私有仓库(离线环境此步跳过,但概念要懂)
# docker push myregistry.local/nginx:alpine-amd64
# docker push myregistry.local/nginx:alpine-arm64

# 5. 创建并推送合并的清单(Manifest)
docker manifest create myregistry.local/nginx:alpine \
    myregistry.local/nginx:alpine-amd64 \
    myregistry.local/nginx:alpine-arm64
docker manifest push myregistry.local/nginx:alpine

在离线环境下,我们无法使用这种动态清单。但我们可以借鉴其思想: 准备一个统一的镜像包,里面包含所有架构的镜像层 。虽然 docker save 不支持直接保存多架构清单,但我们可以将两个架构的镜像都打上相同的标签但不同的后缀,一起保存到一个tar文件。在加载后,再通过一个判断架构的脚本,自动给对应架构的镜像打上应用所需的统一标签。这需要更复杂的安装后处理脚本,但对于大型、固定的多架构部署环境,能极大简化管理。

最后,别忘了给你的离线包写一个清晰的 README.md ,说明包含的组件版本、支持的架构、安装步骤、以及已知问题和注意事项。这个包就是你应对无网环境的“瑞士军刀”,准备得越充分,现场部署就越从容。

更多推荐