Linux Docker安装与升级实战:存储驱动、数据备份与回滚

发布时间:2026/10/27 13:24:26
Linux Docker安装与升级实战:存储驱动、数据备份与回滚 1. 安装和升级是两条不同的路风险不在一个量级我遇到过一个挺典型的场面一台跑了两年多业务的服务器运维同事觉得 Docker 版本太老顺手执行了一条yum remove docker然后重新装新版本。命令跑完不到一分钟/var/lib/docker空了几十个镜像和容器的元数据全没。业务本身有编排和镜像仓库兜底最后是重建回来的但那两个小时的折腾完全是可以避免的。这件事之后我形成了一个习惯只要标题里出现“Linux 系统 Docker 版本升级或安装”我第一反应不是去翻安装命令而是先判断这次到底是“装”还是“升”。装和升在操作层面看着相似都是装包、改配置、起服务但风险模型完全不同。从零安装的机器是一张白纸装错了大不了卸载重来最坏的结果是浪费半小时。原地升级则是在一套已经在跑业务的环境上动刀数据目录、配置文件、容器网络、存储驱动、依赖包每一个环节都可能在你没注意的时候发生变化。所以这两件事的准备动作、执行顺序、验收标准都应该分开设计混在一起谈是很多事故的起点。这篇文章想做的事情很具体把 Linux 上 Docker 的安装和升级拆成两条清晰的链路讲清楚每一步为什么要这么做哪些地方是老手也容易翻车的以及出问题之后怎么把损失控制住。读者对象大概是这样手上有一批 Linux 服务器需要装 Docker 或者把老版本换掉但不想因为一次升级把线上环境搞崩。不需要你是内核专家但基本的命令行操作要熟。1.1 从零安装的容错空间大但也别随便装新机器装 Docker很多人脑子里只有一条命令apt install docker.io或者yum install docker。这两条命令确实能装出一个能跑的 Docker问题在于装出来的是哪个版本、用的是什么存储驱动、cgroup 走的是哪条路这些信息在命令执行完之后没人去看。系统自带源里的 Docker 版本普遍偏保守。以常见的发行版为例某些长期支持版本的系统源里躺着的还是 1.13 或者 18.09 这种年份很久的版本而官方仓库早就推到 24、26 甚至更高的主版本线了。版本差距带来的不只是功能缺失更麻烦的是配置方式和生态已经分叉老版本改参数要动 systemd 的 service 文件新版本统一收口到daemon.json老版本默认存储驱动可能是 devicemapper新版本是 overlay2老版本的 compose 是独立二进制新版本是插件形态。这些东西一旦装错后面所有围绕 Docker 的脚本、文档、运维习惯都要跟着迁就。所以我在新机器上装 Docker 的第一原则是先确定要哪个大版本再决定从哪个源装而不是反过来。生产环境我一般会锁在一个经过验证的次要版本上比如 24.0.x 这条线不追 latest。追 latest 的代价是你永远在适配新行为而收益往往是几个你暂时用不上的特性。1.2 原地升级真正的麻烦藏在数据目录里升级这件事包管理层面其实很简单卸掉旧的装新的重启服务。真正难的是/var/lib/docker这个目录。这个目录里存放着镜像层、容器可写层、卷数据、网络配置、插件状态。不同 Docker 版本对目录结构的组织方式是有差异的更关键的是不同存储驱动之间完全不兼容。devicemapper 时代的镜像层放在一个块设备池里overlay2 时代是普通目录树两者互相看不见。你从 devicemapper 升到 overlay2服务能起来docker images也能执行但输出是空的——不是镜像丢了是新驱动根本不认识旧驱动的数据。这就解释了一个很常见的现象升级完成后 Docker 服务显示 runningdocker version输出正常但业务容器一个都起不来报 “image not found”。很多人这时候以为是卸载时把数据删了其实是驱动换了。所以我在动升级之前一定会先确认三件事当前存储驱动是什么、目标版本默认用什么驱动、数据目录的物理位置在哪。这三件事决定了我这次升级是“平滑切换”还是“必须重建”。2. 动手前先摸清系统底子三个决定成败的参数升级不是从敲命令开始的是从读系统状态开始的。我一般会花十分钟把下面这几项查清楚这十分钟能省掉后面几小时。2.1 内核版本与 cgroup 版本最容易被忽略的前置条件Docker 对内核有硬性要求。较新的 Docker 版本一般需要 3.10 以上的内核推荐 4.x 起步。用uname -r看一眼就知道但真正容易出问题的不是内核主版本而是 cgroup 的版本。cgroup 是内核给进程做资源隔离的机制v1 和 v2 的组织方式差别很大。较新的发行版默认启用 cgroup v2而 Docker 对 cgroup v2 的完整支持是从 20.10 这条线开始的。如果你在一台 cgroup v2 的机器上装了一个 18.09 的 Docker可能出现容器起得来但资源限制不生效或者干脆起不来的情况。查 cgroup 版本有一条很直观的命令stat -fc %T /sys/fs/cgroup/输出cgroup2fs说明是 v2输出tmpfs说明是 v1。这个结果会直接影响你在daemon.json里怎么写 cgroup driver。现在主流的做法是统一走 systemd{ exec-opts: [native.cgroupdriversystemd] }为什么强调统一因为如果你这台机器同时还跑着容器编排组件编排侧默认用 systemdDocker 侧用 cgroupfs两边对 cgroup 的理解不一致节点就会反复报错。这个坑在单机 Docker 上不明显一旦上集群就暴露得很彻底。2.2 存储驱动决定升级后镜像还在不在的分水岭前面提过驱动不兼容的问题这里说怎么查、怎么判断。当前驱动docker info | grep -i storage driver如果你看到的是overlay2恭喜这是现在的主流选择升级路径相对干净。如果看到devicemapper那就要认真规划了因为这个驱动在新版本里已经被标记为不推荐甚至移除。如果看到vfs通常意味着系统文件系统不支持 overlay性能会很差也需要一并处理。目标版本默认用什么驱动较新的 Docker 在主流 Linux 发行版上基本都默认 overlay2但前提是宿主的文件系统支持。ext4 和 xfs 都支持xfs 需要带ftype1挂载这个细节在老系统上经常被忽略。检查方式xfs_info / | grep ftype如果输出ftype0那 overlay2 就没法用你得先考虑文件系统层面的调整而不是硬装。遇到驱动不兼容处理思路只有两条没有第三条场景处理方式代价镜像可以重新拉取升级前导出必要镜像升级后重新 load 或重新 pull中等取决于镜像体积和网络容器里有不可重建的本地状态升级前把状态落盘到卷或外部存储容器本身重建高需要业务配合我个人的偏好是永远让容器无状态本地状态一律外置到卷或者对象存储。这样升级时驱动换了也无所谓重建就行。但现实里总有历史包袱所以升级前那次“镜像清单化”的动作不能省。2.3 磁盘空间与 inode 余量升级过程中最容易被塞爆的地方升级过程本身会临时占用空间新版本的二进制、新的依赖包、旧版本留下的缓存。如果/var/lib/docker所在分区本来就吃紧升级到一半磁盘满了服务起不来回滚也做不了这是最难处理的死局。检查命令很基础但必须执行df -h /var/lib/docker df -i /var/lib/dockerdf -h看容量df -i看 inode。overlay2 在小文件多的场景下 inode 消耗很快很多镜像层里全是几百字节的配置文件容量还有一半inode 先满了。我的经验值是升级前至少留出数据目录当前体积 1.5 倍的空闲空间inode 使用率不要超过 70%。顺便看一眼数据目录多大du -sh /var/lib/docker这个数字在决定备份策略时很有用。如果只有几个 G全量打包备份是可行的如果是几百 G就得改成“备份配置 导出关键镜像”的轻量策略。3. 四条安装路线的取舍逻辑搞清楚系统底子之后才轮到选安装方式。市面上常见的一共四条路每条都有明确的适用场景混用会出问题。3.1 官方仓库源版本新、依赖干净但要处理网络可达性这是最推荐的方式尤其是有外网访问能力的机器。官方仓库的优势在于版本线完整、依赖关系由官方维护、升级路径清晰。Debian 系的操作大致是这样先清掉可能存在的旧包sudo apt remove docker docker-engine docker.io containerd runc然后准备密钥和源sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg写入源地址时注意用系统代号自动取值别写死echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu $(. /etc/os-release echo $VERSION_CODENAME) stable | \ sudo tee /etc/apt/sources.list.d/docker.list /dev/nullRedHat 系则是先装工具再挂源sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo装的时候建议把四个包一起装上别只装主包sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这四个包的关系要理清楚docker-ce是守护进程docker-ce-cli是命令行客户端containerd.io是底层容器运行时后两个是构建和编排插件。只装主包在某些版本上会把 containerd 的依赖留给系统源解决结果装出一个版本不匹配的组合服务起不来。网络可达性是这个方案唯一的门槛。如果直连官方源速度不理想可以配置镜像加速写进daemon.json的registry-mirrors字段就行注意填的是你自己可用的加速地址不要照抄别人的。3.2 系统自带源能装不代表该用apt install docker.io或者yum install docker这两条命令的好处是快不需要额外配源适合临时验证、教学演示、本地开发机。但它的版本往往落后好几个大版本。判断标准很简单如果你只是想在本地跑个容器试试系统源完全够用如果这台机器要承载业务、要接入编排、要长期维护就老老实实换官方源。版本落后带来的问题不是“功能少一点”而是“生态对不上”——文档里写的配置项你的版本不认别人给的 compose 文件你的版本跑不了排查问题时搜到的答案和你实际行为不一致这些隐性成本远高于配一次源的麻烦。3.3 离线二进制与离线包内网环境的唯一正解没有外网的环境只能走离线。两条子路线一条是静态二进制。下载对应架构的压缩包解压出docker、dockerd、containerd等可执行文件丢到/usr/local/bin然后手写 systemd 单元文件。这种方式的好处是完全不依赖包管理器任何发行版都能用坏处是所有升级、依赖、服务管理都要自己维护。另一条是离线包集合。在有网的同类机器上把 rpm 或 deb 连同依赖一起下载下来拷到目标机器上本地安装。RedHat 系可以用yumdownloader --resolveDebian 系可以用apt-get download配合依赖解析工具。这条路更贴近常规运维习惯我一般优先选它。离线安装最需要注意的是架构和系统版本必须完全对齐。x86 的包不能装到 ARM 上同一发行版的不同大版本之间 glibc 版本可能不同装上去会报符号找不到。3.4 四条路线对照路线版本新鲜度依赖管理适用场景主要风险官方仓库源高自动有外网的业务机源地址或密钥配置错误系统自带源低自动本地验证、临时环境版本过旧导致生态不匹配离线二进制取决于下载包手动任意发行版、特殊环境服务单元需自行维护离线包集合取决于下载包半自动内网批量部署依赖漏下载导致安装中断选哪条不是技术偏好问题是环境约束问题。先看网络再看是否要长期维护最后看批量规模。4. 升级前的固定动作配置、清单、旧包升级能不能一次成功八成取决于准备阶段。我这边有一套固定动作每次升级都走一遍不因为“这次看起来很简单”而跳过。4.1 把 /etc/docker 备份当成第一优先级配置文件体积很小但它是升级过程中最容易被覆盖的东西。新版包安装时可能会写入默认的 service 文件或者在你没注意的时候重置某些默认值。cp -a /etc/docker /etc/docker.bak.$(date %F-%H%M)这条命令几秒钟但能救命。备份完之后把当前的daemon.json内容打出来看一眼确认里面有哪些自定义项>docker ps -a --format table {{.Names}}\t{{.Image}}\t{{.Status}} /backup/containers.txt docker images --format table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}} /backup/images.txt docker volume ls /backup/volumes.txt docker network ls /backup/networks.txt镜像如果允许重新拉取清单化就够了。如果有镜像来自本地构建且没有推到仓库就必须导出docker save -o /backup/critical-images.tar image-a:1.0 image-b:2.3容器里有不可重建状态的用提交或者直接拷贝卷目录的方式留一份。我倾向于拷卷目录比docker commit干净因为 commit 会把容器的整个可写层打成镜像体积大且带一堆临时文件。4.3 旧版本包要提前留好回滚窗口至少留一周Docker 官方并不承诺跨大版本的数据目录向下兼容。也就是说新版写过的数据旧版未必读得懂。所以回滚不是“装回旧包”这么简单而是“装回旧包 恢复旧数据”两者要配套。实操上我会做两件事第一把当前安装的包版本记下来并确认仓库里还能拿到这个版本。rpm -qa | grep -E docker|containerd # 或 dpkg -l | grep -E docker|containerd第二确认可安装的历史版本列表把版本号抄进变更单。yum list docker-ce --showduplicates | sort -r # 或 apt-cache madison docker-ce如果仓库里的旧版本已经被清理那就得提前把包文件下载到本地留档。这一步在离线环境里尤其重要因为离线环境往往没有回头路可走。5. 升级执行链路从冻结业务到版本切换准备做完进入执行阶段。这一段的关键是顺序顺序错了会制造出本可以避免的停机。5.1 停业务与停守护进程的先后正确顺序是先停业务容器再停 Docker 守护进程。反过来做容器会被强制终止可能留下不一致的文件状态。# 记录当前运行的容器 docker ps --format {{.Names}} /tmp/running.txt # 逐个停止给业务留出优雅退出的时间 docker stop $(cat /tmp/running.txt) # 确认全部停止 docker ps -a # 再停守护进程 sudo systemctl stop docker sudo systemctl stop docker.socket sudo systemctl stop containerddocker.socket这一条容易被漏掉。某些发行版把 Docker 配成 socket 激活模式只停docker.servicesocket 还在监听稍有个客户端连接就会把服务重新拉起来你后面的卸载操作就会撞上“设备忙”。停完之后确认进程真的没了ps -ef | grep -E dockerd|containerd | grep -v grep有输出就继续等或者手动处理别急着往下走。这里提一句live-restore。这个特性可以让守护进程重启时容器不中断看起来很适合升级场景。但我的建议是小版本升级可以开大版本跨越时不要依赖它。大版本之间运行时接口可能有变化容器虽然活着但和新的守护进程之间的连接状态未必健康出问题时排查成本更高。老老实实停机升级可控性更好。5.2 卸载旧版本那条最危险的命令这是全文最需要划重点的地方。某些发行版的老版本 Docker 包在卸载脚本里带了清理数据目录的逻辑。你执行yum remove docker包管理器跑完卸载脚本/var/lib/docker就没了。这不是都市传说是真实发生过的尤其是在一些年份比较久远的系统源版本上。规避方式有三层建议全做第一层卸载前把数据目录改名而不是删除sudo mv /var/lib/docker /var/lib/docker.old.$(date %F)这样即使卸载脚本要删删的也是新路径旧数据安全。升级验证通过后再决定是保留还是清理。第二层卸载命令只用精确的包名不用模糊匹配# RedHat 系 sudo yum remove -y docker-ce docker-ce-cli containerd.io # Debian 系 sudo apt remove -y docker-ce docker-ce-cli containerd.io不要用yum remove docker*这种通配写法容易连带删掉你不想动的东西。第三层卸载前先在测试机上把同样的命令跑一遍观察/var/lib/docker是否还在。这个验证成本很低但很多团队省掉了。5.3 新版本安装与 daemon.json 的合并装新版本时我建议显式指定版本号而不是让它装最新# RedHat 系 sudo yum install -y docker-ce-24.0.7 docker-ce-cli-24.0.7 containerd.io docker-buildx-plugin docker-compose-plugin # Debian 系 sudo apt install -y docker-ce5:24.0.7-1~ubuntu.22.04~jammy \ docker-ce-cli5:24.0.7-1~ubuntu.22.04~jammy \ containerd.io docker-buildx-plugin docker-compose-plugin装完之后把备份的daemon.json恢复回去但不要无脑覆盖。新版本可能引入了新的配置项或者某些旧配置项已经被废弃。我的做法是先把旧配置放回去然后逐项对照新版本的配置文档确认一遍重点看这几个{ data-root: /data/docker, storage-driver: overlay2, exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, live-restore: false, default-address-pools: [ { base: 172.30.0.0/16, size: 24 } ] }几个字段值得单独说。log-opts那一组一定要配默认的 json-file 驱动不带轮转日志会一直长几个月就能把一个分区撑满这是非常常见的生产事故。default-address-pools是给容器网络划分地址段用的默认的地址池在某些网段规划严格的环境里会撞车提前指定能避免后续调整。5.4 启动与首轮观察配置就位后启动sudo systemctl daemon-reload sudo systemctl start containerd sudo systemctl start docker sudo systemctl enable docker启动后不要立刻跑业务先观察一分钟。看服务状态、看日志尾部sudo systemctl status docker --no-pager sudo journalctl -u docker -n 100 --no-pager日志里如果出现failed to start daemon、error initializing graphdriver、failed to load listeners这类字样先停下来处理别硬着头皮继续。这几个报错基本都指向配置或驱动问题继续往下走只会把问题放大。6. 验收清单跑不通这几项就等于没升完服务起来了不等于升级成功。我这边有一套验收清单每次都跑不跳项。6.1 三个必看指标第一条版本对不对docker version客户端和服务端都要看确认两边版本一致。有时候 CLI 升级了但守护进程还是老的或者反过来这种不一致会导致某些命令行为异常。第二条运行时信息对不对docker info重点看这几个字段Storage Driver、Cgroup Driver、Cgroup Version、Docker Root Dir。四个字段要和你预期的一致。特别是Docker Root Dir如果它指回了/var/lib/docker而你原本配的是独立磁盘说明daemon.json没生效。第三条能不能真的跑起来docker run --rm hello-world这条命令同时验证了镜像拉取、容器创建、网络配置、运行时启动四件事是最小成本的端到端检查。6.2 网络、存储、日志的回归端到端跑通之后还要按业务实际用到的能力逐项回归。网络方面检查自定义网络是否还在容器之间能不能通docker network ls docker run --rm --network your-network alpine ping -c 2 target如果业务依赖固定网段的容器网络一定要确认地址段没有变。升级后默认地址池变化导致容器 IP 段漂移进而撞上内网其他网段这种事我见过不止一次。存储方面检查卷是否挂得上、数据是否读得到docker volume ls docker run --rm -v volume:/data alpine ls -al /data日志方面确认轮转配置生效docker inspect container | grep -A 5 LogConfig输出里应该看到max-size和max-file。6.3 回滚路径怎么走验收不通过就要回滚回滚方案必须提前想好不能临场发挥。标准回滚流程是这样的停掉新版服务和容器装回旧版本包恢复旧的数据目录启动验证。sudo systemctl stop docker sudo systemctl stop containerd # 把新版产生的数据目录挪走 sudo mv /var/lib/docker /var/lib/docker.new.$(date %F) # 把升级前改名的旧目录改回来 sudo mv /var/lib/docker.old.日期 /var/lib/docker # 装回旧版本 sudo yum install -y docker-ce-旧版本号 docker-ce-cli-旧版本号 containerd.io-旧版本号 sudo systemctl start docker这里的关键是数据目录也要一起回滚。只回滚包不回滚数据旧版本守护进程面对新版写过的数据结构可能直接起不来或者起得来但读不出内容。我的建议是把回滚窗口定在一周。这一周内旧包、旧数据目录、旧配置都留着确认新版本稳定之后再清理。清理的时候用删除而不是直接rm -rf先移动到临时位置观察一两天避免误删还在用的东西。7. 几类高频报错的现场判断最后聊几类实际遇到频率最高的报错以及我的判断顺序。这些不是穷举是经验里最常撞上的几个。7.1 服务起不来journalctl 是唯一入口systemctl start docker返回Job for docker.service failed这是最没信息量的一句话。真正的错误在日志里sudo journalctl -u docker -n 200 --no-pager按我的经验这里的报错大概分三类。第一类是配置解析失败报unable to configure the Docker daemon with file /etc/docker/daemon.json。基本就是 JSON 语法错了多一个逗号、少一个引号、用了注释。JSON 标准不支持注释这是最常见的坑。第二类是存储驱动初始化失败报error initializing graphdriver或者driver not supported。这时候要回去看文件系统是否支持 overlay2xfs 的ftype是不是 1以及>sudo apt install -y containerd.io1.6.24-1 docker-ce5:24.0.7-1~ubuntu.22.04~jammyRedHat 系上则可能遇到包管理器的保护机制报conflicts with file from package。这时候需要加--allowerasing让包管理器自行解决冲突但加这个参数之前一定要确认它不会删掉你要的包。先跑一次--assumeno看看它打算做什么sudo yum install --assumeno docker-ce-24.0.7 docker-ce-cli-24.0.7确认清单没问题再把--assumeno换成-y。7.3 socket 权限与用户组的老问题装完之后普通用户执行docker ps报permission denied while trying to connect to the Docker daemon socket这是权限问题不是安装问题。Docker 的 socket 默认只有 root 和 docker 组成员能访问。解决方式是把用户加进组sudo usermod -aG docker $USER newgrp dockernewgrp是为了在当前会话里立刻生效不然要重新登录。这一步看着简单但很多自动化脚本就是以普通用户身份跑的升级完之后脚本全挂排查半天发现是这个原因。需要提醒的是把用户加进 docker 组等价于给了这个用户接近 root 的权限因为挂载宿主目录进容器就能读写任意文件。在生产机上要控制这个组的成员范围不要图省事全员加进去。最后分享一个我自己总结的小习惯每次升级前我会在一张纸上或者变更单里写下三行字——当前版本号、目标版本号、当前存储驱动。升级完成后对着这三行逐一核对全都对得上才算完。这个动作看起来形式主义但它强迫我在动手之前就把最关键的三个事实确认清楚而不是靠记忆或者“应该没问题”。真正救过我的往往不是那些复杂的技术方案而是这种低成本的确认动作。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询