Skip to content

Docker 容器信息查看与生产诊断

docker ps 只能回答“Docker 当前知道哪些容器”,不能完整回答容器为什么退出、实际启动了什么命令、用了什么环境变量、挂载了什么目录、加入了哪个网络、是否被 OOM Killer 杀死。本页从零开始建立一套可直接执行的容器信息检查流程。

学习目标

学完后应能:

  1. 从容器名、ID、镜像、标签中定位目标容器。
  2. 区分 running、exited、restarting、paused、dead 和健康检查状态。
  3. docker inspect 中准确读取 State、Config、HostConfig、NetworkSettings、Mounts。
  4. 查看启动命令、环境变量、端口、网络、挂载、资源限制和重启策略。
  5. 区分 docker logs、容器内文件日志和 Docker 日志驱动。
  6. 使用 statstopexecevents 完成实时诊断。
  7. 判断退出码 137、OOMKilled=true、Java OOM 和容器总内存超限的区别。
  8. 把命令结果组织成“现象—证据—根因—修复”的事故结论。

一、先理解Docker命令在问谁

mermaid
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、镜像和日志驱动"]

当你执行:

bash
docker inspect order-service

CLI 不是进入容器读取一个配置文件,而是向 Docker daemon 查询该容器的创建配置、宿主机配置和当前状态。即使容器已经退出,只要没有被 docker rm 删除,很多元数据仍可查看。

docker exec 不同:它要求容器正在运行,并在容器现有命名空间中启动一个新进程。容器退出后不能再 exec,但仍然可以 inspectlogs

1.1 每条命令看到的信息来自哪里

刚开始学习 Docker 时,最容易把所有命令都理解成“进入容器查看”。实际上,不同命令读取的是完全不同的数据源:

命令主要数据来源容器退出后能否使用信息是否实时回答的问题
docker ps -aDocker daemon 保存的容器对象和状态摘要状态接近实时,配置是创建时快照有哪些容器、现在处于什么状态
docker inspectdaemon 保存的创建配置、HostConfig,加上运行时同步回来的 State配置是快照,State 是最近状态当初怎么创建、最后是什么状态
docker logs容器日志驱动保存或转发的 stdout/stderr通常能取决于日志驱动和保留策略主进程向标准流输出了什么
docker statsLinux cgroup 和网络/存储统计不能查看已退出进程的实时值实时快照当前 CPU、内存、PIDs 和 I/O 怎样
docker topdaemon 从宿主机进程视图筛选容器进程不能实时容器当前有哪些进程
docker execruntime 在既有 namespace/cgroup 中创建新进程不能实时容器内部此刻看到什么
docker eventsdaemon 产生的生命周期事件流不依赖容器仍运行事件流,历史能力有限什么时候 start、die、oom、restart

理解这个表后,就能解释两个常见现象:

  1. 容器已经退出,inspect 仍然能看到配置,因为容器对象还保存在 daemon 中;但 statsexec 不再有运行中的进程可观察。
  2. 容器已经被 docker rm 删除后,daemon 中的对象元数据也消失,不能指望重新执行 inspect 找回现场;只能依靠事先外送的日志、指标、事件、镜像清单和发布记录。

1.2 容器对象与容器进程不是同一个东西

mermaid
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 -vcompose down -v 等操作需要单独评估数据风险。

这也是为什么修改镜像、环境变量、端口或挂载后,通常需要受控 recreate,只执行 restart 不会让创建时配置发生变化。

二、收到问题后第一分钟做什么

mermaid
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["形成证据链并修复"]

先记录:

text
故障时间和时区
Docker宿主机
服务名称
容器名称或ID
镜像名称与tag/digest
是否刚发布、改配置或改资源限制

多台 Docker 主机中,docker ps 只查看当前 Docker context/daemon。排查前确认:

bash
docker context show
docker context ls
docker info

避免连接错环境后得出“容器不存在”的错误结论。

2.1 先保全现场,再决定是否重启

重启可能恢复服务,但会覆盖启动时间、改变 PID、滚动日志,并让“正在发生的资源问题”消失。删除容器造成的证据损失更大。生产事故中,在业务允许的时间内至少先保存:

bash
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

如果容器已经退出,statstop 失败是符合原理的,不表示 Docker 本身损坏。记录失败结果后继续保存 inspectlogs、events、宿主机监控和内核日志。

采集文件可能包含环境变量、挂载路径、业务数据和 Token,必须放在受控目录,上传工单前脱敏。不要为了“收集完整”而在公开聊天中粘贴整份 inspect。

三、docker ps 到底显示什么

3.1 运行中的容器

bash
docker ps

等价写法:

