ARTICLE DETAIL

资讯详情

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

查看已退出Docker容器日志:docker logs命令与json-file原理详解

查看已退出Docker容器日志:docker logs命令与json-file原理详解 身边不少同事第一次遇到这个问题时都蒙了容器明明已经退出了程序早就停了可它崩溃前到底输出了什么登录网页控制台又查不到难道只能重新跑一遍然后现场抓日志其实不用Docker 早就把退出容器的日志保留下来了只是很多人不知道去哪里看、怎么看。这篇文章就围绕“查看已退出 Docker 容器的日志”这个场景把常用的命令、原理、日志文件位置以及我踩过的坑一次性讲清楚无论你是刚入门容器还是已经写了两年 Dockerfile都能直接拿去用。先说结论只要容器使用的是默认的 json-file 日志驱动哪怕容器已经停止、删除前或者重启前它打印到 stdout/stderr 的日志都还在宿主机上。所以你需要做的只有三件事找到容器执行 docker logs或者直接去宿主机翻日志文件。就这么简单但实操里会扯出不少细节下面一个个拆开说。1. 已退出容器日志为什么值得单独讲1.1 日志是排查崩溃问题最直接的证据容器退出分很多种情况正常完成任务退出、OOM 被杀、代码报错抛出异常、健康检查失败被编排系统停止还有手动 docker stop。除了你主动停的那部分其余绝大多数退出都是异常行为而现场第一手证据就是日志。我遇到过很多次类似的问题Java 容器跑着跑着突然退出看 Docker 状态只有 Exited (137) 这个数字表面信息一点用都没有。137 是 128 9意味着进程收到了 SIGKILL很大概率是内存超限被内核杀掉但到底是哪一块内存爆了必须看应用日志才能知道。如果你只看容器当前状态不翻日志会浪费大量的时间去猜。类似地退出码 1 通常是应用自身报错退出码 139 是段错误128 11退出码 143 是 SIGTERM128 15。这些数字只能告诉你“怎么死的”不能告诉你“为什么死”。日志里那一堆堆 UTF-8 编码的异常堆栈才是破案的关键。所以掌握查看已退出容器日志的能力是容错排障的基本功。这比在代码里打一千个 System.out.println 都管用因为只有日志才能还原历史现场。1.2 容器退出后日志并不会自动消失很多人会误以为容器退出后日志会跟着容器一起“结束”。实际上只要容器对象还在docker ps -a 还能看到它日志文件就一直存在宿主机上。默认的 json-file 日志驱动会把每个容器的 stdout/stderr 写入一个文件路径由 Docker 统一管理一般形如/var/lib/docker/containers/container-id/container-id-json.log容器停止只是进程停了这个 log 文件还在磁盘上。只有当你执行 docker rm 真正删除容器后日志文件才可能被清理如果你开了容器文件系统自动清理的话。理解这个文件路径很关键因为后面有些场景下 docker logs 命令可能输出不了东西但文件还能捞回日志。所以我个人的习惯是任何生产环境的容器日志驱动都要单独规划默认的 json-file 必须配好 rotate避免一个日志文件把磁盘撑爆同时还要把关键业务日志输出到 stdout/stderr而不是只写进容器内部的文件否则容器一删日志就真没了。2. 最趁手的工具docker logs 命令全解2.1 先定位你要查的已退出容器既然是查看“已退出容器”的日志第一步一定是把容器找到。运行中的容器用 docker ps 就能看到但退出的容器不会出现在默认列表里必须加 -a 参数docker ps -a | grep 关键词输出里会有一列 STATUS写着 Exited (状态码) 加上退出时间。我一般会先看这一列因为它直接告诉我退出码能快速判断是正常结束还是被杀。容器名字是人为指定的容器 ID 是 Docker 生成的 64 位十六进制字符串。实际上在查日志时你不需要写全 ID前 4-6 位就够了Docker 能根据唯一前缀匹配。比如docker logs abc123如果前缀对不唯一它会提示你 Ambiguous container ID 之类的错误这种时候再补几位字符即可。还有个小技巧如果你用了 docker-compose服务名会自动拼接成容器名比如 project_web_1。用这个格式化后的名字直接查日志就行不用记 ID。2.2 基础查询参数tail、since、until、timestampsdocker logs 的语法很简单docker logs [OPTIONS] CONTAINER不加任何参数时它会输出容器全部日志。这个行为对已经退出的小容器没毛病但如果容器跑了好几天日志文件可能有几百兆直接输出到终端会把屏幕刷爆终端都卡死。所以正确的姿势是先用 tail 限制条数docker logs --tail 200 container-id这表示只看最后 200 行日志对找出容器退出前发生了什么最有用。我排障的时候基本第一遍都是这样查人脑一次能看完的信息量大概就在 200 行以内再多了要从全局找规律反而难。如果想按时间过滤用 --since 和 --until 配合比如docker logs --since 2025-01-01T00:00:00 --until 2025-01-01T02:00:00 container-id--since 还支持相对时间非常方便docker logs --since 10m container-id docker logs --since 1h30m container-id这个相对时间在刚发现问题时最好用因为不用算秒时间戳。比如你在 14:00 发现容器挂了心里估摸着 13:50 左右它就开始不正常直接 --since 10m 查最近十分钟日志就够了。加个 --timestamps 参数会在每行日志前面带上精确到纳秒的时间戳方便和相关监控、业务系统的时间轴对齐docker logs --tail 50 --timestamps container-id需要注意的是--timestamps 输出的时间默认是宿主机时区不是容器内时区。如果容器配置了 TZ 环境变量应用打印的时间可能和 Docker 附加的宿主时间不一致对时间线时要特别注意。2.3 用 -f 参数实时跟踪与退出后的日志查看docker logs -f 是实时跟踪模式相当于 tail -f。对运行中的容器调试很有用但题目说的是“已退出容器”那 -f 还能用吗答案是可以但看到的不会继续增长因为进程已经停了输出到文件中就是固定内容。除非你启动了一个“伪退出”容器也就是因为错误不断重启的容器此时 -f 可能让你看到每一条新的重启日志甚至能看到它“死而复生”的过程。实际使用中如果遇到容器的 restart policy 是 unless-stopped 或 always并且容器被 OOM 反复杀掉那么它会在退出和启动之间循环。你在 docker ps 里看到的 STATUS 可能是 Exited 然后又 Restarting也可能短暂是 Up 又变。这种时候用 docker logs --tail 50 -f 就能实时看到每一次启动时打的日志。需要特别提示一个 bug 一样的体验docker logs -f 并不保证能把已删除容器的日志拉回来。容器一旦执行 docker rm容器对象彻底删除日志文件通常也随之删除默认配置下所以任何时候第一原则都是不要急着删容器先把日志导出来。2.4 查询退出的容器时常见的权限与存根问题有些场景下你会遇到明明 docker ps -a 能看到容器但执行 docker logs 报错。最常见的提示是Error: No such container: id这说明容器已经被删了docker ps -a 看到的是你记忆里的旧输出或者你正在操作的 Docker 环境不是同一个比如先 ssh 到 A 机器查看后来切到 B 机器执行 logs。另外一个经典坑是Docker Desktop 环境里面查日志没问题但到了 Linux 服务器上用非 root 用户执行 docker logs 时会提示权限不足。docker 命令本身走的是 /var/run/docker.sock 这个 Unix socket只有 docker 组里的用户才有权限。如果提示 permission denied 而不是 No such container先确认一下当前用户是否在 docker 组里或者使用 sudo。生产环境里我建议专门建一个 “docker 排障账号”只加入 docker 组不放 root 权限。这样给开发和运维人员查看日志足够又不会开放宿主机全局权限。3. 从宿主机文件系统直接捞日志3.1 搞清楚默认日志文件存哪儿和为什么 docker logs 失效docker logs 命令本质上是读取宿主机上的日志文件只不过封装成了 API。当你面对一个已退出容器且 docker logs 因为权限、Docker daemon 异常或者容器对象丢失而无法使用时可以直接绕开 Docker到宿主机文件系统里翻日志。默认路径为/var/lib/docker/containers/container-id/container-id-json.log但你很难凭肉眼记住那一长串容器 ID。更稳的做法是用 docker inspect 获取日志文件的绝对路径docker inspect --format{{.LogPath}} container-id这行命令会直接输出类似 /var/lib/docker/containers/3a8f.../3a8f...-json.log 的路径。然后用 tail、grep、less 直接操作文件tail -n 200 $(docker inspect --format{{.LogPath}} container-id)这里用到了 shell 命令替换先拿到路径再 tail。有没有注意到就算容器已经退出甚至 docker logs 命令抽风只要你还没 docker rm文件一定还在用 cat/tail/grep 都能正常读。3.2 json-file 日志格式并不是纯文本如果你直接 cat 这个 -json.log 文件你会看到每一行都是 JSON 字符串类似{log:2025-01-01 12:00:00.123 INFO ...\n,stream:stdout,time:2025-01-01T12:00:00.123456789Z}这是 json-file 驱动存储的原始格式其中 log 字段保存的是应用输出的原始行stream 字段标记是 stdout 还是 stderrtime 是 Docker 记录的时间。直接用 grep 时你搜到的结果会带着大括号和转义字符看起来很不舒服。所以如果要从文件层面检索内容建议先做一步提取把 log 字段里的内容拉出来再 grep。一个简单的做法是用 jqcat container-id-json.log | jq -r .log | grep Exceptionjq 的 -r 参数会输出原始字符串也就是去掉转义、还原换行。没有 jq 的环境可以用 python写法也很短python3 -c import json,sys for line in sys.stdin: print(json.loads(line).get(log, ), end) container-id-json.log | grep ERROR这种查法在日志量极大的时候比 docker logs 更灵活因为你可以结合 awk、sed、sort、uniq 做任意处理。比如统计某个时间段异常关键字出现多少行或者按 stream 字段统计 stdout/stderr 的占比这些 docker logs 原生做不到。3.3 默认驱动换成 local 或 journald 后怎么看如果你在宿主机上改过 Docker daemon 配置将日志驱动从 json-file 切换到了 local 或 journald那查看方式会略有不同。local 驱动写日志的方式是二进制格式但它仍然把日志看作一个可迭代的流docker logs 能正常读取。不过直接在文件系统里随时翻 Json 文件的方式就不适用了因为 local 驱动生成的是一个不可直接文本解析的文件长这样/var/lib/docker/containers/container-id/container-id/local-logs/container.log它本质上是基于 protobuf 编码的日志块直接 cat 会看到乱码。这种情况建议还是老老实实用 docker logs别尝试手工解析。journald 驱动更特殊日志会进入 systemd journal不再走 docker 自己的文件。查询方式变成journalctl CONTAINER_IDcontainer-id或者安装 docker 插件后使用 docker logs 也能兼容查询。如果容器退出且 journald 日志被 journald 自动清理过那也会丢历史需要看你主机 journald 的 Retention 配置。在 systemd 版本的 Linux 服务器上我一般不建议维护敏感日志时用默认 journald因为它默认最多存几天和 json-file 的持久性差了一截。3.4 实战容器退出但 docker logs 查不到内容的场景有一种容易误会的情况容器退出了docker logs 执行后没有任何输出不代表日志不存在。最常见的原因是 Docker 日志驱动设置为 none。配置一旦是 none容器的 stdout/stderr 会被直接丢弃docker logs 永远为空只能看应用自己写到挂载卷里的文件日志。排查方法很简单docker inspect --format{{.HostConfig.LogConfig.Type}} container-id如果输出 none那恭喜你踩到一个大坑。解决方案是要么改造应用把日志同时输出到文件卷要么把驱动改回 json-file 后重新创建容器。修改驱动后新容器才会开始记录日志旧容器丢失的日志补不回来这点要有心理准备。顺带提一句容器内应用如果自己写日志文件比如 /logs/app.log而你没有挂载卷容器一删文件就没了。我见过程序员自信地认为“日志都在容器里没事”结果 OOM 重启后代码挂了容器被 Docker 自动清理然后整个人都麻了。所以但凡日志要长期留存的一定挂到宿主机目录或者对象存储。4. 实操全流程从发现退出到导出日志一次走完4.1 判断容器的健康和退出前状态我习惯在排查时先做一套“三板斧”docker ps -a | grep 服务名 docker inspect container-id | grep -A 10 State docker logs --tail 200 --timestamps container-id第二条 inspect 里的 State 字段会告诉你 StartedAt、FinishedAt、ExitCode、OOMKilled 这些关键信息。比如 OOMKilled 为 true那就非常明确了内核杀进程先去看内存配置而不是先去看业务代码。FinishedAt 和 StartedAt 之间的时间差也能反映这次退出的特征。第三步就是日志。这三步下来整个“什么时候挂、怎么挂、挂了以后说了什么遗言”就齐了。这里有个操作时间点的细节很多情况下容器退出后还会被 docker-compose 或 Kubernetes 自动调度重建容器 ID 变了原容器的日志还在但需要花点时间在 docker ps -a | grep 里找到旧 ID。遇到这种情况别慌docker ps -a 能显示所有历史容器包括已经退出的直到你手动清理。4.2 用 docker logs 导出日志并备份到本地有时候容器日志太长你需要在本地慢慢分析或者把日志转给其他同事。常见的导出方式是用重定向docker logs container-id /tmp/container.log 21但注意这种方式有个容易忽视的坑如果你不加 21docker logs 输出的 stderr 并不会被记录为标准输出重定向到文件而终端上可能“看起来”少了很多报错行。其实是两股流混在一起被终端吸收但只有 stdout 进了文件。排障场景里日志凑不齐很头痛所以我会统一加上 21。如果你只想导出最近一小时用docker logs --since 1h container-id /tmp/container-last-1h.log 21导出后的文件可以用 less 查看或用 grep 直接过滤关键信息grep -i exception\|error\|caused by /tmp/container-last-1h.log多说一句日志文件里往往会混着带 ANSI 颜色的控制字符这在终端里是彩色的但落盘后就是一堆乱码。借助 grep 时它们是可见字符有时候会干扰你搜索建议用 sed 去掉sed -r s/\x1b\[[0-9;]*m//g /tmp/container.log /tmp/container-clean.log这样处理后文件更干净发出去别人也不会看到一堆 [32m 之类的字符。4.3 日志轮转配置防止一个容器写满磁盘排查已退出容器的日志时最尴尬的事是日志文件太大docker logs 半天打不开。这背后就是日志没有轮转。默认配置下json-file 驱动不会自动轮转一个跑了几周的容器可能写几十 GB。所以我在生产环境部署新容器时第一步就是给 Docker daemon 设置全局轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }max-size 表示单个日志文件超过 10MB 就轮转max-file 表示最多保留 3 个文件。超过的老日志会被 Docker 自动删除新日志写入新文件。修改 daemon.json 后重启 dockersudo systemctl restart docker重启 docker daemon 会影响所有正在运行的容器所以这种修改最好放在变更窗口里做。如果容器是 docker-compose 启动的也可以在 compose 文件级别单独配置services: app: image: your-app:latest logging: driver: json-file options: max-size: 10m max-file: 3配置轮转后docker logs 读取时还是会自动把多个日志文件拼接起来所以对用户来说体验依然是连续的。已经退出的容器如果之前没配置轮转且日志文件非常大用 docker logs 可能比较吃力此时直接用 tail 宿主机上的原文件会更高效。4.4 已退出容器的日志与 Docker 清理机制还有一个要提前预防的问题Docker 的定时清理机制。docker system prune 默认会清理已经停止的容器并且很多人的 CI 脚本会定期执行docker system prune -f。这意味着今天还在的已退出容器明天可能就被清掉日志也就没了。不管是排障还是合规需要当你发现一个异常容器退出后最好的习惯是第一时间把日志导出备份然后再处理容器本身。甚至对于重点服务建议调整 prune 的过滤条件保留近期退出容器docker system prune -a -f --filter until168h这样只清理创建时间超过 7 天的旧数据最近一周内退出的容器还能保住。别等到线上服务挂了三天才想起容器早被自动清理了那时候再好的命令也找不回日志。5. 常见故障排查与避坑手册5.1 问题速查表我在日常工作和帮别人排查时整理过一张查看已退出容器日志的问题速查表贴出来给各位参考症状可能原因应对方法docker logs 报 No such container容器已被删除或不在当前节点docker ps -a 核对 ID换到正确宿主机docker logs 没有任何输出驱动是 none或容器从未写 stdout改应用输出或检查挂载卷中的文件日志日志文件存在但 docker logs 卡死文件过大内存/IO 吃紧直接使用 tail/grep 操作宿主机原文件日志里有大量转义字符、大括号json-file 原始格式用 jq 或 python 解析 log 字段再过滤容器反复退出且日志反复出现restart policy 导致循环或 OOM看 OOMKilled 状态调整内存限制日志时间与应用内时间不一致时区、时戳来源不同核对 --timestamps 用宿主时区应用内用 TZ这张表基本涵盖了 90% 的日常问题。遇到没覆盖的就先走一遍 inspect 加 logs 组合再查文件和驱动配置大概率能定位。5.2 排查已删除容器日志的一个土办法最后分享一个土办法。有时候容器已经docker rm但文件系统上日志文件还在因为 Docker 不会立刻回收所有文件特别是容器目录还在磁盘上。你可以在宿主机上按模糊匹配找find /var/lib/docker/containers -name *-json.log -size 1k | xargs grep -l 关键词 2/dev/null这个命令会遍历所有历史容器日志找到包含关键词的文件然后你再根据路径里的容器 ID 反查能够找回一部分已经脱离 Docker 管理的日志。可靠性不能保证因为 Docker daemon 如果执行过清理文件可能已经不在了但作为最后的救命稻草大多数场景下都能帮上忙。5.3 给新手的三个忠告第一容器内不要只写文件日志不留 stdout否则一旦容器被删日志全部蒸发输出到 stdout/stderr 是被 Docker 生态默认支持的最佳实践也是 docker logs 能查询的基础。第二运维脚本里不要频繁执行 docker rm尽量让它保留一定时间真要自动清理也把过滤条件写明确。第三一切排查都要有日志留存意识。无论查不查得到原因先导出原始日志归档这笔操作成本不高但未来复盘或定责时就值钱了。我个人在实际操作中最深的体会是查看已退出容器日志这件事难点从来不在于命令本身而在于“你什么时候意识到要查”以及“你还能不能查得到”。养成“容器退出先导日志再动容器”的习惯比背一百条 docker logs 参数都管用。如果你手里还有正在跑批处理任务后频繁退出的容器不妨现在就把它的日志目录和 json-file 配置检查一遍能省下后面一大半排查功夫。
返回列表