ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

离线环境部署Docker:自制flat apt源完整方案

离线环境部署Docker:自制flat apt源完整方案 说个上周刚干完的活儿。客户的机器全是 Ubuntu 22.04 LTS跑在完全隔离的内网里外网访问全断连apt update都出不去。业务那边要求在这批机器上部署 Docker 环境后面还有一堆容器镜像要跟着进去。这种场景我见得不少处理思路也很明确先在联网的开发机上把 Docker 的 deb 依赖包全部拉到本地整理成一个可复用的自定义 apt 源然后整体拷进内网。内网机器把这个源当作唯一的软件源来用直接apt install就能把 Docker 装好。整个过程不复杂真正坑人的是源结构、依赖、签名、传输这些细节。这篇就把完整部署方案写下来给做私有化交付、内网部署、离线环境维护的同学当参考。整个方案的核心步骤可以拆成三块联网环境准备自定义 apt 源、源目录传输到离线环境、离线环境配置并安装 Docker。只要第一块做扎实了后面的批量安装就是重复劳动非常省事。1. 方案整体设计为什么是“自定义apt源”而不是“拷贝deb文件”很多人接到离线安装 Docker 的需求第一反应是把.deb文件拷过去然后dpkg -i。这个思路在只有一两台机器、装完就不管的情况下完全没问题。但如果机器数量多、版本要统一、后续还要装其他软件包直接拷文件就开始踩坑了。1.1 离线部署场景的共性痛点离线环境最麻烦的不是“没有网”而是“缺的东西你事先不知道”。在国内企业内网、实验室隔离网络、私有云机房这些环境下你面对的问题高度相似机器无法访问外网apt update直接超时官方源和 Docker 官方源全部不可达。只拷贝 Docker 的几个核心 deb 文件dpkg -i时会报出各种依赖不满足。依赖包之间还有版本匹配关系比如docker-ce-cli和docker-ce版本必须对应手动逐层补齐很耗时。如果是十几台机器轮流装每台手动处理依赖效率低还容易出错。后期想补装docker-compose-plugin或docker-buildx-plugin又要重新走一遍“找包、拷包、装包”的流程无法复用。把这些痛点放在一起结论就很明显离线环境下与其拷贝零散文件不如直接做一个小型软件源。把索引、依赖、版本关系都交给 apt 工具链去处理人只负责维护好这个源目录。1.2 三种常见方案的横向对比我在不同项目里试过下面几种方案各有各的适用场景方案优点缺点适用场景直接dpkg -i安装 deb操作最简单拿到文件就能装依赖全靠手工补无法追溯版本容易混一两台机器一次性交付制作自定义 flat apt 源支持apt update和apt install依赖自动解析可长期复用需要生成索引和 Release 文件有一定构建成本多台机器、需要批量一致部署的场景自建 Aptly / 全量镜像源功能强大可做快照、多版本管理架构复杂学习成本和服务器资源消耗较高企业长期维护数百个软件包的中心仓库我这个项目选择的是第二种自制 flat apt 源。它不需要单独部署服务器一个目录就是一个源。你既可以用file://协议让本机直接使用也可以放到内网 HTTP 服务上供局域网内所有机器统一访问扩展性完全够用。1.3 整体流程与实施架构整个部署流程可以划分成三个阶段联网中转机准备在能上网的 Ubuntu 22.04 机器上添加 Docker 官方源下载 Docker 系列的 deb 包整理成标准源目录生成 Packages 索引和 Release 文件。内容传输落地将源目录通过 U盘、rsync 或内网文件服务器拷贝到离线环境并在离线机器上校验完整性。离线端配置安装注释掉离线机器原本不可达的官方源新增本地源地址执行apt update和apt install docker-ce最后导入容器镜像并验证 Docker 功能。之所以把“制作源”和“安装 Docker”分开是因为源目录是一次性构建、重复使用的产物。后续所有机器甚至其他软件安装都能复用同一个源。这也是它比单纯拷贝 deb 更占优势的根本原因。2. 联网环境准备制作可复用的自定义apt源这一阶段是整套方案的核心源做得好不好直接决定离线端安装是否顺利。我建议专门找一台 Ubuntu 22.04、amd64 架构的联网虚拟机或开发机来操作架构和系统版本越贴近目标生产环境依赖兼容性问题就越少。2.1 准备环境与目录规划构建自定义源需要用到两个关键工具dpkg-dev提供dpkg-scanpackagesapt-ftparchive则用来生成 Release 文件。前者需要手动装一下后者是 apt 包自带的一般不需要额外处理。sudo apt update sudo apt install -y dpkg-dev然后规划源目录。我做的是 flat repository也就是所有 deb 包和索引文件放在同一个目录下apt 源配置里直接指向这个目录。这种结构最简单适合只承载 Docker 及少量软件包的自定义源sudo mkdir -p /opt/apt-repo cd /opt/apt-repo目录最终大概长这样/opt/apt-repo/ ├── containerd.io_1.7.12-1_amd64.deb ├── docker-ce_26.1.3-1~ubuntu.22.04~jammy_amd64.deb ├── docker-ce-cli_26.1.3-1~ubuntu.22.04~jammy_amd64.deb ├── docker-buildx-plugin_0.14.0-1~ubuntu.22.04~jammy_amd64.deb ├── docker-compose-plugin_2.27.0-1~ubuntu.22.04~jammy_amd64.deb ├── Packages.gz └── Release如果你以后要同时支持 amd64 和 arm64可以考虑把目录升级成树形结构每个架构单独一个子目录。但小规模场景下flat 结构足够了别过早设计过度。2.2 下载Docker核心软件包先把 Docker 官方源加到联网机器上具体命令参考官方文档curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [archamd64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list sudo apt update然后有两种方式把包拉下来。第一种直接用apt-get download精确下载核心包cd /opt/apt-repo apt-get download docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这种方式最适合你已经确定要装最新稳定版的情况下载完就是当前源里的版本。第二种用install --download-only让 apt 自动解析依赖sudo apt-get install --download-only docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo cp /var/cache/apt/archives/*.deb /opt/apt-repo/这里的思路是让 apt 先把所有依赖分析一遍并下载到本地缓存然后统一拷入源目录。不过实际执行时你会发现缓存里可能带进来不少与 Docker 没有直接关系的系统库包比如libc6、libsystemd0这些。直接把全部缓存拷贝进源目录会带来一个隐患离线端执行apt install docker-ce时apt 可能会尝试升级这些系统基础包。在内网离线环境下升级系统基础包引发依赖冲突的风险不小。所以我更推荐方法一只下载 Docker 相关的那几个包。Ubuntu 22.04 自带的基础依赖如libseccomp2、iptables通常已经满足 Docker 运行要求。如果后面真碰到缺失的依赖再去联网机上用apt-get download 包名单独补齐一个一个加入源里这样最安全、最可控。如果你需要指定版本先用apt-cache madison docker-ce查看可用版本然后精确下载apt-cache madison docker-ce VERSION5:26.1.3-1~ubuntu.22.04~jammy apt-get download docker-ce$VERSION docker-ce-cli$VERSION containerd.io docker-buildx-plugin docker-compose-plugin注意 Docker deb 包的版本号里通常带1~ubuntu.22.04~jammy这样的后缀还有5:这样的 epoch 前置标签复制版本号时别漏。2.3 生成Packages索引与Release文件deb 文件放进目录后要生成 apt 能够识别的索引。核心命令是dpkg-scanpackagescd /opt/apt-repo sudo dpkg-scanpackages . /dev/null | gzip -9 Packages.gz这里把当前目录作为包根目录扫描生成Packages.gz。/dev/null是 override 文件我们不需要做优先级覆盖直接传空设备即可。生成索引后还没有结束如果没有 Release 文件apt 在update时会报错。Release 文件的作用是告诉 apt 这个源的基本信息和索引文件的完整性校验值。用apt-ftparchive release生成sudo apt-ftparchive release . Release如果提示缺少字段可以手动补上这些参数再生成保证 apt 能正常识别sudo apt-ftparchive release \ -o APT::FTPArchive::Release::Originlocal \ -o APT::FTPArchive::Release::Labellocal \ -o APT::FTPArchive::Release::Codenamejammy \ -o APT::FTPArchive::Release::Architecturesamd64 \ . Release还有一个重要细节我们制作的源不是官方签名源在离线端使用时必须显式声明[trustedyes]否则 apt 会因为找不到InRelease或Release.gpg而拒绝使用这个源。2.4 验证自定义源是否可用源做完了先在联网机器上验证一遍避免到离线环境才发现问题。在/etc/apt/sources.list.d/下新建一个本地源文件内容为echo deb [trustedyes] file:/opt/apt-repo ./ | sudo tee /etc/apt/sources.list.d/local-offline.list sudo apt update然后检查 apt 是否看到了 Docker 相关包apt-cache policy docker-ce containerd.io docker-compose-plugin如果输出里能看到包名和版本号说明自定义源构建成功。验证完记得把这个测试源文件删掉或注释掉免得影响后续下载真实 Docker 源sudo rm /etc/apt/sources.list.d/local-offline.list sudo apt update3. 离线环境落地传输、配置与安装Docker联网环境把源目录准备就绪后剩下的工作就是搬运和安装了。3.1 文件传输与完整性校验传输方式取决于离线网络条件。常见的有三种如果源目录在移动介质U 盘、移动硬盘上直接拷过去最原始但最可靠。如果离线环境内网是通的可以用scp或rsync从中转机推到离线机器上。如果内网有文件服务器或 HTTP 服务把源目录丢上去离线机器通过wget或curl拉取。从实践经验来看大批量传递时最推荐把源目录打包后再传输既方便移动介质拷贝也能压缩体积tar -czf apt-repo.tar.gz /opt/apt-repo传到离线机器后解压并做一次完整性校验。可以在构建源时就生成校验文件cd /opt/apt-repo sudo sh -c sha256sum *.deb Packages.gz Release SHA256SUMS这个SHA256SUMS文件随着源目录一起传输离线端执行cd /opt/apt-repo sha256sum -c SHA256SUMS看到所有文件都显示OK再继续执行部署。这一步别跳过尤其是跨公网传输或使用移动介质时文件损坏是最隐蔽也最麻烦的问题。3.2 配置Ubuntu使用自定义apt源先把源目录放到一个固定路径我统一放在/opt/apt-reposudo mkdir -p /opt/apt-repo # 如果源目录在临时挂载点 /mnt/usb/apt-repo sudo cp -r /mnt/usb/apt-repo/. /opt/apt-repo/接下来是离线环境的源配置。这一步有深坑因为 Ubuntu 22.04 部分安装形态默认使用的是传统/etc/apt/sources.list文件但部分版本或定制镜像会使用/etc/apt/sources.list.d/ubuntu.sources这种 deb822 格式。两种格式的注释方式不一样。我的做法是统一先把不可用的官方源禁用掉再添加本地源。针对传统格式sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak sudo sed -i s/^deb /# deb / /etc/apt/sources.list如果系统使用 deb822 格式的源文件重命名该文件即可禁用sudo mv /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak然后添加自定义 apt 源echo deb [trustedyes] file:/opt/apt-repo ./ | sudo tee /etc/apt/sources.list.d/local-offline.list sudo apt update看到提示从file:/opt/apt-repo ./读取包索引就说明配置成功。3.3 执行安装与依赖处理源准备好后安装 Docker 就非常简单了sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin这里containerd.io必须单独列出来。Docker 依赖 containerd 来管理容器运行时只装docker-ce会出现依赖缺失。docker-compose-plugin则是为了后续容器编排使用建议一起装上。如果你还需要构建容器的 Buildx 能力也可以把docker-buildx-plugin加进来。启动 Docker 并设置开机自启sudo systemctl enable --now docker sudo systemctl status docker如果安装过程中报依赖不满足先不要急着dpkg -i --force-depends这种暴力操作。正确做法是回到联网机器上用apt-get download 缺失包名拉取对应依赖包放入源目录重新生成Packages.gz和Release再次执行sudo dpkg-scanpackages . /dev/null | gzip -9 Packages.gz sudo apt-ftparchive release . Release sudo apt update sudo apt install -y -f绝大多数依赖问题都可以通过这种方式解决。需要注意的是加入缺失的依赖包时尽量选择目标系统本身没有安装的包避免引入系统库升级。3.4 在离线环境中导入Docker镜像Docker 装好之后还有一个绕不开的问题容器镜像怎么进去离线环境里docker pull当然不可用常规做法是在联网机器上执行docker save和docker load。联网机器上docker pull mysql:8.0 docker pull redis:7.2 docker pull nginx:1.26 docker save mysql:8.0 redis:7.2 nginx:1.26 -o app-images.tar把app-images.tar传给离线机器后执行docker load -i app-images.tar docker images如果单个镜像或整体包特别大可以在 save 时直接走 gzip 压缩节省传输时间docker save mysql:8.0 | gzip mysql-8.0.tar.gz离线端用管道解压加载gunzip -c mysql-8.0.tar.gz | docker load这里有一个经验在联网机上把后续可能要用的镜像一次性都 pull 下来打包做一个镜像清单不要等到了离线现场发现缺镜像再回头折腾来回成本很高。4. Docker环境配置、验证与常驻优化Docker 服务起来之后还需要做一些配置优化尤其是生产服务器不能保持默认配置直接扔进业务环境。4.1 daemon.json的关键配置Docker 的守护进程配置集中在/etc/docker/daemon.json。离线环境常用配置如下{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 }, storage-driver: overlay2, exec-opts: [native.cgroupdriversystemd], registry-mirrors: [https://registry.internal.example.com] }参数含义逐一说一下>sudo systemctl restart docker4.2 启动Docker并验证功能验证 Docker 是否真正可用不只是看服务状态还要实际跑一个容器。先用基础命令确认客户端和服务端版本一致docker version docker info注意docker version的输出里 Server 部分不能为空如果只显示 Client说明 Daemon 没有正常启动或者当前用户权限不够。然后用之前导入的镜像跑一个测试容器验证完整的容器生命周期包括创建、网络、存储和删除docker run --rm -it busybox:1.36 sh -c echo offline docker ok如果没有导入 busybox就随便用之前导入的应用镜像跑一个bash -c echo ok的临时容器测试。如果docker ps等命令报 socket 权限错误处理方式是把当前用户加入 docker 组并重新登录会话sudo usermod -aG docker $USER newgrp docker4.3 离线场景下的Docker使用建议离线环境的 Docker 使用策略和在线环境有一些区别这里分享几个我觉得很重要的建议第一镜像传输尽量用清单化方式。在一台联网管理机上维护一份镜像清单定期docker pull、docker save、压缩归档离线环境直接load。把镜像也当成软件资产来管理不要临时想起什么拉什么。第二如果内网设备多、镜像更新频繁建议搭一个私有 RegistryHarbor 或 Docker Registry 2.0。后续从离线机器上可以直接docker pull内网镜像仓库的镜像免去反复 save/load。自定义 apt 源用file://镜像仓库用内网 HTTP两者搭配就可以支撑一个完全隔离的容器化运行环境。第三源目录要持续维护。Docker 版本升级或者要新增其他软件包时回到联网机器上重新拉包、重建索引、重新同步。整个过程我已经固化成了脚本后续就是执行命令和拷贝文件效率高且不容易漏步骤。5. 常见问题速查与排查实录离线部署 Docker 的过程中我踩过不少坑有些坑非常具有迷惑性。下面按“现象→原因→解决办法”整理成速查表方便现场排错。错误现象可能原因解决办法apt update报NO_PUBKEY或签名相关错误自定义源未签名apt 默认要求签名验证源配置中加[trustedyes]例如deb [trustedyes] file:/opt/apt-repo ./apt update报does not have a Release fileflat 源缺少 Release 文件进入源目录执行sudo apt-ftparchive release . Release后重试apt update正常但apt install docker-ce报依赖不满足源目录缺少某些依赖包回到联网机器apt-get download 缺失包补入源目录并重建 Packages 索引dpkg-scanpackages: command not found联网机器没装dpkg-dev执行sudo apt install -y dpkg-devdocker命令可用但无法访问/var/run/docker.sock权限不够或 Docker 服务未启动执行docker version查看 Server 是否在线权限问题用usermod -aG docker $USERsystemctl status docker显示 failedcontainerd 未启动或 Docker 与 containerd 版本不匹配先systemctl start containerd再systemctl restart docker若仍失败用journalctl -u docker排错docker run时网络报错或 iptables 相关报错内核 netfilter 模块缺失或 Docker 无权限管理 iptables确认内核相关模块已加载检查是否装了iptables命令重启 docker 服务再次验证5.1 高频错误与处理办法上面表格里列出了最常见的问题这里挑几个具体展开一下排错过程。签名问题的表现往往是这样的执行apt update时提示The following signatures couldnt be verified because the public key is not available或者干脆在 update 末尾报NO_PUBKEY。这是拿到非官方源最容易撞上的错误。解决办法就是在源列表的deb后面加[trustedyes]参数。加了之后 apt 不再强制要求签名文件但前提是你确定这个源的内容是自己制作的可信无误。Release 文件缺失的错误起初很迷惑人。明明Packages.gz已经在目录里了apt 还是报does not have a Release file。这是因为 apt 认为没有 Release 文件就不清楚源的基本信息和索引完整性。执行cd /opt/apt-repo sudo apt-ftparchive release . Release然后重新apt update即可。依赖问题的处理要冷静。不要在离线端用--force-depends强行安装那样 Docker 即使启动起来运行到特定功能时可能因为底层库缺失而出现不可预知的行为。统一原则是缺什么回联网机器补什么包补完重建索引。源是可以无限扩展的这就是自定义 apt 源相比手动 dpkg 的又一个优势。5.2 两个容易被忽略的细节最后讲两个我踩过以后印象深刻的细节。第一个是 Ubuntu 22.04 源配置格式的坑。前面提到过22.04 支持传统 sources.list 和 deb822 格式的 ubuntu.sources。如果你只注释了/etc/apt/sources.list而系统实际用的是/etc/apt/sources.list.d/ubuntu.sources那么apt update仍然会去访问不可达的外网源每次都要等到超时才会继续现场体验非常糟糕。所以离线端做完配置后第一件事就是执行一次apt update并仔细观察输出里是否还有外网地址的连接尝试。第二个是源目录里混入多个 Docker 版本的问题。有一次我在源目录里同时放了 Docker 24 和 Docker 26 两个版本的 deb离线端执行apt install docker-ce时apt 默认会选择版本号最高的那个但依赖的containerd.io可能又指向另一个版本的匹配关系结果装出来的组合很混乱。建议在源目录中只保留一个主版本系列的包多版本管理的事情留给 Aptly 这类专业工具做。如果你确实需要锁版本可以在离线端用apt-cache policy docker-ce查看候选版本然后用apt install docker-ce完整版本号指定安装。这套流程前前后后我跑过很多次踩得最多的还是源目录结构这种小地方。第一次图省事跳过 Release 文件apt update 直接报错后来把 flat 源、Packages.gz、Release 这几件套固定成标准流程离线部署就再也没有卡在源配置这个环节上。现在再接到类似需求就是一台上网机下载打包一个源目录结构剩下全是重复的批量操作。如果你也在准备离线环境的 Docker 部署建议提前把目标机器的系统版本、架构、内核版本问清楚连自己构建的源目录也记得留一个校验文件现场能少折腾很多。
返回列表