bash
docker container ls

典型输出:

text
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暴露/发布端口区分容器端口与宿主机端口
NAMESDocker容器名称推荐作为运维命令目标

docker ps 看不到已退出容器。因此“服务没了且 ps 没结果”时第一反应应是:

bash
docker ps -a

而不是重新安装 Docker。

3.2 所有容器

bash
docker ps -a

常见状态:

STATUS含义第一检查项
Up 2 hours主进程仍运行health、stats、应用接口
Exited (0)主进程正常退出是否本来就应常驻
Exited (1)应用以通用失败码退出logs、State.Error
Exited (137)通常收到 SIGKILLOOMKilled、events、宿主机日志
Exited (143)通常收到 SIGTERM 后退出谁发出stop、优雅关闭是否完成
Restarting重启策略反复拉起启动日志、命令、依赖和健康
Created创建但主进程未成功运行Engine错误、入口命令
Paused进程被冻结谁执行pause、是否符合预期
Deaddaemon无法正常清理runtime/挂载/宿主机问题

退出码的常见换算是 128 + signal,所以 137 常对应 SIGKILL 9,143 常对应 SIGTERM 15。但退出码只能说明进程退出方式,不能单独证明是谁发送信号。

3.3 过滤和格式化

bash
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"

避免默认输出截断:

bash
docker ps -a --no-trunc

适合巡检的格式:

bash
docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"

脚本中不要解析默认表格文本,优先使用 --format 或 Engine API。

四、docker inspect 数据结构怎么读

bash
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:

bash
docker inspect order-api --format '{{json .State}}'

更适合事故记录:

bash
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}}'

输出示例:

text
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 启动时间与创建时间不同

bash
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

bash
docker inspect order-api --format '{{.State.Pid}}'

这通常是宿主机 PID 命名空间中的容器主进程 PID。容器内部它可能显示为 PID 1:

bash
docker exec order-api sh -c 'ps -ef'

Java诊断工具要明确在哪个PID命名空间执行。容器内常用 jcmd 1 ...,宿主机看到的PID可能完全不同。

五、容器真正执行了什么命令

镜像的 Entrypoint 和 Cmd、docker run 参数、Compose 覆盖会共同决定最终命令。

bash
docker inspect order-api --format 'Path={{.Path}} Args={{json .Args}}'

查看声明:

bash
docker inspect order-api --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}} WorkingDir={{.Config.WorkingDir}} User={{.Config.User}}'

例如:

text
Entrypoint=["java","-jar","/app/app.jar"]
Cmd=["--spring.profiles.active=prod"]

最终近似:

bash
java -jar /app/app.jar --spring.profiles.active=prod

Shell form 与 exec form 影响信号:

dockerfile
# 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。

六、环境变量与敏感信息

查看配置环境变量:

bash
docker inspect order-api --format '{{range .Config.Env}}{{println .}}{{end}}'

运行中容器:

bash
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,再确认应用是否实际读取该名称。

七、端口映射从哪里看

bash
docker port order-api

示例:

text
8080/tcp -> 0.0.0.0:18080
8080/tcp -> [::]:18080

表示访问宿主机 18080 转发到容器 8080。查看最终设置:

bash
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,宿主机映射也可能访问不到。

排查顺序:

text
容器是否运行
→ 应用进程是否存在
→ 容器内端口是否监听
→ 容器内localhost是否可访问
→ PortBindings是否正确
→ 宿主机端口是否监听/冲突
→ 防火墙、安全组、反向代理

容器内命令取决于镜像是否包含工具:

bash
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和容器互访

查看容器加入的网络:

bash
docker inspect order-api --format '{{range $name, $network := .NetworkSettings.Networks}}{{println $name $network.IPAddress $network.Gateway}}{{end}}'

查看网络:

bash
docker network ls
docker network inspect app-net

用户自定义 bridge 网络通常提供容器名 DNS:

text
order-api访问redis:6379

容器中的 localhost 永远指向当前容器自身,不是宿主机,也不是另一个 MySQL 容器。Compose 中应使用服务名:

text
jdbc:mysql://mysql:3306/order

网络问题检查:

bash
docker exec order-api getent hosts mysql
docker exec order-api sh -c 'nc -vz mysql 3306'

需要区分 DNS 解析失败、TCP 拒绝、超时、防火墙和应用认证失败。ping 失败不一定代表 TCP 不通,目标可能禁止 ICMP。

九、挂载和数据从哪里来

bash
docker inspect order-api --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "RW=" .RW}}{{end}}'
类型来源适用
bind明确宿主机路径配置、开发目录、日志目录
volumeDocker管理目录数据持久化、迁移与备份
tmpfs内存文件系统临时敏感/高速数据,重启丢失

