ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu离线部署Docker:基于自定义apt源的完整实践

Ubuntu离线部署Docker:基于自定义apt源的完整实践 1. 离线部署的整体思路与方案选型1.1 为什么是自定义apt源而不是直接拷deb包先回答一个问题很多人第一次做离线安装下意识会把安装包直接拷到内网机器上然后用dpkg -i一个个装。这种方法不是不行但坑非常多尤其是装 Docker 这种依赖链比较长的软件。dpkg -i不会自动解析依赖你装 docker-ce 的时候如果系统里没有 containerd.io它直接就报依赖不满足然后中断。你手动去装 containerd.io可能又发现它依赖 libseccomp2 的版本不够于是你又得去找 libseccomp2 的新版。这样一层层手动查依赖、找包、按顺序安装在包少的时候还能忍一旦超过五个包纯手工就很容易乱。更麻烦的是如果你一次性用dpkg -i *.deb这种批量方式虽然它会临时做一次依赖检查但包之间的安装顺序不对照样失败你还要看输出日志去人工调整顺序。所以我更推荐做成自定义apt源。思路很简单把这些 deb 包放进一个固定目录用工具生成一个 apt 能识别的索引文件然后在/etc/apt/sources.list.d/下写一个指向本地目录的源。这样一来apt 会把本地目录当成一个“软件仓库”安装时自动处理依赖、自动选择版本跟你用线上官方源没有任何区别。优势很明显第一依赖问题交给 apt 解决不用人工排查依赖顺序。第二这个本地源只要你做好一次以后同一批机器、同一个系统版本都能复用比每次手动 dpkg 省事得多。第三你还可以把其他常用软件包也放进去形成一个内网通用的基础软件仓库Docker 只是其中一项。1.2 整体流程与前置准备整个方案分成三个阶段我在做项目时习惯这样界定在一台有外网、系统版本为 Ubuntu 22.04 的机器上把 Docker 安装所需的全部 deb 包下载下来。这台机器我习惯叫它“参考机”或“打包机”。把下载好的 deb 包传到离线环境的一台 Ubuntu 22.04 机器上生成 apt 源索引配置本地源。在离线机器上用 apt 安装 Docker 全部组件启动服务并验证。这里面有几个前提条件需要注意都是我自己踩过坑后总结出来的系统版本必须一致。你离线目标机是 Ubuntu 22.04参考机也必须是 22.04小版本最好也别差太多。因为不同大版本的依赖版本差异很大比如 Ubuntu 20.04 的 libseccomp2 可能不满足 Docker 新版本的要求你在 20.04 上下载的依赖包拿到 22.04 上可能完全不适用或者反过来。用同版本系统做参考机能最大程度保证依赖兼容性。架构必须一致。x86_64 的包不能在 arm64 机器上安装。下载前务必用uname -m或dpkg --print-architecture确认架构然后在下载时选择对应架构的包。离线目标机需要能登录 root 或具备 sudo 权限。制作源和安装过程都需要写系统目录没有权限会卡在第一步。另外我建议在动手前先把有网参考机上原有的 docker 相关包清干净避免旧版本干扰下载过程。执行一下apt-get remove docker docker-engine docker.io containerd runc之类的清理命令确保环境干净再开始。2. 在有网环境准备离线软件包2.1 准备参考机并配置Docker官方源在有网的 Ubuntu 22.04 机器上先更新系统索引然后安装必要的基础工具sudo apt update sudo apt install -y apt-transport-https ca-certificates curl software-properties-commonDocker 官方源在国内直连有时候不太稳定这个每个人网络环境不一样我这里不讨论任何代理之类的操作就说最通用、适合离线打包的做法。你只需要在参考机上成功执行一次 apt update能从 Docker 官方仓库读到软件包列表就行。添加 Docker 官方 GPG 密钥和 apt 源这一步是标准的常规操作curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) 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 /dev/null注意上面的写法用了$(lsb_release -cs)动态获取系统版本代号Ubuntu 22.04 会输出 jammy这保证源地址指向正确的发行版目录。然后更新索引sudo apt update如果这一步能看到类似Reading package lists... Done的提示没有报错说明 Docker 官方源已经可用了。可以用一个简单命令确认 Docker 相关包是否可见apt-cache policy docker-ce输出里只要出现 Candidate 版本号就说明源配置成功。2.2 下载Docker安装所需的所有deb包这一步是整个离线打包的关键比较容易出问题的地方在于依赖包遗漏。Docker 在 Ubuntu 22.04 上的核心组件有这几个docker-ceDocker 服务端、docker-ce-cli命令行工具、containerd.io容器运行时、docker-buildx-plugin构建扩展、docker-compose-pluginCompose 插件。我见过不少人在离线环境只拷贝了 docker-ce 和 docker-ce-cli 两个包结果安装时提示 containerd.io 缺失又得重新找包。实际上 containerd.io 是 docker-ce 的硬依赖没有它 Docker 根本起不来。所以这里我强烈建议把五个组件全部下载用不用得到先下了再说离线环境下多带一个包比少带一个包稳妥得多。推荐用apt-get install --download-only的方式它会把软件包连同依赖一起下载到本地缓存目录sudo rm -rf /var/cache/apt/archives/*.deb sudo apt-get install --download-only docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin为什么先清空/var/cache/apt/archives因为 apt 下载的包都会缓存在这里系统之前可能残留一些旧的 deb 文件不清空的话后面拷贝时会把无关文件一起带进去而且这些旧包可能与本次下载的版本有冲突。--download-only的意思是只下载不安装非常适合做离线打包。命令执行完检查一下缓存目录ls -lh /var/cache/apt/archives/*.deb正常情况下你会看到至少五个 docker 相关的包以及少数几个依赖包。比如系统自带的 libseccomp2 如果版本不够新这里会出现一个更高版本的 libseccomp2 包如果系统里没有 iptables 的相关组件也可能会一起拉下来。这时候建议再用apt-cache depends手动作一次依赖复核apt-cache depends docker-ce重点看 Depends 一栏里列出的包确认每个依赖包都能在系统当前状态或缓存目录中找到。如果发现某个依赖既不在系统里又不在缓存目录里说明刚才的下载漏了多半是命令执行时有报错。最后把这些 deb 文件集中拷贝到一个干净的目录里。我习惯在/root/docker-offline-packages下操作mkdir -p /root/docker-offline-packages cp /var/cache/apt/archives/*.deb /root/docker-offline-packages/ ls -lh /root/docker-offline-packages/到这里参考机上的工作基本结束你手上已经有了一份完整的 Docker 离线安装包。2.3 把软件包完整拷贝到内网机器这步本身不难但有一个细节值得提醒拷贝时保留一个包清单文件方便内网核对。在拷贝前先生成一个清单cd /root/docker-offline-packages dpkg-deb --field *.deb Package, Version, Architecture package-list.txt或者用更直观的方式把每个 deb 包的详细信息列出来for f in *.deb; do echo $f ; dpkg-deb -I $f | grep -E Package|Version|Architecture|Depends; done | tee package-list.txt这个清单传到内网后非常有用。一方面你可以对照清单确认文件齐全另一方面如果安装时提示某个依赖缺失你能立刻查到这个依赖应该由哪个 deb 包提供。传输方式就看你的环境条件了。我实践中用过的有 scp、U盘拷贝、内网共享目录、甚至光盘刻录核心原则只有一个确保文件完整。deb 包如果拷贝中断导致文件损坏dpkg-scanpackages 生成索引时可能报错或者安装时出现 checksum mismatch 之类的提示。所以拷完后建议在目标机上先做一次完整性校验cd /root/docker-offline-packages for f in *.deb; do dpkg-deb -I $f /dev/null 21 echo $f OK || echo $f CORRUPT; done这个命令会逐个尝试读取 deb 包的头部信息能正常读出来说明文件没坏。如果出现 CORRUPT 的提示老老实实重新拷贝对应文件。3. 在内网机器上制作自定义apt源3.1 创建源目录并校验软件包完整性到了离线机器上先把软件包放到一个固定的目录。这个目录路径后面会写进 apt 源配置里所以想清楚再定避免后续迁移路径导致源失效。我建议使用/opt/apt-local/packages这类一眼就能看出用途的路径sudo mkdir -p /opt/apt-local/packages sudo cp /path/to/your/debs/*.deb /opt/apt-local/packages/把 deb 包放好后先做一次安装前的系统检查。确认目标机器的 Ubuntu 版本和参考机一致lsb_release -a uname -m如果版本一致、架构一致才能继续走本地源方案。我遇到过有人拿 Ubuntu 20.04 上打好的包拿到 22.04 上装安装时依赖解析报了一堆错最后全部重来这个时间成本完全不值得。3.2 用dpkg-scanpackages生成包索引apt 之所以能从一个目录里发现软件包依赖的是索引文件 Packages。这个文件里记录了每个 deb 包的包名、版本、依赖关系、文件路径、校验和等信息相当于整个软件仓库的目录。正常工作流程里这个文件由仓库维护者生成我们离线场景下用dpkg-scanpackages工具就能做同样的事。dpkg-scanpackages 包含在dpkg-dev包里Ubuntu 22.04 默认不一定会装先检查一下which dpkg-scanpackages如果没有输出安装 dpkg-devsudo apt install -y dpkg-dev然后进入包目录生成索引cd /opt/apt-local/packages sudo dpkg-scanpackages . | tee Packages这个命令会扫描当前目录下所有 deb 文件解析它们的控制信息输出一个 Packages 文件。tee Packages的意思是把输出同时打印到屏幕和写入文件如果扫描过程中有报错你能立即看到。为了让 apt 读取索引时更快通常会把 Packages 压缩成 gz 格式sudo gzip -kf Packages这里-k是保留原始文件-f是强制覆盖。生成完后目录里应该有Packages和Packages.gz两个文件。这一步经常有人忽略一个小问题dpkg-scanpackages 生成索引时会把 deb 文件的相对路径记录到 Packages 文件里。如果目录里还有子目录索引里会带上子目录前缀而 apt 解析时是相对于源根目录来查找的。所以源目录的结构尽量保持简单所有 deb 包平铺在一个目录里不要嵌套子目录以免出现 “找不到文件” 的问题。3.3 配置sources.list并让apt识别本地源接下来把本地目录告诉 apt。创建一个独立的源配置文件我习惯命名为local.list放在/etc/apt/sources.list.d/下echo deb [trustedyes] file:///opt/apt-local/packages ./ | sudo tee /etc/apt/sources.list.d/local.list这里有几个关键点要解释清楚。file:///opt/apt-local/packages是本地文件系统协议告诉 apt 去本地路径找包。最后的./表示相对于上面路径的索引文件放在当前目录。注意这个写法有讲究sources.list 的格式是deb [选项] URI 发行版 组件对于 file 类型的源URI 和发行版/组件的组合方式是apt 会把 URI 和后面的路径拼接成一个完整路径。写成file:///opt/apt-local/packages ./它最终会去/opt/apt-local/packages/Packages找索引文件。[trustedyes]表示信任该源跳过 GPG 签名验证。离线环境下不存在伪造仓库的风险加上这个选项可以省去签名配置的麻烦。配置写完后先更新源索引sudo apt update如果输出里能看到类似Hit:1 file:/opt/apt-local/packages ./的行说明本地源已经被识别。用apt-cache policy验证 Docker 包是否可见apt-cache policy docker-ce正常输出会显示 Candidate 版本号和你在参考机上看到的版本一致。到这一步本地源就算完全可用了。这里有一个容易踩的坑系统默认的/etc/apt/sources.list里通常配置了 Ubuntu 官方在线源如果离线机器没有外网apt update 时会尝试连接在线源然后超时整个 apt 命令可能要等很久才结束有时候还会因为部分源读取失败导致整体报错。解决办法是如果这台机器确定长期离线建议把/etc/apt/sources.list里所有非本地源都注释掉或者干脆把/etc/apt/sources.list.d/下除了 local.list 之外的源文件全部移走。具体做法sudo mkdir -p /etc/apt/sources.list.d/backup sudo mv /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/backup/ 2/dev/null sudo mv /etc/apt/sources.list /etc/apt/sources.list.d/backup/ 2/dev/null注意上面的 mv 命令把 local.list 也会一起移走所以建议先创建 local.list再做这个备份操作最后再重新创建 local.list或者先用grep排除一下。更稳妥的做法是只注释掉在线源不删除文件sudo sed -i s/^deb /#deb /g /etc/apt/sources.list这样 apt update 时就不会去尝试在线连接了。3.4 关于GPG签名如何处理很多刚接触离线源的人会在签名问题上卡住。默认情况下apt 要求源必须经过 GPG 签名验证没有签名的源会报The repository ... is not signed的错误。我在前面的配置里用了[trustedyes]这是最简单粗暴的解法等于告诉 apt 这个源不需要验证签名。对于完全隔离的内网环境这个做法是安全的因为没有外部攻击者能够篡改你的本地源文件。如果你的公司安全规范比较严格不允许使用 trustedyes那就得自己做一套签名。操作流程分几步在参考机上用 gpg 生成一个密钥对。把生成的公钥导出。用私钥对 Packages 文件签名生成 Release 文件和 InRelease 文件。把公钥放到离线机器的/etc/apt/trusted.gpg.d/目录下。这个流程本身不复杂但涉及 gpg 的细节不少而且一旦配置错误排查起来比较费时间。实际项目中如果安全合规没有特别要求用 trustedyes 是最省事的方案。我这里先不展开签名的完整操作后面如果大家需要我再单独出一篇。4. 通过本地apt源安装并验证Docker环境4.1 安装docker-ce及其组件本地源配置好之后安装就非常简单了跟在线环境几乎一模一样sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginapt 会自动从本地源里选择合适的包然后自动解决依赖顺序。这比手动 dpkg -i 不知道省了多少事。安装完成后先检查一下各组件版本dpkg -l | grep -E docker-ce|containerd|docker-compose|docker-buildx确认版本信息正确。然后启动服务sudo systemctl enable --now docker sudo systemctl status docker如果状态显示active (running)说明 Docker 服务已经正常启动。4.2 启动服务与运行验证服务起来了不等于 Docker 完全可用建议做一次完整的验证流程。先看客户端和服务端版本信息docker version输出里会有 Client 和 Server 两段Server 段能正常显示说明 daemon 已经在运行。如果 Server 段报错或显示Cannot connect to the Docker daemon先看服务状态systemctl status docker journalctl -u docker -n 50日志是最直接的排查入口大部分启动失败原因都能在这里看到。版本验证没问题后跑一个最简单的验证容器。但离线环境通常没有外网拉取 hello-world 镜像可能会失败。这个要分情况处理如果离线机器上有内网镜像仓库比如 Harbor、Nexus 之类你可以直接从内网仓库拉取镜像来验证docker pull 内网镜像仓库地址/library/hello-world docker run --rm 内网镜像仓库地址/library/hello-world如果内网实在没有任何镜像仓库可用那就退而求其次用系统自带的镜像验证 Docker 的基本功能。比如从本地导入一个预置的镜像或者用docker run跑一个基于系统现有镜像的容器。最保守的验证方式是检查 Docker 是否能正常创建网络、是否能管理容器生命周期docker network create test-net docker network ls docker network rm test-net另外容器运行时有一个隐藏问题值得留意Ubuntu 22.04 默认启用了 cgroup v2而较老版本的 Docker 在 cgroup v2 环境下需要额外配置。你如果安装的是 Docker 20.10.x 或更高版本一般没问题如果因为某些原因装了旧版运行容器时可能会出现cgroup ... is not mounted的报错。这里我建议安装时就选最新的稳定版本能省掉一堆兼容性问题。4.3 内网环境下镜像拉取问题的补充建议Docker 环境装好后你很快就会面对一个现实问题容器镜像从哪来。内网离线环境下镜像的获取方式通常有三种:第一种在有网环境用docker pull把镜像拉下来再用docker save导出成 tar 文件传到内网后用docker load导入。这种方法适合一次性、少量镜像的场景。第二种在内网搭建一个镜像仓库服务把需要的基础镜像预先推上去内网机器从这里拉取。第三种如果你用的是 Docker Desktop 或者完整版 Docker 引擎部分版本支持配置 registry mirror指向内网的镜像缓存服务。不管用哪种方式我都建议在部署 Docker 之前就规划好镜像来源。不然环境装好了应用跑不起来等于只完成了一半工作。我在实际项目中通常会做两件事第一梳理业务需要哪些基础镜像比如 nginx、redis、mysql、业务应用镜像等在有网环境全部 pull 下来并用 docker save 导出第二如果内网机器数量多一次性导入镜像用 docker load 逐个执行太繁琐可以写一个简单的循环脚本批量导入。for f in /path/to/images/*.tar; do echo Loading $f ... docker load -i $f done5. 常见问题与排查技巧实录5.1 高频报错的排查速查表离线部署 Docker 过程中有一些报错出现频率特别高我整理了一个速查表方便你对照排查现象可能原因排查与解决方法apt update 报错No Release file本地源路径配置错误检查 local.list 中路径是否正确确认 Packages 文件是否生成在目标目录apt update 长时间卡住不动系统仍在尝试访问在线源注释掉/etc/apt/sources.list和/etc/apt/sources.list.d/下的在线源配置apt install 提示无法定位软件包apt 索引里没有这个包先apt update再用apt-cache policy docker-ce确认包是否可见安装时报依赖不满足deb 包不齐全对比apt-cache depends docker-ce检查依赖回参考机补充下载dpkg-scanpackages: command not found缺少 dpkg-dev 工具执行apt install -y dpkg-devPackages 文件生成后 apt 仍找不到包索引文件路径不匹配确认deb file:///opt/apt-local/packages ./中的路径能拼出/opt/apt-local/packages/Packagesdocker: Cannot connect to the Docker daemon服务未启动或启动失败systemctl status docker查看状态journalctl -u docker -n 50看日志运行容器报 cgroup 相关错误cgroup v2 与旧版 Docker 不兼容升级 Docker 到 20.10 版本容器运行时提示 network 创建失败系统 firewalld 或 iptables 规则冲突检查 iptables 规则必要时重启 docker 服务5.2 踩坑记录与个人经验这块我多写点真实经历都是我反复做离线部署积攒下来的细节一般文档里不会写。第一个坑源目录结构变更导致源失效。我第一次做本地源时直接把 deb 包放在了/root/docker-pkgs下测试没问题。后来为了统一管理把这个目录挪到了/opt/apt-local结果 apt update 直接报错找不到索引。原因是源配置里的路径是绝对路径目录一挪配置没跟着改。以后遇到类似情况记得改完目录后同步修改/etc/apt/sources.list.d/local.list里的路径。第二个坑重复的架构混在一起。有一次我在打包机上下了 amd64 的包但拷贝时不小心把 arm64 的包也放进去了dpkg-scanpackages 没报错但 apt install 时提示架构不匹配排查了半天才找到原因。建议在拷贝到内网前用脚本统一校验一遍架构for f in *.deb; do arch$(dpkg-deb -f $f Architecture) if [ $arch ! amd64 ]; then echo WARNING: $f arch is $arch fi done第三个坑系统自带 Docker 包版本干扰。Ubuntu 22.04 的官方源里也带一个 docker.io 包如果你在配置本地源之前系统已经装过 docker.io之后再通过本地源安装 docker-ce可能会出现两个 Docker 相关的包打架的情况。遇到这种问题先彻底清掉旧包sudo apt remove -y docker.io docker-compose containerd runc sudo apt autoremove -y第四个坑apt update 之后源被覆盖。Ubuntu 的 apt 在部分场景下会自动清理/etc/apt/sources.list.d/下的文件虽然这种情况不常见但我确实遇到过系统升级时把源配置给改了。稳妥的做法是本地源配置完成后把两台机器的部署步骤记录下来万一源失效可以通过记录快速重建不用重新摸索。第五个细节也是我自己后来的习惯本地源里除了 Docker 相关的包我还喜欢把一些常用的运维工具一起放进去比如 vim、curl、tcpdump、net-tools、lrzsz 等。反正都是 deb 包放进去不占多少空间但在内网排查问题时这些工具随时能用上。你可以按需调整包列表把离线环境变成一个自带工具箱的安装源。第六个经验如果内网机器数量比较多比如几十台以上每台机器都拷贝一份完整 deb 包目录再创建本地源效率很低。更合理的做法是选一台机器做“源服务器”用 nginx 或 python3 -m http.server 把/opt/apt-local/packages目录通过 HTTP 共享出去其他机器直接配置deb [trustedyes] http://源服务器IP/apt-local/packages ./。这样所有机器共享一份软件包更新维护只需要操作源服务器一台机器Docker 版本升级时也只是替换参考机下载的新包其他机器 apt update 后就能看到新版本。至于源服务器的 HTTP 服务怎么做python3 自带了一个最轻量的方案cd /opt/apt-local/packages python3 -m http.server 8080然后其他机器把源配置写成echo deb [trustedyes] http://源服务器IP:8080/ ./ | sudo tee /etc/apt/sources.list.d/docker-local.list注意这里的 URI 是http://源服务器IP:8080/后面的组件路径是./最终访问路径会拼成http://源服务器IP:8080/Packages。如果源目录是挂在 HTTP 服务的子路径下调整对应 URI 即可。这个 HTTP 源方案我强烈推荐尤其适合批量交付的场景。我第一次用这个方案是给一个有一定规模的项目做环境初始化几十台机器同时部署靠 U盘一台台拷贝根本不现实。搭好源服务器后写一个自动化脚本批量执行 apt 安装整个 Docker 环境初始化时间从按天算缩短到按小时算。最后再提醒一句关于系统防火墙的事情。如果你用 HTTP 方式提供本地源源服务器要确保 8080 端口在防火墙策略里放行。Ubuntu 22.04 默认可能装有 ufw可以临时允许一下sudo ufw allow 8080/tcp如果内网安全策略比较严格你只对特定网段开放这个端口就行反正根据现场情况调整。
返回列表