Docker升级全攻略:Ubuntu 20.04无缝升级与故障排查

发布时间:2026/9/17 11:39:20
Docker升级全攻略:Ubuntu 20.04无缝升级与故障排查 前两天帮朋友处理一台跑了好几年项目的 Ubuntu 20.04 服务器docker --version一看还是 19.03机器上 MySQL、Redis、GitLab 一堆容器跑着还有两个和 SLAM 仿真相关的环境容器属于典型的“不敢动、又不得不动”。好在我把整体流程走了一遍升级完容器一个没丢、数据卷完好、服务恢复得也很快这才有底气把整个过程整理成这篇实战指南。在 Ubuntu 20.04 下做 Docker 无缝升级最怕的不是版本差异而是升级前没想清楚“无缝”的含义、升级时踩中仓库源失效、daemon 配置不兼容、containerd 版本不匹配这些暗坑。这篇内容主要围绕三大块来写升级前的准备和风险排查、升级中的命令与配置处理、升级后的完整验证方法同时把我实际操作中遇到过的典型问题整理成一个速查表方便你直接照着排查。如果你是第一次在生产环境升级 Docker或者机器上已经积累了大量容器和数据这篇文章值得通篇看完再动手。1. 升级前先想清楚为什么升、怎么才算“无缝”1.1 升级 Docker 到底解决了什么问题很多朋友会觉得 Docker 用得好好的没必要频繁升级。这句话在个人开发机上有一定道理但在 20.04 这种长期维护的系统上长期不升级 Docker 的风险是实实在在的。首先是安全问题。Docker 由 dockerd、containerd、runc 等多个组件组成其中 runc 是底层容器运行时一旦出现容器逃逸漏洞攻击者可能直接从容器内部突破到宿主机。这类漏洞几乎都是通过升级 Docker 发行版来修复的你如果一直锁在旧版本相当于把这些已知漏洞长期暴露在外面。其次是兼容性问题。现在的镜像构建工具、编排平台、监控采集组件都在往新版本推进老版本 Docker 在 BuildKit、Compose V2、新的 API 接口上支持得不够好。比如新版 docker compose 插件需要 Docker Engine 20.10 以上的 API 版本如果你还在 19.03 上很多新特性用不了。再比如一些云平台、CI/CD 流水线默认调用的 API 字段在老版本里不存在会导致自动化流程直接中断。还有 Ubuntu 20.04 这个系统本身。它发布早默认软件源里的 Docker 版本往往偏旧很多教程让大家通过apt install docker.io装的其实是发行版自带的阉割版和 Docker 官方源里的 docker-ce 不是一个版本体系。升级到官方维护的稳定版修复 bug 的速度快很多更新也跟得上节奏。1.2 “无缝”指的是什么很多人一开始就理解偏了我在实际操作里发现很多人听到“无缝升级”就以为整个升级过程中所有容器一秒都不停。这个理解太理想化了。Docker 升级本质上做的事情是替换 dockerd、containerd、runc 这些二进制文件然后重启 docker 守护进程。只要 daemon 重启默认情况下容器就会跟着停掉再自动拉起中间必然有一次中断。要做到真正的无缝关键在/etc/docker/daemon.json里有个配置叫live-restore。如果你在升级前已经把它设置为 true那么即使 docker daemon 重启容器进程本身不会被 kill只是守护进程短暂跳一下网络和存储层面也基本不受影响。这个机制我在生产环境里实际用过简直是为升级场景量身定做的。反过来说如果你的 daemon.json 没开 live-restore又要求业务完全无感那就要提前评估能否接受一次秒级或分钟级的抖动。对数据库这类对连接中断敏感的容器我一般建议不仅打开 live-restore还要在非业务高峰窗口操作并且提前做好数据库逻辑备份双保险。1.3 先做一次风险摸底列好检查清单再动手升级前我会先把可能翻车的点列一遍确认每一项都心里有数。下面这张表在多次升级实践中帮我避了不少坑分享出来给你参考风险点可能造成的影响应对方式/var/lib/docker 所在分区空间不足升级后新镜像拉不下来、日志写满、容器启动异常用 df -h 确认剩余空间至少留出 20% 余量daemon.json 存在废弃配置dockerd 启动失败服务直接不可用升级前备份配置文件并逐项审查containerd.io 被 apt 锁定dockerd 连不上 containerd启动报错检查 apt-mark showhold必要时解除锁定自定义网桥和 iptables 规则冲突端口映射失效、宿主机能访问但外部进不来升级后逐项测试端口映射和容器互通registry-mirrors 地址失效拉取新镜像超时失败提前拉一个测试镜像验证地址可用性这张表看着简单但它背后对应的是升级失败最常见的原因。我见过太多人跑到一半卡住最后发现都是这些基础项没检查。升级这件事功夫全在动手之前。2. 升级前准备备份、体检、确认源2.1 给容器、镜像、数据卷做一次“快照登记”只要机器上还有一点跑着的业务我都不建议直接执行升级命令。跳过的第一步永远是把现状记录下来。# 记录当前容器、镜像、数据卷信息 docker ps -a --format table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}} container_list_$(date %F).txt docker image ls --format table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}} image_list_$(date %F).txt docker volume ls volume_list_$(date %F).txt这三条命令生成的文本文件虽然不起眼关键时刻能救命。比如升级后发现某个容器没有自动拉起你能根据文件知道它原来叫什么名字、用的哪个镜像、端口映射规则是什么几分钟就能手工重建而不是对着空空的 docker ps 发呆。数据卷备份这块我的原则是空间足够就全量打包空间不够就至少做关键应用的逻辑备份。sudo tar -czf docker_volumes_backup_$(date %Y%m%d).tar.gz /var/lib/docker/volumes如果是数据库容器我更推荐额外做一次逻辑备份。MySQL 用 mysqldumpRedis 用 BGSAVE 后拷贝持久化文件这样即使数据卷在极端情况下损坏也能从逻辑备份恢复数据不至于全部推到重来。2.2 检查 Docker 运行状态和系统基线登记完成后要花几分钟确认宿主机当前状态尤其是下面这几项docker --version docker info | grep -E Server Version|Storage Driver|Cgroup Driver|Docker Root Dir sudo systemctl status docker --no-pager sudo df -h sudo apt-mark showhold这里我特别关注两个输出项存储驱动和 Cgroup 驱动。存储驱动必须是 overlay2。Ubuntu 20.04 的默认内核基本都支持 overlay2大多数用户也是这个驱动。但如果你是从很老的机器升级上来的docker info里显示 vfs 或者 devicemapper那就要警惕了。devicemapper 在新版本 Docker 里已经完全不支持daemon 直接起不来这个问题我在 3.2 节会详细讲。Cgroup 驱动建议是 systemd。如果你看到 cgroupfs也不要急着改先确认当前容器是否正常运行。升级过程中改 cgroup 驱动属于额外动作如果没有 K8s 之类的强依赖可以暂时不动避免引入新的变量。2.3 确认 Docker APT 源能正常更新别在入口就被卡住Ubuntu 上 Docker 升级依赖 APT 源这一步如果没配对后面全是白搭。最常见的现象是执行sudo apt update的时候报 404 或者签名错误原因是源文件里的仓库地址已经失效或者 GPG key 不对。我现在的习惯是升级前先跑sudo apt update看输出里有没有 docker 相关的报错。如果发现源有问题不要反复重试老地址直接重新配置一条可靠的源。当前我在 Ubuntu 20.04 上最常用的配置方式如下# 以阿里云 Docker CE 镜像源为例 curl -fsSL https://mirrors.aliyun.com/docker-ce/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://mirrors.aliyun.com/docker-ce/linux/ubuntu focal stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update配置好之后务必用apt-cache policy docker-ce确认能拉到官方同源的 docker-ce 包。看到输出里有 20.10、24.x、26.x 这类版本号说明源没问题可以放心进入下一步。3. 核心升级实操从 apt 命令到 daemon 启动3.1 执行升级的命令和参数选择准备工作和源都确认好后执行升级本身反而很简单。我一直用的命令是sudo apt install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里有两个细节需要说明。第一我特意没有用apt upgrade做全局升级而是用--only-upgrade限定只升级 Docker 相关组件。生产环境里系统里可能还有其他软件依赖特定版本的内核库、运行库全局升级容易把不相关的东西一起动排查问题时很难定位。第二升级 docker-ce 的同时必须把 docker-ce-cli、containerd.io、docker-buildx-plugin、docker-compose-plugin 一起带上。很多人只装 docker-ce结果版本升级后 client 还是老版本或者 compose 插件缺失用起来各种别扭。如果你想升级到指定版本比如选择 26.x 稳定版先查可用的版本列表apt-cache madison docker-ce输出类似这样docker-ce | 5:26.1.4-1~ubuntu.20.04~focal | https://mirrors.aliyun.com/docker-ce/linux/ubuntu focal/stable amd64 Packages然后安装时加上版本号sudo apt install docker-ce5:26.1.4-1~ubuntu.20.04~focal docker-ce-cli5:26.1.4-1~ubuntu.20.04~focal containerd.io1.7.18-1生产环境我一般不建议追最新版本选一个发布超过 1 到 2 个月的稳定小版本比较稳妥让社区把新版本的坑先趟平。3.2 daemon.json 的兼容性整理这是最容易翻车的环节升级包安装完成后系统会自动尝试重启 docker daemon。如果重启失败最可能的原因就在/etc/docker/daemon.json里。升级前一定要先看这个文件cat /etc/docker/daemon.json我见过最典型的致命配置是storage-driver: devicemapper。这通常是早期用 Ubuntu 16.04 搭建 Docker 环境时留下的配置后来系统一路升级到 20.04daemon.json 一直没动过。新版本 Docker 不支持 devicemapperdockerd 会直接拒绝启动日志里会提示类似devicemapper was deprecated或unsupported storage driver。遇到这种情况需要把存储驱动改成 overlay2{ storage-driver: overlay2, exec-opts: [native.cgroupdriversystemd] }这里有个必须要提醒的坑修改存储驱动是一个大动作涉及底层存储层重建旧镜像可能无法直接读取。如果升级后 daemon 起不来你要先判断是不是这个原因。确认后改成 overlay2 再启动如果某些镜像丢失从 image_list 文件里找回来重新拉取数据卷部分不受影响容器数据还在。除了存储驱动还有三类配置升级时不能乱动live-restore必须保留 true这是无缝升级的关键>sudo systemctl daemon-reload sudo systemctl restart docker sudo journalctl -u docker -n 50 --no-pager日志里如果出现failed to start containerd或者context deadline exceeded优先检查 containerd 的版本。3.3 升级后一定要看的几个版本与系统信息升级完成不代表万事大吉我用下面的命令做快速判断docker version docker info | grep -E Server Version|Storage Driver|Cgroup Driver|Live Restore docker compose version docker buildx version这里有三个重点。第一确认 Server 版本已经升级到目标版本。很多人只盯着 Client 版本实际上真正干活的是 Server 端 daemon。第二确认 Storage Driver 是 overlay2、Live Restore 是 true。docker info输出里如果显示 Daemon 配置和预期不一致先回 daemon.json 里查。第三docker compose 插件要单独确认。新版本 Docker 使用的是docker compose命令中间有空格和老版本独立的docker-compose不是一回事。如果你之前安装过 docker-compose-plugin升级后会同步更新如果没有要单独安装sudo apt install docker-compose-plugin补充一点升级后你可能会注意到 compose 项目里的version: 3字段开始出现提示信息。这是 Compose V2 的正常反应老项目写法基本兼容但如果项目里用了特别老的扩展字段建议用docker compose config先解析一遍语法问题当场就能看出来。4. 升级后的验证清单容器、网络、业务三关4.1 容器状态检查与启动策略升级后第一次执行docker ps -a看到容器全是 Exited 状态很正常不要慌。重点在于确认每个容器的重启策略是否在升级后正常生效。docker inspect -f {{.Name}} {{.HostConfig.RestartPolicy.Name}} $(docker ps -aq) | sort如果容器配置了unless-stopped或alwaysdocker daemon 重启后理论上会自动拉起。但实际环境中容器可能因为端口占用、挂载目录权限变化等原因启动失败。我的习惯是按依赖顺序手动启动docker start mysql8 redis gitlab nginx然后逐个看日志确认容器内部进程真的起来了docker logs --tail 50 mysql8 docker logs --tail 50 gitlab这里特别提醒像 GitLab 这种体量大的容器即使状态变成 running内部服务也可能还没完全就绪。一定要看健康检查状态和实际业务响应不要看到容器在跑就宣布胜利。4.2 网络、存储和镜像源的实际验证容器能起来不等于网络一定通。docker daemon 升级时会重建网桥和部分 iptables 规则这个过程中最容易出现端口映射失效、容器间互通异常的问题。我的验证套路是# 1. 验证本机端口映射 ss -tlnp | grep 容器映射端口 # 2. 进入容器测试 DNS 解析 docker exec nginx ping -c 2 registry.example.com # 3. 自定义网络内容器互通 docker network inspect 网络名称如果你用的是自定义 bridge 网络升级后网络对象通常还在连接的容器也还在但子网和网关信息可能发生细微变化。应用代码里如果写死了容器 IP升级后很可能出现“连不上数据库”“访问不到服务”这种诡异故障。镜像源验证我建议直接拉一个测试镜像docker pull hello-world如果有超时或者速度特别慢优先怀疑 registry-mirrors 失效。现在的可用镜像地址变化很快跟上一次升级时用的不一定一致确认无法使用后换成可用地址重启 docker 再试。存储这块查看数据卷是否都完整挂载docker ps -q | xargs -I {} docker inspect {} --format {{.Name}} {{range .Mounts}}{{.Source}}-{{.Destination}} {{end}}确认挂载源路径还在目录内容没有丢失。4.3 业务服务验证别只看容器是 running最后一步是真实业务验证。命令行层面的健康检查忽略了很多业务细节所以我通常这样测Web 服务curl 首页和关键接口看状态码和响应耗时数据库执行一条最简单的查询或事务确认连接正常消息队列发一条测试消息并消费确认链路通定时任务确认下一次触发能正常执行。这里没有统一脚本核心思路是“最小验证 关键路径”。我在一次升级完以为一切正常结果客户反馈接口偶发超时排查半天发现是容器重建后 IP 变了而在另一个服务的配置文件里还在用旧 IP。从那以后只要涉及跨容器调用我验证时候一定会走一遍完整业务调用链绝不只盯“容器活着”。5. 升级实录最常踩的 6 个坑与排查经验5.1 常见问题速查表现象可能原因处理方式apt update 报 404 或仓库失效Docker 源地址或签名过期重新配置 docker.list换成可用镜像源升级后 docker daemon 启动失败daemon.json 里有废弃配置逐行检查配置storage-driver 改为 overlay2dockerd 报 failed to connect to containerdcontainerd.io 未一起升级或版本不匹配检查 apt-mark showhold解除锁定后重新安装 containerd.io容器全部 Exited 且不会自动拉起restart 策略缺失或 daemon 重启时序问题手动 start并检查 RestartPolicy 配置端口映射失效外部无法访问docker0 网桥重建、iptables 规则冲突用 iptables -t nat -L -n 检查 DOCKER 链调整 ufw 规则拉取镜像超时失败registry-mirrors 地址失效换成可用镜像地址重启 docker 后重试5.2 一个印象深刻的排查案例之前帮一台机器升级升级本身很顺利docker version 也正常但我发现 GitLab 的页面突然打不开。ss 看端口都在监听curl 本机 80 也正常但从外部访问就是不通。排查到最后发现是这台机器之前手动改过 ufw 规则Docker 升级时重建了 iptables 的 DOCKER 链NAT 规则和 ufw 规则叠加后出现了冲突。这个案例说明一个关键点升级后如果遇到“本机能通、外部不通”不要一头扎进容器日志里先站在宿主机网络层面排查。处理方式是调整 ufw 放行规则让 Docker 映射出来的端口在防火墙层放通或者把该容器改为 host 网络模式来规避 NAT 链的问题。具体选哪个要看业务需求我的习惯是优先调整 ufw 规则尽量不动容器网络模式。5.3 升级后的日常维护为下次省点事升级这件事不是一锤子买卖。我现在的固定习惯有三条第一每次升级前把容器清单和配置备份成一个固定脚本放在项目目录里下次直接执行不用临时想命令。#!/bin/bash # docker_pre_upgrade_backup.sh docker ps -a --format table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}} container_list_$(date %F).txt docker image ls --format table {{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}} image_list_$(date %F).txt docker volume ls volume_list_$(date %F).txt第二升级完成后把 docker version、docker info 的关键输出、验证结果记录到一个 Markdown 文件里作为每次升级的存档。下次出问题翻历史记录能很快定位是什么时候发生的变更。第三关注 Docker 在 Ubuntu 20.04 上的支持周期。Launched 版本有自己的生命周期Docker 官方也会对旧版系统保留一段时间的兼容支持。如果机器上还跑着 ROS、SLAM 仿真这类依赖显卡透传的环境升级 Docker 后务必用nvidia-smi和容器内 GPU 验证命令双重确认这类环境配置一次很花时间别让升级把好不容易配好的 GPU 能力弄丢。回到开头那句话升级 Docker 不可怕可怕的是不准备就动手。把风险清单过一遍把配置备份做一遍把验证流程走一遍整个升级就是一次可预测的常规操作。每次升级完我都会额外记录一句关键信息到运维笔记里比如“这次升级后 bridge 网络的子网段变化了”“这次 registry mirror 换成了某个新地址”这些小记录在后续排障时真的帮我节约过不少时间。希望这篇实战记录对你有用也希望你的升级过程比我的更顺利。