挂载会覆盖镜像目标目录中的原内容。例如镜像 /app/config 已有默认文件,bind 一个空宿主机目录到 /app/config 后,容器看到的是空目录,不是镜像文件“被删除”。

排查配置不生效:

  1. inspect确认 Source/Destination。
  2. 检查宿主机 Source 是否是预期文件。
  3. 检查读写、SELinux、UID/GID权限。
  4. 容器内只读查看目标内容。
  5. 确认应用真正读取该路径和 profile。

数据卷:

bash
docker volume ls
docker volume inspect mysql-data

删除容器默认不会删除命名卷;docker compose down -v 会删除 Compose 相关卷,生产执行前必须确认备份和目标。

十、资源限制与实时使用

10.1 实时指标

bash
docker stats
docker stats order-api
docker stats --no-stream order-api
bash
docker 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 创建时资源限制

bash
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 和内存。

十一、容器日志从哪里来

bash
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-api

docker logs 读取 Docker 配置的日志驱动所接收的 stdout/stderr。Java 如果写 /app/logs/app.log 而不输出控制台,docker logs 不会自动读取该文件。

查看日志驱动:

bash
docker inspect order-api --format 'LogDriver={{.HostConfig.LogConfig.Type}} LogOptions={{json .HostConfig.LogConfig.Config}}'

json-file 未配置轮转可能打满 /var/lib/docker。常见 daemon 或容器配置需要设置 max-sizemax-file,修改后通常只影响新建容器,需验证实际环境。

容器文件日志检查挂载后,从宿主机或受控日志系统读取;不建议把“进入生产容器 tail 文件”当长期日志方案。

PowerShell 保存日志:

powershell
docker logs --timestamps order-api *> .\order-api.log

Linux:

bash
docker logs --timestamps order-api > order-api.log 2>&1

十二、topexeccp分别解决什么

12.1 查看进程

bash
docker top order-api
docker top order-api -eo pid,ppid,user,%cpu,%mem,etime,args

docker top 通过 daemon/宿主机查看容器进程,不依赖容器镜像中安装 ps,适合极简镜像。

12.2 执行只读诊断

bash
docker exec order-api printenv SPRING_PROFILES_ACTIVE
docker exec order-api sh -c 'ls -lah /app && id'
docker exec -it order-api sh

docker exec 创建新进程,不是“登录一台虚拟机”。容器主进程退出后 exec 不能使用。生产中不要手工改应用文件,重建后会丢失且破坏不可变交付。

12.3 复制诊断文件

bash
docker cp order-api:/data/dump/app.hprof ./app.hprof
docker cp order-api:/app/logs/app.log ./app.log

Heap dump 含密码、Token、业务数据,需要访问控制、脱敏和安全传输。复制前确认磁盘空间,避免宿主机也被大文件写满。

十三、健康检查与“假存活”

bash
docker inspect order-api --format 'Health={{if .State.Health}}{{.State.Health.Status}}{{else}}not-configured{{end}}'

查看历史:

bash
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 混为一谈。

十四、重启策略和反复重启

bash
docker inspect order-api --format '{{json .HostConfig.RestartPolicy}}'
策略行为
no不自动重启
on-failure非零退出按配置重试
alwaysdaemon重启后也尝试恢复
unless-stopped手工停止后不自动恢复

反复重启时日志可能很快滚动,应立即保存:

bash
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还原时间线

bash
docker events --since 1h --filter container=order-api

按事件过滤:

bash
docker events --since 1h \
  --filter type=container \
  --filter event=oom \
  --filter event=die \
  --filter event=restart

events 是流式接口,历史保留受 daemon 实现和时间范围影响。它能帮助回答:容器何时 start、health_status、oom、die、restart、stop、kill,以及谁的操作时间与故障吻合。

十六、镜像身份不能只看tag

bash
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”。

bash
docker history --no-trunc order-api:1.4.2

用于分析镜像层和体积,但 history 可能暴露构建命令,不能把密码作为 Dockerfile ARG/RUN 写进层。后续 rm 不会从旧层彻底删除秘密。

十七、Docker Compose信息检查

bash
docker compose ps
docker compose logs -f --tail 200 order-api
docker compose top
docker compose config
docker compose images

docker compose config 展示变量替换和多个 Compose 文件合并后的模型,适合检查端口、环境、Volume、Network、healthcheck、restart。输出可能含敏感变量,分享前脱敏。

Compose 容器通常带标签:

bash
docker ps --filter "label=com.docker.compose.project=myproject"

可复现实验:创建、观察、制造故障与清理

