资讯动态

Docker容器存储全解析:分层机制、数据卷与磁盘清理实战

发布时间:2026/10/27 3:54:14 来源:尧图企业网站定制
1. 先把存不下这件事说清楚容器存储到底分几层docker 容器存储问题十个里有八个最后都落到同一句抱怨上——我也没往容器里放多少东西磁盘怎么就满了。这个直觉之所以经常骗人是因为容器的磁盘占用和你看到的文件大小根本不是一张账本。镜像层、容器可写层、数据卷、绑定挂载进来的宿主机目录、容器日志、构建缓存这六样东西在docker ps里全都看不见但它们才真正吃掉你的容量。我在第一次遇到宿主机磁盘被吃满时反复删容器、删镜像docker system df显示空间确实降了可df -h里的可用空间只回了不到三分之一剩下的部分藏在被删除但仍被进程持有的文件句柄里那是后话了。这篇内容想解决的是把容器存储从一团模糊的焦虑拆成一串可以按图索骥的具体问题镜像和容器各自占哪些目录、什么数据该放卷、什么数据天生不该持久化、默认存储路径怎么迁走、日志怎么限制、空间异常时按什么顺序排查。适合已经能把容器跑起来、但被磁盘告警和容器删了数据也没了折腾过的人也适合刚接手一套容器化环境、准备把存储策略定下来的同行。命令和参数我会给全思路和取舍我也会讲清楚因为哪一步没想明白后面都要用事故补学费。1.1 镜像层与容器可写层写时复制到底付出了什么代价要理解容器的磁盘账先得接受一个事实容器不是一台虚拟机它没有一整块属于自己的磁盘。镜像由若干只读层叠起来容器启动时在最上面加一个薄薄的可写层所有写操作都落在这个可写层里。这套机制叫写时复制Copy-on-Write意思是文件原本在下层是只读的你要改它内核先把整个文件复制到上层再在上层改。注意是整个文件不是一个字节。这就解释了一个反直觉的现象你在容器里往一个 2GB 的数据文件尾部追加了一行看起来只写了几十字节实际磁盘上多了一整个 2GB 的副本。日志文件、SQLite 数据库、正在被写入的二进制文件放在可写层里都是这个待遇。我见过一个服务把日志写在容器内的应用目录下日志涨到 8GB容器的 writable layer 就实打实占了 8GB而宿主机上的日志轮转策略对它完全无效因为这份文件根本不在宿主机的目录树里被管理。可写层的生命周期也值得强调容器删除可写层跟着消失数据一起蒸发。这不是缺陷是设计。容器被当成一次性计算单元状态应该外置。所以判断一段数据该不该留在可写层只需要问一句这个容器被重建时这份数据丢了会不会出事。会出事的一律外置不会出事的临时缓存、pid 文件、运行时生成的锁留在可写层反而干净。还有一种更容易被忽略的占用镜像层本身。每次docker build都可能产生新的层旧的中间层不会自动回收构建缓存会一直堆在/var/lib/docker下。一个每周构建几十次的环境构建缓存涨到几十 GB 是很平常的事而且它不在docker images的列表里只能靠docker system df里那一行 Build Cache 才看得见。1.2 三种数据落盘方式卷、绑定挂载、tmpfsDocker 给你三条把数据搬出可写层的路选错了会在权限、性能、可移植性上连本带利地还回来。**数据卷volume**由 Docker 自己管理物理位置在宿主机的/var/lib/docker/volumes/卷名/_data。它的优势是跨平台一致、可以用 Docker 的命令备份和迁移、在多个容器间共享而且不依赖宿主机上某个具体路径存在。生产环境里凡是需要持久化的数据我的默认选择都是卷。**绑定挂载bind mount**直接把宿主机的一个目录映射进容器。它适合配置文件、开发时的源码热加载、以及需要和宿主机上其他程序共享的目录。代价是耦合了宿主机的目录结构换一台机器路径就可能不存在而且宿主机上的权限、属主、强制访问控制策略会原封不动地带进容器权限问题的坑大多出在这里。tmpfs挂在内存里容器停止就没了。适合放会话缓存、临时解压目录、需要极低延迟又不该落盘的中间文件。它有个容易被忽略的好处给/tmp挂一个带 size 限制的 tmpfs可以防止某个失控进程写满整块磁盘。方式物理位置生命周期典型用途主要风险可写层/var/lib/docker/overlay2/id/diff随容器删除运行时临时文件写放大、随容器消失数据卷/var/lib/docker/volumes/独立于容器数据库、上传文件忘记清理越积越多绑定挂载宿主机任意路径独立于容器配置文件、源码权限、路径耦合tmpfs内存随容器停止缓存、临时目录吃内存超限会写失败选型上我自己的规矩很朴素配置用绑定挂载只读挂进去业务数据用卷临时垃圾用 tmpfs可写层只放重建就重建、丢了也无所谓的东西。这条线一划清后面百分之八十的存储事故都不会发生。1.3 存储驱动选型为什么现在几乎只剩下 overlay2早些年存储驱动是个需要认真比较的选项aufs、devicemapper、btrfs、zfs 各有拥趸。今天的结论很干脆主流发行版上就用 overlay2别折腾。原因不复杂——overlay2 已经进了内核主线不需要额外装内核模块层数支持和性能都够用社区和生态的默认适配也都在它身上。docker info里能看到当前用的驱动。如果显示的是 vfs那要留神vfs 不做任何分层复用每一层都完整复制一份磁盘占用会成倍膨胀只适合测试环境或者文件系统实在不支持的场合。devicemapper 的 loop-lvm 模式更是老古董性能和容量都有硬伤遇到就换成 overlay2。overlay2 也有它的边界层数太多会拖慢文件查找所以 Docker 对镜像层数有上限约束这也是为什么 Dockerfile 里要尽量合并 RUN 指令。另外 overlay2 依赖底层文件系统支持d_type早年 XFS 如果不带ftype1格式化就会出问题现在新版本基本默认开启但接手老机器时值得用xfs_info确认一下。这些细节平时用不上一旦踩到就是启动直接失败排查起来毫无头绪。2. 上线前要定下来的几件事存储方案怎么选存储这块有个特点事后补救的成本远高于事前想清楚。容器跑起来、数据写进去之后再改存储方式意味着停机、迁移、校验还得赌一把数据完整性。所以在按下docker run之前有几件事值得花半小时定下来哪些服务是有状态的、卷用什么命名规范、权限怎么对齐、容量按什么口径估。这半小时省下来的往往是后面几个通宵。2.1 有状态与无状态的分界线我习惯把服务先分两类。无状态服务比如前端静态资源、无状态的 API 网关随便重建存储上不需要任何持久化配置日志走标准输出交给日志驱动处理就行。有状态服务数据库、消息队列、对象存储、上传目录必须把数据目录挂出来而且要在编排文件里写死不能靠人记。这条线画错的典型症状是数据库容器的数据目录没挂卷跑了三个月某次升级重建容器数据没了。更隐蔽的版本是挂是挂了但挂的是容器内路径的一个子目录父目录下的其他数据照样丢。我的做法是把有状态服务的挂载点在编排文件里单独列一段注释写清楚此处数据不可丢并且定期用脚本校验这些挂载点确实指向了卷或者宿主机目录而不是静默地落在了可写层。还有一类服务容易被误判成无状态带本地缓存的搜索服务、需要保留索引的日志采集器。它们丢了数据不会立刻报错但会引发长时间的重建和性能抖动。这类我也归到有状态一侧给卷图个安稳。2.2 参数对照-v 与 --mount 到底差在哪-v和--mount做的是同一件事但行为差异会在出问题时放大。-v是简写如果宿主机路径不存在Docker 会自动创建一个目录你以为挂的是文件结果挂了个空目录进去应用读配置读了个寂寞。--mount更严格源路径不存在直接报错不会替你猜。我现在的习惯是交互式试验用-v图快写进编排文件、脚本、文档的一律用--mount。多打几个字换来的是错误当场暴露而不是三天后才发现。# 简写宿主机路径不存在时会自动建目录容易埋坑 docker run -d --name app1 -v /data/app/conf:/etc/app/conf:ro nginx:stable # 显式写法源不存在直接报错类型和只读属性一目了然 docker run -d --name app2 \ --mount typebind,source/data/app/conf,target/etc/app/conf,readonly \ nginx:stable # 命名卷不用关心宿主机路径交给 Docker 管理 docker run -d --name app3 \ --mount typevolume,sourceapp_data,target/var/lib/app \ nginx:stable命名卷有个必须知道的行为当卷是空的、且容器内目标路径已有内容时Docker 会把镜像里那份内容复制进卷。这个初始化只发生一次。它对镜像自带一份默认配置用户第一次启动时想要这份默认值的场景很友好但在恢复备份时会坑人——你先docker run创建了卷已经写入了默认内容再往里面解压备份旧文件和新文件混在一起出现一堆莫名其妙的残留。正确顺序是先建空卷、不解压前不启动容器或者解压前把卷清空。2.3 权限与属主容器里写文件为什么会 Permission denied绑定挂载最常见的报错是Permission denied原因是容器内进程的 UID 和宿主机目录的属主对不上。举例宿主机目录属主是 UID 1000容器里跑的应用是 UID 101很多官方镜像会给应用建一个固定 UID 的用户写文件自然被拒。这不是 Docker 的 bug是 Linux 权限模型在起作用容器不是权限的例外。处理办法有几种各有代价。最省事的是在宿主机上chown成容器内的 UID缺点是换个镜像 UID 变了又得改稍微讲究一点的是在编排文件里显式指定user让容器以宿主机上某个确定 UID 运行两边对齐再进一步是启用用户命名空间映射让容器内的 root 映射到宿主机上的普通用户安全性更好但会带来卷属主的整体变化不适合在所有环境一把梭。# 查宿主机目录属主 stat -c %u:%g %n /data/app/uploads # 查容器内进程的 UID docker exec app1 id # 让容器以指定 UID 运行编排文件里对应 user: 1000:1000 docker run -d --user 1000:1000 \ --mount typebind,source/data/app/uploads,target/data/uploads \ myapp:1.0提示新建的绑定挂载目录属主和权限一定要在上线前定好别指望容器自己去改。容器启动时改宿主机目录权限在多容器共享同一目录时极易互相覆盖。另外宿主机如果启用了强制访问控制策略即使 UID 对上了也可能被拦。这类问题日志里通常有明确提示看到就按提示给目录打上正确的安全上下文标签不要图省事关掉整个策略。3. 实操把存储路径、日志和清理策略一次性配好前面聊的是判断这一段全是落地。一套容器环境想长期稳定通常要做三件事把数据目录从系统盘挪到独立盘、给容器日志加上限、把清理动作变成例行公事而不是救火。这三件事都有标准做法照着做基本不会出问题唯一的要求是别在生产高峰动。3.1 迁移 /var/lib/docker 到独立数据盘系统盘被容器占满是最常见的故障场景之一。镜像、容器可写层、卷、构建缓存默认都在/var/lib/docker和系统日志、包管理器抢同一块盘。给它单独挂一块数据盘是投入产出比最高的一步。迁移过程本质是把目录整体搬走然后告诉 Docker 新位置。核心是rsync的参数-a保留权限时间-H保留硬链接overlay2 目录里硬链接很多普通cp会把它们展开成多份容量翻倍-A保留 ACL-X保留扩展属性--numeric-ids避免跨机器时 UID 被映射错。# 1. 停服务确保没有写入 systemctl stop docker docker.socket containerd # 2. 预演一次看看要搬多少、有没有权限问题 rsync -aHAX --numeric-ids --dry-run /var/lib/docker/ /data/docker/ # 3. 正式搬迁建议加上进度显示 rsync -aHAX --numeric-ids --infoprogress2 /var/lib/docker/ /data/docker/ # 4. 核对两边文件数量和总大小 du -sh /var/lib/docker /data/docker # 5. 改配置/etc/docker/daemon.json # { # data-root: /data/docker, # log-driver: json-file, # log-opts: { max-size: 100m, max-file: 3 } # } # 6. 启动并确认新路径生效 systemctl start docker docker info | grep -i docker root dir # 7. 观察几天没问题后再删旧目录有两个细节容易翻车。一是旧目录不要立刻删Docker 里有些元数据和挂载状态在切换瞬间还没稳定留几天观察是廉价的保险。二是/etc/docker/daemon.json必须是合法 JSON多一个逗号 Docker 就起不来改完先systemctl start docker看状态别急着重启整台机器。3.2 日志轮转容器日志把磁盘吃满的经典事故容器日志默认走json-file驱动位置在/var/lib/docker/containers/容器ID/容器ID-json.log默认没有任何大小上限。一个打日志比较勤的服务一天写几 GB 很正常如果它同时往标准输出和标准错误狂打磁盘见底只是时间问题。限制要配置在 daemon 层面让所有容器继承默认值而不是逐个容器加参数——逐个加总有一天会漏。{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }这组参数的意思是单个日志文件最大 100MB最多保留 3 个也就是单个容器日志上限 300MB。量级怎么定按峰值日志量估假设某服务峰值每秒写 200KB 日志一天约 17GB那 100MB×3 相当于只保留了几分钟的历史排查问题根本不够用。这种情况下正确的做法不是把上限调到 20GB而是把日志级别降到 warn、把详细日志交给专门的日志采集组件外送本地只留个尾巴。日志存储和日志分析的职责分开各自才好控制。注意日志驱动和轮转参数只在容器创建时生效。改完daemon.json并重启 Docker已存在的容器不会自动应用新参数需要重建容器。想临时验证可以先docker inspect看某个老容器的HostConfig.LogConfig十有八九还是空的。3.3 清理策略与 docker system df 的正确读法docker system df是看存储账本的第一入口加-v能看到每一项的具体条目。它给出四行Images、Containers、Local Volumes、Build Cache。新手常犯的错是只盯第一行看到镜像不大就以为没问题实际上构建缓存和卷才是大头。列含义常见膨胀原因清理命令Images镜像层总占用含未被引用的频繁构建、多版本 tag 并存docker image prune -aContainers容器可写层占用应用往容器内写日志、缓存docker container pruneLocal Volumes卷占用卷随容器删除但卷本身保留docker volume pruneBuild Cache构建缓存每次构建产生新层旧层不回收docker builder prune清理命令里最危险的是docker volume prune它删的是没有被任何容器引用的卷。听起来安全实际上没被引用不等于没用——你把一个容器停掉准备明天恢复它的卷在此期间可能就是未引用状态一执行就没了。我的规矩是卷的删除永远手工确认先docker volume ls列出来看清楚名字再逐个删绝不在生产机上跑带-a的批量清理。# 只看不删先弄清楚要清理什么 docker system df -v # 清理停止的容器、悬空镜像、未使用的网络不碰卷 docker system prune # 清构建缓存保留最近 7 天内的 docker builder prune --filter until168h # 卷相关先列出再决定别用 -a docker volume ls --filter danglingtrue docker volume rm 卷名3.4 卷的备份与恢复三个命令级别的手艺卷由 Docker 管理位置在/var/lib/docker/volumes下但不要直接去那个目录里cp。直接复制文件在有写入的情况下可能拿到撕裂状态而且跨机器的属主、扩展属性容易丢。标准做法是起一个临时容器把卷和目标目录都挂进去在容器里打包。# 备份把 app_data 卷打成 tar.gz 放到当前目录 docker run --rm \ --mount typevolume,sourceapp_data,target/src,readonly \ --mount typebind,source$(pwd),target/backup \ alpine:3.19 \ tar czf /backup/app_data-$(date %Y%m%d).tar.gz -C /src . # 恢复先建一个空卷再解压进去顺序很重要 docker volume create app_data_restore docker run --rm \ --mount typevolume,sourceapp_data_restore,target/dst \ --mount typebind,source$(pwd),target/backup,readonly \ alpine:3.19 \ sh -c cd /dst tar xzf /backup/app_data-20240101.tar.gz # 备份前最好让应用处于静止状态或者在应用层做一次一致性导出这里有个我踩过的坑值得单独说数据库类的卷直接打包文件系统是不可靠的。数据库在运行时页缓存里的数据和磁盘上的文件不一致打出来的包可能起不来。正确姿势是先用数据库自己的导出工具做逻辑备份再考虑文件级备份作为兜底如果必须文件级备份就先停写或者用文件系统快照。文件级备份看起来快、看起来省事恢复失败一次就够记一辈子。4. 空间异常排查实录从现象到根因的路径磁盘告警时最忌讳的是上来就docker system prune -a。删完之后空间是回来了但根因没找到下个月同一时间再告警一次而且删掉的东西里可能有别人正在用的镜像。我一般的顺序是先确认是哪一块在涨再确认是哪个容器再确认是文件还是句柄最后才动手清。这个顺序能把误删的概率压到很低。4.1 删了文件磁盘不降被进程占用的句柄df -h显示用了 95%du -sh加起来只有 60%这中间 35% 的差额几乎总是同一个原因有文件被删了但仍有进程持有它的句柄inode 没释放空间自然不回收。容器场景下这个进程可能是容器里的应用也可能是宿主机上的日志采集器。# 找出被删除但仍被占用的文件 lsof L1 2/dev/null | head -50 # 按占用大小排序看谁最狠 lsof L1 2/dev/null | awk {print $7, $1, $2, $9} | sort -rn | head -20找到之后有两条路重启对应进程或者手工清空句柄 /proc/pid/fd/fd仅对部分场景有效且风险较高。生产上我倾向重启或滚动替换那个容器干净、可控。这类问题的根因通常是应用自己删日志文件日志组件按大小切分文件删掉旧的但写日志的进程还开着老文件的 fd空间就一直挂着。解决办法是让应用支持 reopen收到信号后重新打开文件或者干脆别让应用自己管文件把日志打到标准输出交给日志驱动落盘和轮转交给 Docker。4.2 overlay2 目录里那堆看不懂的目录名/var/lib/docker/overlay2下面是一堆 64 位十六进制命名的目录每个对应一个层或者一个容器的可写层。直接ls完全看不出谁是谁但按大小排序之后最大的那几个往往就是问题所在。# 按占用排序找最大的层 du -sh /var/lib/docker/overlay2/* 2/dev/null | sort -rh | head -10 # 把目录名映射回容器diff 目录对应哪个容器 docker ps -aq | while read id; do dir$(docker inspect $id --format {{.GraphDriver.Data.UpperDir}}) size$(du -sh $dir 2/dev/null | cut -f1) echo $size $id $(docker inspect $id --format {{.Name}}) done | sort -rh | head -15这段脚本解决的是我知道有个容器占了很多但不知道是哪个的经典困境。跑完通常能立刻定位到一个容器进去看它的可写层里到底堆了什么。我遇到过的情况包括应用把日志写到容器内的相对路径、临时上传文件没清理、崩溃转储文件、包管理器缓存没删。这些都能通过改配置或挂卷解决改完重建容器即可。overlay2/l这个目录也值得知道它存的是层之间引用的短符号链接方便 lowerdir 参数不至于超长。它不属于任何单个容器别手动去删。4.3 inode 用满与磁盘配额有时候df -h显示还有空间但就是写不进去报No space left on device。这时候看df -i。容器环境极容易把 inode 用光——大量小文件是 inode 杀手比如缓存目录里每个请求一个文件、日志按分钟切分、包管理器缓存里成千上万个小文件。overlay2 的每个层都带一套完整目录结构层数多了 inode 消耗也翻倍。现象首要怀疑验证命令有空间但写不进去inode 耗尽df -idu和df差异巨大已删除文件被占用lsof L1单个容器占用巨大可写层写了大量数据按 UpperDir 统计卷越积越多卷随容器删除但未清理docker volume ls构建后磁盘暴涨构建缓存未回收docker system df -vinode 耗尽没有优雅解法只能清。优先清构建缓存和包管理器缓存这类文件数量多、价值低。长期方案是给容器挂的卷设置配额或者在挂载 tmpfs 时加size限制把失控写入约束在可控范围内。很多文件系统支持项目配额配置稍麻烦但对多租户环境是值得的。4.4 常见问题速查表把上面这些零散经验收成一张表出问题时按顺序对一遍比从头推理快得多。现象最可能原因处理动作重启容器后数据全没了数据写在可写层改挂卷后续所有数据外置容器内写文件报 Permission deniedUID 与宿主机目录属主不一致对齐 UID 或调整目录属主改了 daemon.json 日志配置不生效只对新建容器生效重建老容器卷里出现一堆不该有的默认文件空卷初始化时复制了镜像内容恢复备份前清空卷磁盘满了但不知道谁占的先查四类占用docker system df -v删文件后空间不回文件句柄未释放lsof L1定位后重启进程5. 性能与容量容易被忽略的细节存储这块最后悔的事往往是当初只考虑了能不能存下没考虑存得够不够快换算成钱合不合算。一个跑在 overlay2 可写层上的数据库写入性能可能只有直连卷的一半一个把所有上传文件都放本地盘的架构扩容时只能整机扩成本曲线很难看。这些不属于故障属于设计债早还便宜。5.1 卷的性能差异本地盘、网络存储、可写层同一份数据放在可写层、本地卷、网络卷上性能差着量级。可写层最差因为每次首次写文件都要 copy-up改大文件等于复制大文件。本地卷接近宿主机裸盘性能只是多一层目录映射几乎无损耗。网络存储看实现块存储挂成本地盘之后当本地卷用性能较好文件级网络存储NFS 之类在小文件随机写上会明显劣化元数据操作开销大数据库这类场景基本不适合。我自己的判断标准是看 IO 模式。顺序读写为主的日志归档、大文件上传网络存储能接受随机小 IO 密集的数据库、消息队列索引一律本地盘宁可让数据跟着机器走靠复制和故障转移保证高可用而不是靠共享存储。这个取舍听着激进但在实际项目里比所有数据都放共享存储稳得多因为共享存储一旦抖动所有依赖它的服务同时受影响。还有一个细节容器内目录的层级也会影响性能。挂载点越深层越复杂overlay2 的路径解析成本越高虽然量级很小但在极高频的小文件访问场景下能测出差别。这事不值得为了性能去改目录结构但值得知道它存在避免在压测出现微小差异时找错方向。5.2 镜像瘦身与构建缓存管理镜像体积直接决定两件事拉取时间和磁盘占用。一个 2GB 的镜像在十台机器上就是 20GB每台机器还有历史版本。控制体积的手段不复杂难在坚持。多阶段构建是最有效的一招编译环境和运行环境分离最终镜像里只有运行时产物编译工具链、源码、中间文件统统留在前一个阶段。# 构建阶段带完整工具链 FROM golang:1.22 AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 go build -trimpath -ldflags -s -w -o /out/app ./cmd/app # 运行阶段只要二进制和必要配置 FROM alpine:3.19 RUN adduser -D -u 10001 appuser WORKDIR /app COPY --frombuilder /out/app /app/app USER appuser ENTRYPOINT [/app/app]配套的两个小动作收益很大写一份.dockerignore把.git、node_modules、测试数据、本地构建产物排掉避免它们进入构建上下文拖慢传输并污染层缓存把变化频率低的指令放前面依赖安装在前源码复制在后让层缓存在日常开发中真正命中。# 看每一层多大找出真正的大头 docker history --no-trunc --format {{.Size}}\t{{.CreatedBy}} myapp:1.0 | head -20 # 构建时不使用缓存验证从零构建是否真的能成功 docker build --no-cache -t myapp:1.0 . # 定期清构建缓存保留最近几天 docker builder prune --filter until72h这里有个容易被误用的习惯为了省空间把--no-cache当成日常构建参数。它会让每次构建都从零开始缓存全废磁盘短期是干净了但构建时间和镜像层重复率都会上升。正确用法是只在验证构建正确性或者怀疑缓存污染时用。5.3 容量规划的一点经验算法容量这事我不想给预留 30%这种拍脑袋数字给个能算的口径更有用。一台容器宿主机的磁盘按这几项加起来再乘个安全系数镜像总量现有镜像层去重后的实际占用按docker system df的第一行取值再考虑未来半年的版本增长通常按 1.5 倍估。容器可写层单个容器按 100–500MB 估如果是写日志到容器内的应用另算。卷按业务数据增长曲线估日志类按日增量乘保留天数。日志单容器上限 × 容器数量即max-size × max-file × 容器数。构建缓存按镜像总量的 1–2 倍估取决于构建频率。系统与其他操作系统、软件包、临时文件留 20GB 起步。举个具体例子50 个容器镜像去重后 25GB单容器可写层按 300MB 估是 15GB卷 100GB日志 300MB × 50 15GB构建缓存按 30GB 估系统留 20GB加起来 205GB乘 1.3 的安全系数约 267GB。这个数比买块 500G 的盘吧靠谱得多也更容易跟非技术同事解释为什么需要这个容量。提示算完之后记得单独看 inode 上限。容量够但 inode 不够一样写不进去而且加盘不一定能解决得看文件系统的 inode 分配策略。6. 我在实际项目里攒下的几条硬经验存储这块的坑有个共同点它们都不会立刻报错而是静静积累直到某天以一个看似无关的故障形式爆发。所以比起记住多少命令我更看重几条能落到日常操作里的习惯。第一条任何容器在写编排文件时先问一句这个容器的数据丢了要不要紧。要紧就立刻挂卷不要先跑起来看看回头再加。回头往往是很久以后而且是数据丢了以后。第二条日志配置写在 daemon 层面不写在单个容器上。逐个容器加参数这种做法的失败率接近百分之百因为总会有人新加容器时忘了加。第三条卷的清理永远手工确认。docker volume prune我基本不用改成先docker volume ls --filter danglingtrue看一眼确认名字不是备用待恢复这类之后再逐个删。批量清理节省的那点时间抵不上一次误删带来的损失。第四条把docker system df -v加进例行巡检每周看一次。这个动作花不了一分钟但能在磁盘告警之前发现异常增长的趋势。等到 95% 再排查选择余地就很小了只能先删东西腾空间问题根因反而没时间找。第五条迁移/var/lib/docker这类操作rsync的-H参数千万别省。overlay2 目录里硬链接非常多用普通复制会把它们展开成独立文件容量翻倍不说Docker 起来之后层引用关系也可能出问题。我见过有人用cp -r搬完新目录比旧目录大了将近一倍还以为是复制出错。最后一条也是最重要的数据库这类有状态服务文件级备份只是兜底主力备份一定是它自己的逻辑导出。文件系统层面的备份在运行中打出来看着是完整的 tar 包恢复起来才发现数据不一致这种事故很难在恢复演练之前被发现。所以备份做完一定要演练一次恢复哪怕只是恢复到一台测试机上验证能起来。没演练过的备份只能算半个备份。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