Docker 容器信息查看与生产诊断
docker ps 只能回答“Docker 当前知道哪些容器”,不能完整回答容器为什么退出、实际启动了什么命令、用了什么环境变量、挂载了什么目录、加入了哪个网络、是否被 OOM Killer 杀死。本页从零开始建立一套可直接执行的容器信息检查流程。
学习目标
学完后应能:
- 从容器名、ID、镜像、标签中定位目标容器。
- 区分 running、exited、restarting、paused、dead 和健康检查状态。
- 从
docker inspect中准确读取 State、Config、HostConfig、NetworkSettings、Mounts。 - 查看启动命令、环境变量、端口、网络、挂载、资源限制和重启策略。
- 区分
docker logs、容器内文件日志和 Docker 日志驱动。 - 使用
stats、top、exec、events完成实时诊断。 - 判断退出码 137、
OOMKilled=true、Java OOM 和容器总内存超限的区别。 - 把命令结果组织成“现象—证据—根因—修复”的事故结论。
一、先理解Docker命令在问谁
flowchart TD
A["Docker CLI"] --> B["Docker Engine API"]
B --> C["Docker daemon"]
C --> D["容器元数据与运行状态"]
C --> E["containerd / OCI runtime"]
E --> F["宿主机上的容器进程"]
C --> G["网络、Volume、镜像和日志驱动"]当你执行:
docker inspect order-serviceCLI 不是进入容器读取一个配置文件,而是向 Docker daemon 查询该容器的创建配置、宿主机配置和当前状态。即使容器已经退出,只要没有被 docker rm 删除,很多元数据仍可查看。
docker exec 不同:它要求容器正在运行,并在容器现有命名空间中启动一个新进程。容器退出后不能再 exec,但仍然可以 inspect 和 logs。
1.1 每条命令看到的信息来自哪里
刚开始学习 Docker 时,最容易把所有命令都理解成“进入容器查看”。实际上,不同命令读取的是完全不同的数据源:
| 命令 | 主要数据来源 | 容器退出后能否使用 | 信息是否实时 | 回答的问题 |
|---|---|---|---|---|
docker ps -a | Docker daemon 保存的容器对象和状态摘要 | 能 | 状态接近实时,配置是创建时快照 | 有哪些容器、现在处于什么状态 |
docker inspect | daemon 保存的创建配置、HostConfig,加上运行时同步回来的 State | 能 | 配置是快照,State 是最近状态 | 当初怎么创建、最后是什么状态 |
docker logs | 容器日志驱动保存或转发的 stdout/stderr | 通常能 | 取决于日志驱动和保留策略 | 主进程向标准流输出了什么 |
docker stats | Linux cgroup 和网络/存储统计 | 不能查看已退出进程的实时值 | 实时快照 | 当前 CPU、内存、PIDs 和 I/O 怎样 |
docker top | daemon 从宿主机进程视图筛选容器进程 | 不能 | 实时 | 容器当前有哪些进程 |
docker exec | runtime 在既有 namespace/cgroup 中创建新进程 | 不能 | 实时 | 容器内部此刻看到什么 |
docker events | daemon 产生的生命周期事件流 | 不依赖容器仍运行 | 事件流,历史能力有限 | 什么时候 start、die、oom、restart |
理解这个表后,就能解释两个常见现象:
- 容器已经退出,
inspect仍然能看到配置,因为容器对象还保存在 daemon 中;但stats和exec不再有运行中的进程可观察。 - 容器已经被
docker rm删除后,daemon 中的对象元数据也消失,不能指望重新执行inspect找回现场;只能依靠事先外送的日志、指标、事件、镜像清单和发布记录。
1.2 容器对象与容器进程不是同一个东西
flowchart TD
A["docker create / docker run"] --> B["daemon保存容器对象与创建配置"]
B --> C["runtime创建主进程"]
C --> D["Running:对象和进程都存在"]
D --> E["主进程退出"]
E --> F["Exited:对象仍在,进程已不存在"]
F --> G["docker start:按原配置再启动进程"]
F --> H["docker rm:删除容器对象和可写层"]所以:
docker stop是停止主进程,不是删除容器对象。docker start使用原容器保存的环境、端口、挂载和资源配置重新启动,不会自动采用 Compose 文件的新配置。docker restart也是停止再启动同一个容器,不等于重新创建。docker rm才删除容器对象及其可写层;普通命名 Volume 通常仍在,但rm -v、compose down -v等操作需要单独评估数据风险。
这也是为什么修改镜像、环境变量、端口或挂载后,通常需要受控 recreate,只执行 restart 不会让创建时配置发生变化。
二、收到问题后第一分钟做什么
flowchart TD
A["收到服务异常或容器告警"] --> B["记录时间、主机、服务和版本"]
B --> C["docker ps -a定位容器"]
C --> D["inspect读取状态、退出码和OOMKilled"]
D --> E{"容器是否运行"}
E -- "否" --> F["logs与events还原退出前现场"]
E -- "是" --> G["stats、top、health判断实时状态"]
F --> H["检查命令、环境、挂载、网络和限制"]
G --> H
H --> I["进入容器做最小只读验证"]
I --> J["形成证据链并修复"]先记录:
故障时间和时区
Docker宿主机
服务名称
容器名称或ID
镜像名称与tag/digest
是否刚发布、改配置或改资源限制多台 Docker 主机中,docker ps 只查看当前 Docker context/daemon。排查前确认:
docker context show
docker context ls
docker info避免连接错环境后得出“容器不存在”的错误结论。
2.1 先保全现场,再决定是否重启
重启可能恢复服务,但会覆盖启动时间、改变 PID、滚动日志,并让“正在发生的资源问题”消失。删除容器造成的证据损失更大。生产事故中,在业务允许的时间内至少先保存:
docker ps -a --no-trunc --filter "name=order-api"
docker inspect order-api > order-api.inspect.json
docker logs --timestamps order-api > order-api.logs.txt 2>&1
docker stats --no-stream order-api
docker top order-api
docker events --since 30m --filter container=order-api如果容器已经退出,stats 和 top 失败是符合原理的,不表示 Docker 本身损坏。记录失败结果后继续保存 inspect、logs、events、宿主机监控和内核日志。
采集文件可能包含环境变量、挂载路径、业务数据和 Token,必须放在受控目录,上传工单前脱敏。不要为了“收集完整”而在公开聊天中粘贴整份 inspect。
三、docker ps 到底显示什么
3.1 运行中的容器
docker ps等价写法:
docker container ls典型输出:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a31f2a9bd123 order-api:1.4.2 "java -jar app.jar" 2 hours ago Up 2 hours (healthy) 0.0.0.0:8080->8080/tcp order-api| 字段 | 准确含义 | 排查价值 |
|---|---|---|
| CONTAINER ID | 容器唯一ID的短形式 | 后续命令可用ID或名称 |
| IMAGE | 创建容器时声明的镜像引用 | 不能单靠tag证明镜像内容未变化 |
| COMMAND | 展示的启动命令,默认可能截断 | 用inspect看完整Entrypoint/Cmd |
| CREATED | 容器对象创建时间 | 不等于本次进程启动时间 |
| STATUS | 进程状态、运行时间、健康状态 | 判断运行、退出、重启、健康 |
| PORTS | 暴露/发布端口 | 区分容器端口与宿主机端口 |
| NAMES | Docker容器名称 | 推荐作为运维命令目标 |
docker ps 看不到已退出容器。因此“服务没了且 ps 没结果”时第一反应应是:
docker ps -a而不是重新安装 Docker。
3.2 所有容器
docker ps -a常见状态:
| STATUS | 含义 | 第一检查项 |
|---|---|---|
Up 2 hours | 主进程仍运行 | health、stats、应用接口 |
Exited (0) | 主进程正常退出 | 是否本来就应常驻 |
Exited (1) | 应用以通用失败码退出 | logs、State.Error |
Exited (137) | 通常收到 SIGKILL | OOMKilled、events、宿主机日志 |
Exited (143) | 通常收到 SIGTERM 后退出 | 谁发出stop、优雅关闭是否完成 |
Restarting | 重启策略反复拉起 | 启动日志、命令、依赖和健康 |
Created | 创建但主进程未成功运行 | Engine错误、入口命令 |
Paused | 进程被冻结 | 谁执行pause、是否符合预期 |
Dead | daemon无法正常清理 | runtime/挂载/宿主机问题 |
退出码的常见换算是 128 + signal,所以 137 常对应 SIGKILL 9,143 常对应 SIGTERM 15。但退出码只能说明进程退出方式,不能单独证明是谁发送信号。
3.3 过滤和格式化
docker ps -a --filter "name=order-api"
docker ps -a --filter "status=exited"
docker ps --filter "label=app=order-api"
docker ps -a --filter "ancestor=order-api:1.4.2"避免默认输出截断:
docker ps -a --no-trunc适合巡检的格式:
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"脚本中不要解析默认表格文本,优先使用 --format 或 Engine API。
四、docker inspect 数据结构怎么读
docker inspect <container>输出是 JSON 数组,核心区域:
| 区域 | 内容 |
|---|---|
.Id、.Created、.Name | 容器身份 |
.State | 当前运行、退出、健康、PID、错误 |
.Config | 镜像内和创建时的命令、环境、标签、工作目录 |
.HostConfig | 端口绑定、Volume、资源限制、重启策略、日志驱动 |
.NetworkSettings | 网络、IP、端口结果 |
.Mounts | 最终生效的 bind/volume/tmpfs 挂载 |
.Image | 容器使用的镜像内容ID |
4.1 状态检查
Linux/macOS/PowerShell 都可使用单引号包住 Go Template:
docker inspect order-api --format '{{json .State}}'更适合事故记录:
docker inspect order-api --format 'Status={{.State.Status}} Running={{.State.Running}} Restarting={{.State.Restarting}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Pid={{.State.Pid}} StartedAt={{.State.StartedAt}} FinishedAt={{.State.FinishedAt}} Error={{.State.Error}}'输出示例:
Status=exited Running=false Restarting=false OOMKilled=true ExitCode=137 Pid=0 StartedAt=2026-07-15T01:15:00Z FinishedAt=2026-07-15T01:37:20Z Error=可得出:容器主进程已退出;Docker 记录由 OOM Killer 导致;退出方式是 137;原容器不能再 exec。还不能得出“Java Heap泄漏”,必须继续核算容器总内存。
4.2 启动时间与创建时间不同
docker inspect order-api --format 'Created={{.Created}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}} RestartCount={{.RestartCount}}'同一个容器可以 stop/start 多次,.Created 不变,.State.StartedAt 更新。故障时间线应使用 StartedAt/FinishedAt、日志时间和 events,而不是只看 CREATED 列。
4.3 宿主机PID和容器PID
docker inspect order-api --format '{{.State.Pid}}'这通常是宿主机 PID 命名空间中的容器主进程 PID。容器内部它可能显示为 PID 1:
docker exec order-api sh -c 'ps -ef'Java诊断工具要明确在哪个PID命名空间执行。容器内常用 jcmd 1 ...,宿主机看到的PID可能完全不同。
五、容器真正执行了什么命令
镜像的 Entrypoint 和 Cmd、docker run 参数、Compose 覆盖会共同决定最终命令。
docker inspect order-api --format 'Path={{.Path}} Args={{json .Args}}'查看声明:
docker inspect order-api --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}} WorkingDir={{.Config.WorkingDir}} User={{.Config.User}}'例如:
Entrypoint=["java","-jar","/app/app.jar"]
Cmd=["--spring.profiles.active=prod"]最终近似:
java -jar /app/app.jar --spring.profiles.active=prodShell form 与 exec form 影响信号:
# shell form,PID 1可能是 /bin/sh
ENTRYPOINT java -jar app.jar
# exec form,Java直接成为PID 1
ENTRYPOINT ["java", "-jar", "app.jar"]生产更推荐 exec form,让 SIGTERM 直接到达 Java,便于 Spring Boot 优雅关闭。若 shell 没有 exec 转发,可能出现 docker stop 超时后被 SIGKILL,最终表现为退出码 137,但并非 OOM。
六、环境变量与敏感信息
查看配置环境变量:
docker inspect order-api --format '{{range .Config.Env}}{{println .}}{{end}}'运行中容器:
docker exec order-api printenv
docker exec order-api printenv SPRING_PROFILES_ACTIVE环境变量可能包含数据库密码、Token、AK/SK。不要把完整 docker inspect 输出直接贴到公开工单,因为 Config.Env 可能泄密。生产优先使用 Secret 管理、最小权限和日志脱敏。
同名变量的最终值可能来自 Dockerfile ENV、Compose env_file/environment、docker run -e。查看最终 .Config.Env,再确认应用是否实际读取该名称。
七、端口映射从哪里看
docker port order-api示例:
8080/tcp -> 0.0.0.0:18080
8080/tcp -> [::]:18080表示访问宿主机 18080 转发到容器 8080。查看最终设置:
docker inspect order-api --format '{{json .NetworkSettings.Ports}}'
docker inspect order-api --format '{{json .HostConfig.PortBindings}}'区别:
- Dockerfile
EXPOSE 8080只是镜像元数据,不自动发布宿主机端口。 docker run -p 18080:8080才创建发布规则。- 应用必须在容器内监听
0.0.0.0:8080;如果只监听127.0.0.1,宿主机映射也可能访问不到。
排查顺序:
容器是否运行
→ 应用进程是否存在
→ 容器内端口是否监听
→ 容器内localhost是否可访问
→ PortBindings是否正确
→ 宿主机端口是否监听/冲突
→ 防火墙、安全组、反向代理容器内命令取决于镜像是否包含工具:
docker exec order-api sh -c 'curl -f http://127.0.0.1:8080/actuator/health'
docker exec order-api sh -c 'ss -lntp || netstat -lntp'极简镜像没有 curl/ss 不代表服务异常,应使用批准的调试容器或宿主机网络工具,不要现场随意安装并改变容器。
八、网络、DNS和容器互访
查看容器加入的网络:
docker inspect order-api --format '{{range $name, $network := .NetworkSettings.Networks}}{{println $name $network.IPAddress $network.Gateway}}{{end}}'查看网络:
docker network ls
docker network inspect app-net用户自定义 bridge 网络通常提供容器名 DNS:
order-api访问redis:6379容器中的 localhost 永远指向当前容器自身,不是宿主机,也不是另一个 MySQL 容器。Compose 中应使用服务名:
jdbc:mysql://mysql:3306/order网络问题检查:
docker exec order-api getent hosts mysql
docker exec order-api sh -c 'nc -vz mysql 3306'需要区分 DNS 解析失败、TCP 拒绝、超时、防火墙和应用认证失败。ping 失败不一定代表 TCP 不通,目标可能禁止 ICMP。
九、挂载和数据从哪里来
docker inspect order-api --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "RW=" .RW}}{{end}}'| 类型 | 来源 | 适用 |
|---|---|---|
| bind | 明确宿主机路径 | 配置、开发目录、日志目录 |
| volume | Docker管理目录 | 数据持久化、迁移与备份 |
| tmpfs | 内存文件系统 | 临时敏感/高速数据,重启丢失 |
挂载会覆盖镜像目标目录中的原内容。例如镜像 /app/config 已有默认文件,bind 一个空宿主机目录到 /app/config 后,容器看到的是空目录,不是镜像文件“被删除”。
排查配置不生效:
- inspect确认 Source/Destination。
- 检查宿主机 Source 是否是预期文件。
- 检查读写、SELinux、UID/GID权限。
- 容器内只读查看目标内容。
- 确认应用真正读取该路径和 profile。
数据卷:
docker volume ls
docker volume inspect mysql-data删除容器默认不会删除命名卷;docker compose down -v 会删除 Compose 相关卷,生产执行前必须确认备份和目标。
十、资源限制与实时使用
10.1 实时指标
docker stats
docker stats order-api
docker stats --no-stream order-apidocker stats order-api --no-stream --format 'Name={{.Name}} CPU={{.CPUPerc}} Memory={{.MemUsage}} MemoryPercent={{.MemPerc}} Net={{.NetIO}} Block={{.BlockIO}} PIDs={{.PIDs}}'| 指标 | 观察方向 |
|---|---|
| CPU % | 持续高、限额节流、核数口径 |
| MEM USAGE/LIMIT | 当前使用与cgroup上限 |
| NET I/O | 累计收发,不是瞬时带宽 |
| BLOCK I/O | 累计块设备读写 |
| PIDS | 容器task数量,Java线程会影响 |
docker stats 是当前值,无法证明故障时历史峰值。生产需要 cAdvisor/Prometheus/云监控保存时间序列。
10.2 创建时资源限制
docker inspect order-api --format 'Memory={{.HostConfig.Memory}} MemoryReservation={{.HostConfig.MemoryReservation}} MemorySwap={{.HostConfig.MemorySwap}} NanoCpus={{.HostConfig.NanoCpus}} CpuQuota={{.HostConfig.CpuQuota}} CpuPeriod={{.HostConfig.CpuPeriod}} Cpuset={{.HostConfig.CpusetCpus}} PidsLimit={{.HostConfig.PidsLimit}}'这些数字常以字节、纳秒CPU或quota/period表达,需要换算。内存为 0 通常表示没有通过该字段设置显式上限,不代表宿主机无限资源。
CPU限制可能让应用出现高延迟但宿主机CPU未满,需观察 cgroup throttling。线程创建失败还要看 PidsLimit、宿主机限制、-Xss 和内存。
十一、容器日志从哪里来
docker logs order-api
docker logs -f --tail 200 order-api
docker logs --since 30m --timestamps order-api
docker logs --since "2026-07-15T10:00:00+08:00" --until "2026-07-15T10:30:00+08:00" --timestamps order-apidocker logs 读取 Docker 配置的日志驱动所接收的 stdout/stderr。Java 如果写 /app/logs/app.log 而不输出控制台,docker logs 不会自动读取该文件。
查看日志驱动:
docker inspect order-api --format 'LogDriver={{.HostConfig.LogConfig.Type}} LogOptions={{json .HostConfig.LogConfig.Config}}'json-file 未配置轮转可能打满 /var/lib/docker。常见 daemon 或容器配置需要设置 max-size、max-file,修改后通常只影响新建容器,需验证实际环境。
容器文件日志检查挂载后,从宿主机或受控日志系统读取;不建议把“进入生产容器 tail 文件”当长期日志方案。
PowerShell 保存日志:
docker logs --timestamps order-api *> .\order-api.logLinux:
docker logs --timestamps order-api > order-api.log 2>&1十二、top、exec、cp分别解决什么
12.1 查看进程
docker top order-api
docker top order-api -eo pid,ppid,user,%cpu,%mem,etime,argsdocker top 通过 daemon/宿主机查看容器进程,不依赖容器镜像中安装 ps,适合极简镜像。
12.2 执行只读诊断
docker exec order-api printenv SPRING_PROFILES_ACTIVE
docker exec order-api sh -c 'ls -lah /app && id'
docker exec -it order-api shdocker exec 创建新进程,不是“登录一台虚拟机”。容器主进程退出后 exec 不能使用。生产中不要手工改应用文件,重建后会丢失且破坏不可变交付。
12.3 复制诊断文件
docker cp order-api:/data/dump/app.hprof ./app.hprof
docker cp order-api:/app/logs/app.log ./app.logHeap dump 含密码、Token、业务数据,需要访问控制、脱敏和安全传输。复制前确认磁盘空间,避免宿主机也被大文件写满。
十三、健康检查与“假存活”
docker inspect order-api --format 'Health={{if .State.Health}}{{.State.Health.Status}}{{else}}not-configured{{end}}'查看历史:
docker inspect order-api --format '{{if .State.Health}}{{range .State.Health.Log}}{{println .Start "exit=" .ExitCode .Output}}{{end}}{{end}}'状态:starting、healthy、unhealthy。Running=true 只表示容器主进程存在;应用可能线程池耗尽、Full GC 或依赖不可用,属于假存活。健康检查应快速、稳定、有超时,不能每秒执行重 SQL。
Docker healthcheck 本身不会必然重启 standalone 容器;是否重启取决于外部编排和策略。不要把健康检查与 restart policy 混为一谈。
十四、重启策略和反复重启
docker inspect order-api --format '{{json .HostConfig.RestartPolicy}}'| 策略 | 行为 |
|---|---|
| no | 不自动重启 |
| on-failure | 非零退出按配置重试 |
| always | daemon重启后也尝试恢复 |
| unless-stopped | 手工停止后不自动恢复 |
反复重启时日志可能很快滚动,应立即保存:
docker ps -a --no-trunc
docker inspect order-api > order-api-inspect.json
docker logs --timestamps order-api > order-api.log 2>&1
docker events --since 30m --filter container=order-api再判断启动命令、配置、依赖、端口冲突、权限、资源或应用异常。不要只把 restart 从 always 改成 no 后认为故障解决。
十五、Docker events还原时间线
docker events --since 1h --filter container=order-api按事件过滤:
docker events --since 1h \
--filter type=container \
--filter event=oom \
--filter event=die \
--filter event=restartevents 是流式接口,历史保留受 daemon 实现和时间范围影响。它能帮助回答:容器何时 start、health_status、oom、die、restart、stop、kill,以及谁的操作时间与故障吻合。
十六、镜像身份不能只看tag
docker inspect order-api --format 'ConfiguredImage={{.Config.Image}} ImageID={{.Image}}'
docker image inspect order-api:1.4.2 --format 'Id={{.Id}} RepoDigests={{json .RepoDigests}} Created={{.Created}}'Tag 是可变标签,同名 tag 可能被重新推送;容器的 .Image 是创建时镜像内容ID。生产发布记录应保存不可变 digest、构建号和 Git commit,不能只说“线上跑 latest”。
docker history --no-trunc order-api:1.4.2用于分析镜像层和体积,但 history 可能暴露构建命令,不能把密码作为 Dockerfile ARG/RUN 写进层。后续 rm 不会从旧层彻底删除秘密。
十七、Docker Compose信息检查
docker compose ps
docker compose logs -f --tail 200 order-api
docker compose top
docker compose config
docker compose imagesdocker compose config 展示变量替换和多个 Compose 文件合并后的模型,适合检查端口、环境、Volume、Network、healthcheck、restart。输出可能含敏感变量,分享前脱敏。
Compose 容器通常带标签:
docker ps --filter "label=com.docker.compose.project=myproject"可复现实验:创建、观察、制造故障与清理
下面的实验把前面的命令串成一个闭环。它使用 Nginx 只是为了得到一个长期运行、能发布端口并能输出日志的容器;重点不是学习 Nginx。
执行前先运行
docker version,确认 Client 能连接 Server。若镜像尚不存在,第一次运行会访问镜像仓库。生产服务器不要直接照抄实验端口、环境变量和容器名。
第一步:创建带完整观察项的实验容器
Linux/macOS:
docker run -d \
--name container-inspection-lab \
--label app=inspection-lab \
--env APP_ENV=learning \
--memory 128m \
--pids-limit 100 \
--restart on-failure:2 \
--health-cmd='wget -q -O- http://127.0.0.1/ >/dev/null || exit 1' \
--health-interval=5s \
--health-timeout=2s \
--health-retries=3 \
-p 18080:80 \
nginx:1.27-alpinePowerShell:
docker run -d `
--name container-inspection-lab `
--label app=inspection-lab `
--env APP_ENV=learning `
--memory 128m `
--pids-limit 100 `
--restart on-failure:2 `
--health-cmd="wget -q -O- http://127.0.0.1/ > /dev/null || exit 1" `
--health-interval=5s `
--health-timeout=2s `
--health-retries=3 `
-p 18080:80 `
nginx:1.27-alpine这里故意设置了 label、普通环境变量、内存、PID 数量、重启策略、健康检查和端口映射,让 inspect 的各个区域都有可观察内容。真实密码不要放在 --env 中做实验截图。
第二步:先用摘要定位,再读完整模型
docker ps -a --filter "name=container-inspection-lab" --no-trunc
docker inspect container-inspection-lab --format 'Status={{.State.Status}} Health={{if .State.Health}}{{.State.Health.Status}}{{else}}not-configured{{end}} Image={{.Config.Image}} Memory={{.HostConfig.Memory}} Pids={{.HostConfig.PidsLimit}} Restart={{json .HostConfig.RestartPolicy}}'预期能看到:
- 状态从
starting变成healthy;健康检查是异步周期执行的,不保证容器创建瞬间就是 healthy。 - Memory 为
134217728,因为 128 MiB 等于128 × 1024 × 1024字节。 - PidsLimit 为 100。
- 重启策略为
on-failure,最大重试次数为 2。
继续把同一创建配置映射到不同区域:
docker inspect container-inspection-lab --format '{{json .Config.Labels}}'
docker inspect container-inspection-lab --format '{{range .Config.Env}}{{println .}}{{end}}'
docker inspect container-inspection-lab --format '{{json .NetworkSettings.Ports}}'
docker inspect container-inspection-lab --format '{{json .State.Health}}'
docker port container-inspection-lab第三步:验证“Running不等于接口一定可用”
先验证正常链路:
curl -i http://127.0.0.1:18080/
docker logs --tail 20 --timestamps container-inspection-lab
docker stats --no-stream container-inspection-lab
docker top container-inspection-lab请求经过“宿主机 18080 → Docker 发布规则 → 容器 80 → Nginx”,访问日志由 Nginx 写 stdout 后进入 Docker 日志驱动,因此能被 docker logs 读取。
然后暂停容器:
docker pause container-inspection-lab
docker ps -a --filter "name=container-inspection-lab"
curl --max-time 2 http://127.0.0.1:18080/此时容器进程没有退出,只是 cgroup freezer 冻结了任务,状态会变成 Paused,请求通常超时。这个实验说明“进程对象存在”和“服务能及时响应”不是同一个结论。
恢复:
docker unpause container-inspection-lab
curl -i http://127.0.0.1:18080/第四步:观察 Exited 状态下哪些命令还能用
docker stop container-inspection-lab
docker ps -a --filter "name=container-inspection-lab"
docker inspect container-inspection-lab --format 'Status={{.State.Status}} ExitCode={{.State.ExitCode}} Pid={{.State.Pid}} Finished={{.State.FinishedAt}}'
docker logs --tail 20 container-inspection-lab
docker stats --no-stream container-inspection-lab
docker exec container-inspection-lab sh -c 'echo hello'预期:
ps -a、inspect、logs仍能使用,因为容器对象和日志仍在。- PID 通常变为 0,
exec会失败,因为已经没有可加入的运行中容器进程。 stats不再能提供该容器的正常实时进程统计。
重新启动后,容器对象 ID 不变,但主进程 PID 和 StartedAt 会变化:
docker start container-inspection-lab
docker inspect container-inspection-lab --format 'Id={{.Id}} Pid={{.State.Pid}} Started={{.State.StartedAt}} RestartCount={{.RestartCount}}'第五步:安全清理
docker rm -f container-inspection-lab
docker ps -a --filter "name=container-inspection-lab"清理后 ps -a 不应再返回该容器,inspect 也会提示对象不存在。镜像默认仍保留,这是容器对象生命周期和镜像生命周期彼此独立的证据。
实验验收题
完成后应能不用背答案解释:
- 为什么容器停止后还能 inspect,却不能 exec?
- 为什么 Memory 显示为 134217728 而不是
128m? - 为什么
EXPOSE 80不能替代-p 18080:80? - 为什么 pause 后进程没有退出,请求却会超时?
- 为什么 start 后容器 ID 没变、PID 却变了?
- 为什么删除容器后不再能从 daemon 取回 inspect 现场?
十八、Java容器OOM完整案例
18.1 现象
订单服务突然重启,业务日志没有明显 Java OOM。
18.2 定位容器
docker ps -a --filter "name=order-api" --no-trunc18.3 检查死亡方式
docker inspect order-api --format 'Status={{.State.Status}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}'输出:
Status=exited OOMKilled=true ExitCode=137结论只能是容器总内存超限,被 OOM Killer 杀死。不能直接说 Heap 泄漏。
18.4 查看限制和启动参数
docker inspect order-api --format 'Memory={{.HostConfig.Memory}} PidsLimit={{.HostConfig.PidsLimit}} Path={{.Path}} Args={{json .Args}}'假设:
容器limit = 1 GiB
-Xmx = 900 MiB
-Xss = 1 MiB
线程数 = 200进程预算至少包含:
Java Heap 900 MiB
+ Metaspace
+ Direct Memory
+ 线程栈约 200 × Xss
+ Code Cache
+ GC/JVM本地结构
+ JNI/mmap明显可能超过 1 GiB。堆没有达到 Xmx 前,内核就可能按容器总内存杀死进程,所以没有 Java OOM 栈和 heap dump。
18.5 对齐日志和事件
docker logs --since 1h --timestamps order-api > order-api.log 2>&1
docker events --since 1h --filter container=order-api如果进程仍存活,才执行:
docker stats --no-stream order-api
docker top order-api
docker exec order-api jcmd -l
docker exec order-api jcmd 1 VM.flags
docker exec order-api jcmd 1 VM.native_memory summaryNMT 必须启动时开启;极简JRE镜像可能没有 jcmd。不要在已经退出的新容器上分析并声称是旧容器现场。
18.6 修复
- 重新核算容器内存,给堆外和线程栈安全余量。
- 限制线程池、直接内存、Metaspace并监控。
- 开启 OOM dump 与 GC 日志,持久化到受控挂载。
- 保存容器 RSS、JVM Heap/Non-Heap/Direct、线程数历史。
- 验证是容量不足、泄漏、峰值还是无界队列,不只调大 limit。
十九、服务访问不到的逐层排查
flowchart TD
A["客户端访问失败"] --> B["docker ps -a检查状态"]
B --> C["inspect检查health、命令和端口"]
C --> D["logs检查应用启动"]
D --> E["top确认主进程"]
E --> F["容器内访问localhost端口"]
F --> G["检查PortBindings"]
G --> H["检查宿主机监听、防火墙"]
H --> I["检查Nginx/DNS/安全组"]
I --> J["检查下游依赖"]每一步都要记录实际输出。比如“端口映射正确”不能只看 Dockerfile EXPOSE,要提供 docker port 和应用监听证据。
二十、常见误区
| 误区 | 为什么错 |
|---|---|
docker ps 没有就是未创建 | 退出容器需要 docker ps -a |
| Exit 137 一定是OOM | 也可能被手动/超时SIGKILL,查OOMKilled和events |
| Running就是服务健康 | 进程可存活但接口、线程池、依赖已失效 |
| EXPOSE就是端口映射 | EXPOSE是元数据,发布要 -p/ports |
| 容器IP是稳定地址 | 重建可能变化,使用用户网络DNS/编排Service |
| docker logs能看所有文件日志 | 只读取日志驱动接收的stdout/stderr |
| exec修改配置可以长期生效 | 重建丢失,破坏不可变交付 |
| stats能证明昨天峰值 | stats是当前值,历史靠监控 |
| tag证明镜像版本 | tag可变,使用image ID/digest和commit |
| inspect输出可以直接分享 | Env、Mounts、Labels可能包含敏感信息 |
二十一、最小诊断命令集
# 1. 定位和状态
docker ps -a --no-trunc --filter "name=order-api"
# 2. 死亡方式与时间
docker inspect order-api --format 'Status={{.State.Status}} Running={{.State.Running}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}} Error={{.State.Error}}'
# 3. 启动命令和镜像身份
docker inspect order-api --format 'Image={{.Config.Image}} ImageID={{.Image}} Path={{.Path}} Args={{json .Args}}'
# 4. 日志
docker logs --tail 200 --timestamps order-api
# 5. 当前资源与进程
docker stats --no-stream order-api
docker top order-api
# 6. 端口、网络、挂载
docker port order-api
docker inspect order-api --format '{{json .NetworkSettings.Networks}}'
docker inspect order-api --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "RW=" .RW}}{{end}}'
# 7. 健康与重启策略
docker inspect order-api --format 'Health={{if .State.Health}}{{.State.Health.Status}}{{else}}not-configured{{end}} Restart={{json .HostConfig.RestartPolicy}}'
# 8. 时间线
docker events --since 1h --filter container=order-api二十二、面试标准回答
Docker怎么查看容器信息
我先用
docker ps -a定位容器并判断 running、exited 或 restarting,再用docker inspect查看 State、退出码、OOMKilled、启动时间、镜像ID、Entrypoint/Cmd、环境、端口、网络、挂载、资源限制和重启策略。运行中的容器用docker stats看实时资源、docker top看进程、docker exec做最小只读验证;日志用docker logs,但它只覆盖日志驱动收集的 stdout/stderr。容器退出时结合 logs、events 和宿主机监控还原时间线。退出码137还要看OOMKilled,不能直接认定Java Heap OOM。
docker inspect主要看哪几块
.State看运行、退出、PID、OOMKilled和健康;.Config看镜像声明、环境、Entrypoint/Cmd;.HostConfig看端口绑定、资源限制、日志驱动和重启策略;.NetworkSettings看网络和IP;.Mounts看最终挂载;.Image看实际镜像内容ID。生产分享inspect结果前要脱敏环境变量和标签。
docker logs为什么看不到应用日志
docker logs读取容器日志驱动接收的 stdout/stderr。如果应用只写容器内文件,Docker不会自动读取。应检查Logback配置和Mounts,生产更推荐输出标准流由统一日志系统采集,并配置日志轮转,避免Docker数据目录被写满。
退出码137一定是OOM吗
不一定。137通常表示进程收到SIGKILL,OOM Killer、手工
docker kill、停止超时后的强杀都可能产生。要结合docker inspect的OOMKilled、Docker events、宿主机内核日志和内存监控判断。即使 OOMKilled,也只是容器总内存超限,不等于一定是Java Heap泄漏。