下面的实验把前面的命令串成一个闭环。它使用 Nginx 只是为了得到一个长期运行、能发布端口并能输出日志的容器;重点不是学习 Nginx。

执行前先运行 docker version,确认 Client 能连接 Server。若镜像尚不存在,第一次运行会访问镜像仓库。生产服务器不要直接照抄实验端口、环境变量和容器名。

第一步:创建带完整观察项的实验容器

Linux/macOS:

bash
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

PowerShell:

powershell
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 中做实验截图。

第二步:先用摘要定位,再读完整模型

bash
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。

继续把同一创建配置映射到不同区域:

bash
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不等于接口一定可用”

先验证正常链路:

bash
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 读取。

然后暂停容器:

bash
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,请求通常超时。这个实验说明“进程对象存在”和“服务能及时响应”不是同一个结论。

恢复:

bash
docker unpause container-inspection-lab
curl -i http://127.0.0.1:18080/

第四步:观察 Exited 状态下哪些命令还能用

bash
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 -ainspectlogs 仍能使用,因为容器对象和日志仍在。
  • PID 通常变为 0,exec 会失败,因为已经没有可加入的运行中容器进程。
  • stats 不再能提供该容器的正常实时进程统计。

重新启动后,容器对象 ID 不变,但主进程 PID 和 StartedAt 会变化:

bash
docker start container-inspection-lab
docker inspect container-inspection-lab --format 'Id={{.Id}} Pid={{.State.Pid}} Started={{.State.StartedAt}} RestartCount={{.RestartCount}}'

第五步:安全清理

bash
docker rm -f container-inspection-lab
docker ps -a --filter "name=container-inspection-lab"

清理后 ps -a 不应再返回该容器,inspect 也会提示对象不存在。镜像默认仍保留,这是容器对象生命周期和镜像生命周期彼此独立的证据。

实验验收题

完成后应能不用背答案解释:

  1. 为什么容器停止后还能 inspect,却不能 exec?
  2. 为什么 Memory 显示为 134217728 而不是 128m
  3. 为什么 EXPOSE 80 不能替代 -p 18080:80
  4. 为什么 pause 后进程没有退出,请求却会超时?
  5. 为什么 start 后容器 ID 没变、PID 却变了?
  6. 为什么删除容器后不再能从 daemon 取回 inspect 现场?

十八、Java容器OOM完整案例

18.1 现象

订单服务突然重启,业务日志没有明显 Java OOM。

18.2 定位容器

bash
docker ps -a --filter "name=order-api" --no-trunc

18.3 检查死亡方式

bash
docker inspect order-api --format 'Status={{.State.Status}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}'

输出:

text
Status=exited OOMKilled=true ExitCode=137

结论只能是容器总内存超限,被 OOM Killer 杀死。不能直接说 Heap 泄漏。

18.4 查看限制和启动参数

bash
docker inspect order-api --format 'Memory={{.HostConfig.Memory}} PidsLimit={{.HostConfig.PidsLimit}} Path={{.Path}} Args={{json .Args}}'

假设:

text
容器limit = 1 GiB
-Xmx = 900 MiB
-Xss = 1 MiB
线程数 = 200

进程预算至少包含:

text
Java Heap 900 MiB
+ Metaspace
+ Direct Memory
+ 线程栈约 200 × Xss
+ Code Cache
+ GC/JVM本地结构
+ JNI/mmap

明显可能超过 1 GiB。堆没有达到 Xmx 前,内核就可能按容器总内存杀死进程,所以没有 Java OOM 栈和 heap dump。

18.5 对齐日志和事件

bash
docker logs --since 1h --timestamps order-api > order-api.log 2>&1
docker events --since 1h --filter container=order-api

如果进程仍存活,才执行:

bash
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 summary

NMT 必须启动时开启;极简JRE镜像可能没有 jcmd。不要在已经退出的新容器上分析并声称是旧容器现场。

18.6 修复

  • 重新核算容器内存,给堆外和线程栈安全余量。
  • 限制线程池、直接内存、Metaspace并监控。
  • 开启 OOM dump 与 GC 日志,持久化到受控挂载。
  • 保存容器 RSS、JVM Heap/Non-Heap/Direct、线程数历史。
  • 验证是容量不足、泄漏、峰值还是无界队列,不只调大 limit。

十九、服务访问不到的逐层排查

mermaid
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可能包含敏感信息

二十一、最小诊断命令集

bash
# 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 inspectOOMKilled、Docker events、宿主机内核日志和内存监控判断。即使 OOMKilled,也只是容器总内存超限,不等于一定是Java Heap泄漏。

关联知识点