第 4 篇:容器运行、生命周期与排障
0. 本篇定位
前三篇已经讲完了为什么需要容器化、镜像制品模型和 Dockerfile 写法。本篇开始进入容器真正运行后的世界:一个镜像启动成容器后,状态如何变化,主进程如何退出,日志从哪里看,运行参数怎么确认,端口为什么访问不到,内存限制为什么会杀掉进程,进入容器排障应该看什么,又不应该做什么。
这一篇的目标是建立单机 Docker 排障基本功。后面进入 Docker 网络、数据卷、Compose、Kubernetes 以后,问题会变得更复杂,但很多排障动作仍然来自这里:看状态、看退出码、看日志、看 inspect、看进程、看端口、看环境变量、看挂载、看资源限制。只要这套基本功不稳,到了 Kubernetes 里看到 CrashLoopBackOff、ImagePullBackOff、OOMKilled、Readiness probe failed 时就会更慌。
读完本篇后,你应该能做到三件事。第一,看到容器异常时不急着重启,而是先保存状态和证据。第二,能用一条清晰路径判断问题发生在启动命令、配置注入、端口监听、网络映射、资源限制、文件权限还是应用逻辑。第三,知道 docker exec 是观察工具,不是长期热修工具,不能把容器当成一台需要手工维护的服务器。
1. 问题背景
容器运行问题最容易制造错觉。容器退出了,有人说“Docker 不稳定”;端口访问不到,有人说“容器网络有问题”;日志里没内容,有人说“应用没打印”;进入容器发现缺工具,有人说“镜像做得不好”;容器被杀,有人说“Java 又 OOM 了”。这些判断可能对,也可能完全错。真正的问题是团队没有先建立证据链。
后端容器运行时,至少有六类状态要区分。第一,镜像是否能被找到和拉取。第二,容器是否被创建。第三,主进程是否启动。第四,应用是否监听端口。第五,端口是否被映射到宿主机或网络入口。第六,应用是否真的可用。docker ps 看到 Running,只能说明容器主进程还在,不代表业务健康;docker run 命令执行成功,只能说明创建和启动动作完成,不代表端口、配置、依赖和资源都正确。
本地 Docker 排障的价值不仅在本地。Kubernetes 的 Pod 里同样有容器状态、退出码、日志、环境变量、挂载、资源限制和探针。区别只是对象和命令变了。你在单机 Docker 里理解了这些证据,后面看 kubectl describe pod、kubectl logs、kubectl exec、事件和容器状态时会轻松很多。
这篇文章有意不急着进入 Compose 和 Kubernetes,因为单容器排障是所有复杂场景的地基。很多 Kubernetes 问题本质上仍然是:镜像启动命令错了、环境变量没注入、应用监听错端口、容器内没有权限写目录、内存限制太小、日志没有输出标准输出、主进程没有正确处理 SIGTERM。如果这些问题在单机 Docker 中还不能判断,到了集群里只会被更多对象遮住。
后端同学还要改变一个习惯:不要把“进入机器看一眼”的服务器思维直接搬到容器里。容器是可替换实例,不是长期维护对象。排障时可以进入容器观察,但修复必须回到镜像、配置、部署声明或应用代码。只要修复动作只能存在于某个正在运行的容器里,它就不是可靠修复,而是临时现场操作。
2. 核心概念
| 对象 | 负责什么 | 不负责什么 | 排障时先看什么 |
|---|---|---|---|
| container state | 表达 created、running、exited 等生命周期状态 | 不解释业务是否健康 | docker ps -a、docker inspect |
| exit code | 主进程退出后的数字结果 | 不提供完整上下文 | State.ExitCode、日志最后几行 |
| logs | stdout/stderr 输出 | 不自动采集应用写入文件的日志 | docker logs |
| inspect | 容器真实配置和状态元数据 | 不替代应用日志和业务指标 | 环境变量、挂载、端口、命令、状态 |
| exec | 在运行中容器里执行命令 | 不适合长期修补生产容器 | 观察进程、文件、网络、环境 |
| resources | CPU、内存、进程和 I/O 约束 | 不保证应用自动适配 | docker stats、退出原因、JVM 参数 |
2.1 状态、退出码、日志和 inspect 的分工
状态负责告诉你容器处于哪个生命周期阶段。created 表示容器已创建但未启动,running 表示主进程仍在,exited 表示主进程已经退出,restarting 表示重启策略正在拉起它,paused 表示进程被暂停。状态是排障入口,但不是结论。Running 的容器可能业务不可用,Exited 的容器也可能是任务正常完成。
退出码负责告诉你主进程如何结束。0 通常代表正常退出,非零通常代表异常,137 常见于被 SIGKILL 杀掉,可能和 OOM 或强制停止有关,143 常见于收到 SIGTERM 后退出。退出码不是完整诊断,它只是指向下一步证据。看到 137,要继续看内存限制和 OOM 记录;看到 1,要继续看应用启动日志;看到 0,要确认容器是不是本来就应该常驻。
日志负责给你应用视角。Docker 默认采集容器主进程的 stdout 和 stderr。如果 Spring Boot 仍然把日志写到容器内部文件,docker logs 可能看不到关键内容。容器化后推荐应用默认输出标准输出,文件日志只作为特殊需求处理。启动失败时,docker logs 往往比进入容器更有价值,因为容器可能已经退出,现场只剩最后日志。
inspect 负责给你运行时事实。很多问题不是 Dockerfile 写错,而是运行参数覆盖了默认值。docker inspect 能看到容器使用的镜像、命令、入口、环境变量、端口映射、挂载、网络、资源限制、退出码、启动时间和状态。排障时如果只看 Dockerfile,不看 inspect,就可能忽略实际运行参数。
这四类证据有先后关系。状态告诉你“现在在哪里”,退出码告诉你“主进程怎么结束”,日志告诉你“应用自己说了什么”,inspect 告诉你“运行时到底给了什么参数”。如果状态是 Exited,就先看退出码和日志;如果状态是 Running 但业务不可用,就看日志、端口、健康接口和依赖;如果日志和预期不一致,就看 inspect 里的环境变量、命令和挂载。不要把所有证据一次性摊开看,否则注意力会被噪音带跑。
退出码要结合信号理解。137 通常是 128 + 9,对应 SIGKILL,常见于 OOM 或强制 kill;143 通常是 128 + 15,对应 SIGTERM,常见于正常停止流程;126 可能是命令不可执行;127 常见于命令不存在。它们不是绝对诊断,但能帮你决定下一步看哪里。比如 127 更像 ENTRYPOINT 或路径问题,137 更像资源或强杀问题,143 需要结合是否是预期停止。
2.2 exec 是观察工具,不是热修入口
docker exec 很有用,但它只适用于运行中的容器。容器已经退出时,不能 exec 进去;镜像里没有 shell 或工具时,也无法用你习惯的方式观察。exec 适合确认环境变量、文件是否存在、端口是否监听、进程是否存在、DNS 是否可解析、配置是否挂载。它不适合进入容器改配置、装工具、改文件后当作正式修复。
把容器当服务器维护,是容器化最典型的反模式。你进入容器改了文件,改动只在当前容器可写层里;容器一重建就消失,CI、镜像仓库、发布记录也不会知道发生过什么。正确做法是把修复回到 Dockerfile、配置、镜像、部署声明或应用代码,然后重新构建和发布。
exec 还有一个边界:它看到的是某个实例的现场,不一定代表所有实例。如果同一个镜像启动了多个容器,其中一个容器的环境变量、挂载、网络或写入状态可能和另一个不同。生产排障不能只进入一个实例就下结论,要结合发布记录、配置版本、运行参数和多个实例状态。尤其是在滚动发布过程中,新旧版本可能同时存在,exec 进去前必须确认实例对应的镜像 digest 和配置。
2.3 resources 决定运行边界
资源限制决定容器能使用多少 CPU、内存和进程能力。它不直接告诉你业务是否健康,但会影响 JVM 堆大小、线程调度、GC、连接池、超时和 OOM 行为。排障时如果只看应用日志,不看资源边界,就容易把 OOMKilled、延迟抖动、CPU throttling 误判成纯业务代码问题。
3. 运行机制
3.1 docker run 背后的状态转换
docker run 背后不是单个动作,而是一串状态转换。运行时先解析镜像引用,如果本地没有镜像就尝试拉取;然后创建容器对象,合并镜像 config 和运行参数;准备文件系统、可写层、网络、端口映射、挂载、环境变量和资源限制;最后启动主进程。主进程退出后,容器进入 exited 状态,运行时保存退出码和部分状态。
这里的“合并”非常重要。镜像在 Dockerfile 中声明了默认 entrypoint、cmd、env、workdir、exposed ports,但 docker run 可以覆盖其中很多内容。实际运行容器时,最终命令可能不是 Dockerfile 里的命令,最终环境变量可能被 -e 覆盖,最终挂载可能让镜像内文件被宿主机目录遮住,最终端口映射也可能和 EXPOSE 完全不同。排障时必须看最终运行事实,而不是只看镜像声明。
容器主进程是生命周期的核心。只要主进程退出,容器就结束。很多初学者会在容器里启动后台进程,然后主命令立即结束,容器也随之退出。后端服务应该让 Java 进程以前台主进程运行。不要把容器启动脚本写成“启动服务到后台,然后脚本退出”。这类写法在传统服务器上常见,在容器里会直接破坏生命周期模型。
resolve image -> create container -> prepare filesystem / mount / network / env / resources -> start main process -> collect stdout and stderr -> wait process exit -> record exit code and state3.2 docker run 到容器退出的状态流
可以用伪代码理解运行过程:
function runContainer(imageRef, runtimeOptions): image = resolveLocalOrPull(imageRef) config = merge(image.config, runtimeOptions)
container = createContainer( rootfs = image.readOnlyLayers + newWritableLayer(), env = config.env, command = config.entrypoint + config.cmd, ports = config.ports, mounts = config.mounts, resources = config.resources )
process = startMainProcess(container) streamLogs(process.stdout, process.stderr)
exitCode = wait(process) container.state = "exited" container.exitCode = exitCode return container.state这个流程说明排障不能跳步。镜像解析失败,看仓库和 tag;容器创建失败,看运行参数、挂载和权限;进程启动失败,看 entrypoint、文件路径和环境变量;进程运行后异常退出,看日志、退出码和资源;服务能启动但访问不到,看监听地址、端口映射和网络。
3.3 排障顺序:先保留现场,再缩小范围
容器异常时,不要第一时间 docker rm 或重新 build。先执行 docker ps -a 看状态,再 docker logs <container> 保存日志,再 docker inspect <container> 保存运行参数和退出码。如果容器仍在运行,再用 docker exec 观察现场。如果怀疑资源问题,再看 docker stats 或宿主机事件。如果怀疑端口问题,再同时检查应用监听地址、容器端口和宿主机映射。
排障时可以按四个问题推进。第一,容器有没有成功创建和启动。第二,主进程有没有正常运行。第三,应用有没有按预期监听和读取配置。第四,外部流量有没有正确进入容器。每一步都对应不同证据,跳过其中任何一步都容易误判。
如果问题和端口有关,可以按“应用监听、容器端口、宿主机映射、外部访问”四层拆开。应用监听看日志或容器内 ss -lntp;容器端口看应用配置和 Dockerfile EXPOSE,但记住 EXPOSE 只是元数据;宿主机映射看 docker ps 的 PORTS 列;外部访问看防火墙、代理、浏览器访问地址和端口占用。很多“Docker 端口不通”最后都只是应用监听在 127.0.0.1、端口写反或宿主机端口被占。
如果问题和配置有关,可以按“注入、读取、覆盖、生效”四层拆开。注入看 docker inspect 的 Env 和挂载;读取看应用启动日志;覆盖看命令行参数、profile、配置文件、环境变量和配置中心优先级;生效看应用实际行为或健康接口。后端配置问题复杂,是因为配置来源很多,不是因为 Docker 本身神秘。
4. 最小可运行示例
4.1 启动一个带限制的后端容器
使用第 3 篇构建出的 backend-api:1.0.0,我们运行一个带资源限制、端口映射和环境变量的容器:
docker run -d \ --name backend-api-demo \ -p 8080:8080 \ -e SPRING_PROFILES_ACTIVE=local \ -e SERVER_PORT=8080 \ --memory=512m \ --cpus=1 \ backend-api:1.0.04.2 查看状态、日志、参数和资源
查看运行状态:
docker psdocker ps -a查看日志:
docker logs backend-api-demodocker logs --tail=100 -f backend-api-demo查看运行参数和状态:
docker inspect backend-api-demo查看资源使用:
docker stats backend-api-demo进入容器观察:
docker exec -it backend-api-demo sh4.3 停止、删除与故障模拟
停止容器:
docker stop backend-api-demo删除容器:
docker rm backend-api-demo这个示例覆盖了后端本地排障最常用的一组动作。注意 docker stop 默认会先发送 SIGTERM,等待一段时间后再 SIGKILL。Spring Boot 服务如果支持优雅停机,应该在这段时间内停止接新请求、处理完已有请求并释放资源。这个行为会在 Kubernetes 滚动发布中变得更重要。
如果想模拟启动失败,可以故意传入错误环境变量或错误 jar 路径,再观察退出码和日志。如果想模拟 OOM,可以降低 --memory 并触发大内存接口,再观察容器状态和应用日志。如果想模拟端口错误,可以把应用 SERVER_PORT 改成 9090 但仍映射 8080:8080,此时容器 Running 但访问失败。通过这些小实验,你会更快建立现象和证据之间的映射。
本地实验要注意清理现场。--rm 会在容器退出后自动删除容器,适合一次性实验,但不适合排查启动失败,因为退出后容器状态也没了。排障时可以先不加 --rm,保留 exited 容器,查看 logs 和 inspect;定位完成后再删除。这个细节很小,但对初学者很重要。
5. 配置逐行拆解
5.1 启动参数:后台、命名、端口和环境变量
-d 表示后台运行。后台运行后,终端不会直接显示应用日志,需要用 docker logs 查看。初学时如果容器立即退出,可以先不加 -d,让日志直接打印到终端,便于看到第一现场。
--name backend-api-demo 给容器一个稳定名字。名字方便排障,但同名容器不能重复创建。如果之前的容器还存在,即使已经 exited,再次使用同名也会失败。排障时不要看到创建失败就误以为镜像坏了,先 docker ps -a 看旧容器是否占名。
-p 8080:8080 是宿主机端口到容器端口的映射。左边是宿主机端口,右边是容器端口。应用必须在容器内监听右边端口,外部才能访问左边端口。如果应用只监听 127.0.0.1 而不是 0.0.0.0,容器外可能仍访问不到。端口问题要同时检查应用监听、容器端口和宿主机映射。
-e SPRING_PROFILES_ACTIVE=local 注入环境变量。它会覆盖或补充镜像默认环境。排障时可以通过 docker inspect 查看容器创建时的环境变量,也可以在运行中用 docker exec env 观察。注意应用可能读取配置文件、命令行参数、环境变量和配置中心,环境变量存在不代表最终配置一定生效。
5.2 资源和观察命令:memory、cpu、logs、inspect、exec
--memory=512m 设置内存限制。Java 服务看到的可用内存会受到这个限制影响,但堆、非堆、线程栈、直接内存都要共享这 512MB。容器被 OOMKilled 时,应用未必能打印 Java OOM,因为它可能直接被内核杀掉。排障要看容器状态、退出码、宿主机或 Docker 事件、JVM 参数和内存指标。
--cpus=1 限制 CPU。CPU 限制不会像内存那样直接杀进程,但会影响延迟、GC、线程调度和超时。后端服务压测时不能只看平均响应时间,还要看 CPU throttling、p95/p99 延迟、GC pause 和下游超时。
docker logs 读取 stdout/stderr。它不保证读取应用写入容器内部文件的日志。容器化后应用日志应优先输出标准输出,统一由运行时或采集器接管。
docker inspect 是确认事实的工具。它能看到运行时真正使用的命令、环境变量、挂载、网络、端口、资源、状态和退出码。很多时候 inspect 比猜测 Dockerfile 更可靠,因为运行参数可能覆盖镜像默认值。
docker exec 只能进入运行中的容器。它适合观察,不适合修复。进入容器后可以看 ps、env、ls、cat、netstat 或 ss,但是否有这些工具取决于镜像内容。生产镜像很精简时,缺少工具是正常取舍,不一定是错误。
docker stop 和 docker kill 也要区分。stop 给进程优雅退出机会,先发 SIGTERM,再等待超时后发 SIGKILL;kill 更直接,通常立即发送 SIGKILL。后端服务在发布下线、重启和缩容时,应该尽量走优雅路径。你可以通过日志观察 Spring Boot 是否收到了关闭信号,是否完成了 graceful shutdown。如果没有任何停机日志,就要回头检查 ENTRYPOINT 和应用配置。
docker restart 是排障中很容易被滥用的命令。它能让服务恢复,但也会抹掉一些现场线索,比如第一次失败的时间、内存峰值、临时文件状态。生产环境中,重启前最好先保存日志、inspect、资源指标和最近变更记录。恢复服务当然重要,但没有证据的恢复会让问题反复出现。
5.3 端口、环境变量和资源限制要组合判断
很多运行问题来自组合误判。端口映射正确,但应用监听了错误端口;环境变量注入了,但 Spring profile 没按预期激活;内存限制设置了,但 JVM 堆比例过高;容器 Running,但健康接口依赖数据库而数据库不可达;日志系统没采集到内容,但应用其实写在文件里。
因此排障时不要只看单个字段。访问不到服务时,至少确认容器是否 Running、应用日志是否显示启动成功、应用监听哪个端口、-p 映射是否正确、宿主机端口是否被占用、防火墙是否阻断。OOM 时,至少确认 memory limit、JVM 参数、容器退出码、应用日志和流量峰值。配置未生效时,至少确认环境变量、配置文件、启动参数和应用配置优先级。
Java 服务还要特别关注容器可见资源。现代 JVM 能感知容器限制,但这不代表你可以忽略资源设置。--memory=512m 下,堆、非堆、线程栈和直接内存都要共享内存;--cpus=1 下,GC 线程、业务线程和 Netty/连接池线程都会受到调度影响。容器被 OOMKilled 时,Java 进程可能没有机会打印堆转储;CPU 被限制时,服务可能不是报错,而是延迟升高和超时增加。
日志采集也要组合判断。docker logs 有内容,说明 stdout/stderr 有输出;没有内容,不代表应用没有日志,可能是应用写文件、日志级别过高、启动命令没执行到应用、或者容器根本没启动成功。生产中更进一步,还要确认日志采集器是否采集、是否带版本和 trace id、是否能按容器实例关联。
6. 后端工程场景
6.1 本地、CI、测试与生产的排障入口
本地开发阶段,容器运行排障能帮助新人快速定位“为什么我启动不起来”。常见问题是端口占用、环境变量缺失、依赖服务地址写错、容器名冲突、旧容器未删除。此时重点是看 docker ps -a、docker logs 和端口映射。
CI 阶段,容器运行排障常用于 smoke test。镜像构建后,CI 可以启动容器,注入最小配置,等待健康接口返回。如果启动失败,CI 保存日志和 inspect 输出。这样 Dockerfile、ENTRYPOINT、权限、缺文件、缺证书等问题能更早暴露。
测试环境阶段,排障重点是配置和依赖。容器能启动不代表依赖可用。测试同学可以通过日志确认 profile、数据库地址、Redis 地址、端口和版本信息,通过 inspect 确认环境变量和镜像 digest。这样能减少“我以为部署了新版本”的误判。
生产阶段,单机 Docker 命令会被 Kubernetes 命令替代,但思路不变。docker logs 对应 kubectl logs,docker inspect 的很多信息对应 kubectl describe pod 和 Pod status,docker exec 对应 kubectl exec,退出码和 OOMKilled 仍然是关键证据。单机基本功越扎实,Kubernetes 排障越不容易迷路。
6.2 订单 API 的启动失败案例
订单 API 容器启动后立即退出,开发第一反应是“镜像坏了”。正确排查路径应该是:先 docker ps -a 看到容器状态为 Exited,再 docker inspect 查看退出码,再 docker logs 看启动日志。日志显示 Permission denied: /app/logs,说明不是镜像缺 jar,也不是端口问题,而是非 root 用户没有写日志目录权限。
修复方向不是进入容器 chmod,因为容器重建后改动会消失。正确做法是回到 Dockerfile 或部署挂载,把日志输出改成标准输出,或者提前创建并授权必要目录。如果这是生产环境,还要检查为什么应用仍写本地文件日志,是否符合日志采集规范。
这个案例的重点不是权限本身,而是排障顺序。先看状态,再看退出码,再看日志,再看 inspect,最后决定修复位置。顺序对了,问题很快收敛;顺序错了,就会在重建镜像、重启容器、怀疑网络之间来回打转。
订单 API 后续又出现一次“访问不到端口”。容器 Running,日志显示 Spring Boot started on port 9090,但启动命令仍然映射 -p 8080:8080。这说明应用监听端口和容器映射端口不一致。修复不是改 Docker 网络,而是统一 SERVER_PORT、Dockerfile EXPOSE、运行参数和文档。这个问题很典型:容器状态正常,应用也正常,但外部入口配置错了。
6.3 端口错误和 OOM 要回到运行事实
再后来订单 API 压测时出现 OOMKilled。日志最后没有 Java OutOfMemoryError,容器退出码是 137。团队最初怀疑代码内存泄漏,但查看 docker inspect 发现 memory limit 只有 512m,而 JVM 默认堆比例加上线程和直接内存已经接近限制。最终修复是调整容器资源、JVM 参数和连接池,并补充压测基线。这个案例说明 OOMKilled 不是单一应用问题,它是 JVM、容器限制和流量模型共同作用的结果。
7. 常见错误与排障
容器运行问题很多,但初学阶段先掌握五类高频故障就够用。
7.1 启动、端口、配置、OOM 和工具缺失
第一类是容器立即退出。现象是 docker ps 看不到,docker ps -a 显示 Exited。可能原因包括启动命令错误、jar 不存在、配置缺失、端口被应用内部占用、权限不足、应用主动退出。排查时看退出码和 docker logs。如果日志为空,再看 entrypoint 和命令是否正确。修复应该回到 Dockerfile、运行参数或应用配置。
第二类是访问不到端口。现象是容器 Running,但浏览器或 curl 访问失败。可能原因包括未做 -p 映射、左右端口写反、应用监听端口和映射端口不一致、应用只监听 localhost、宿主机端口被占用、防火墙阻断。排查时看应用日志、容器内监听、docker ps 的 PORTS 列和宿主机端口。不要只盯着 Dockerfile 的 EXPOSE。
第三类是环境变量未生效。现象是容器里能看到变量,但应用仍使用旧配置,或者不同配置来源互相覆盖。可能原因包括 Spring 配置优先级、变量名写错、profile 未激活、命令行参数覆盖环境变量、配置中心覆盖本地配置。排查时看 docker inspect 的 Env、应用启动日志和最终配置摘要。修复方向是统一配置命名和优先级。
第四类是容器被 OOMKilled。现象是退出码可能为 137,状态或事件显示 OOMKilled。可能原因包括 memory limit 太小、JVM 堆比例过高、线程数过多、直接内存过大、流量峰值异常、内存泄漏。排查时看 docker inspect 状态、docker stats、JVM 参数、GC 日志和应用内存指标。修复方向是结合压测调整资源和 JVM,不要只盲目重启。
第五类是进入容器发现缺工具。现象是 sh、curl、ps、netstat 都没有。可能原因是镜像精简或 distroless。它不一定是错误。排障时可以使用临时 debug 容器、在本地用同镜像复现、依赖日志和 inspect,或者在测试镜像中保留工具但生产镜像精简。不要因为缺工具就把生产镜像做成全能运维机。
这些故障都强调同一个原则:容器运行排障不是“进去看看”这么简单。很多证据在容器外部更完整,例如状态、退出码、端口映射、资源限制和挂载。进入容器只是补充观察,不应该成为唯一手段。
7.2 挂载遮蔽和工作目录错误
还有两类故障值得提前记住。第一类是挂载遮蔽。你以为镜像里有 /app/config/application.yml,但运行时把宿主机目录挂载到 /app/config,镜像里的文件被遮住了。现象是容器内文件和镜像构建时不一致。排查时看 inspect 的 Mounts。第二类是工作目录错误。应用用相对路径读取文件,但运行时 WORKDIR 或 command 改了,导致文件找不到。排查时看 Dockerfile、inspect 的 WorkingDir 和应用日志。
7.3 排障动作也要控制风险
排障时也要控制动作影响。进入容器执行 cat、env、ls 属于低风险观察;执行 rm、chmod、vi、安装工具、修改配置属于高风险修改。生产环境应尽量避免后者。实在需要临时操作,也要记录原因、命令、时间和后续正式修复方式,否则现场操作会变成新的不可追踪状态。
8. 生产化边界
8.1 生产排障要从个人命令升级为团队流程
生产环境里,容器运行排障要从个人命令变成团队流程。日志必须进入统一采集,启动日志必须包含版本、profile、端口、关键配置摘要和 JVM 资源信息。容器状态、退出原因、重启次数、资源指标要能被监控系统采集。发布系统要保留镜像 digest、配置版本和运行参数。事故中要能快速回答:运行的是什么,怎么启动的,为什么退出,谁改了配置,是否发生 OOM,是否接入了流量。
生产环境也不能依赖进入容器手工修复。容器应当可替换,修复应当通过镜像、配置或部署声明重新发布。进入容器可以观察,但观察过程要尽量只读,避免破坏现场。对于精简镜像,团队应准备 debug 工具链,例如临时 debug 容器、节点级诊断工具、日志指标和事件采集,而不是要求每个业务镜像都内置完整工具。
生产环境还要明确“谁有权进入容器”。exec 权限很敏感,因为它可以读取环境变量、文件、挂载的 Secret,也可能执行影响业务的命令。团队应该通过平台权限控制、审计日志和操作规范管理 exec,而不是把它当成所有人都能随便使用的便利工具。越是容器化,越要减少不可审计的人肉操作。
资源限制也要纳入生产基线。没有 memory limit,容器可能影响宿主机和其他服务;limit 太小,服务频繁 OOMKilled;CPU limit 太紧,延迟抖动;没有日志限制,异常日志可能打爆磁盘。单机 Docker 中这些问题已经能模拟,生产环境只是规模更大、影响更广。
8.2 从命令排障升级到证据体系
本地排障可以靠几条 Docker 命令,生产排障必须靠证据体系。证据体系至少包含容器状态、退出码、日志、指标、事件、镜像版本、配置版本、资源限制、探针结果和发布记录。任何一个环节缺失,事故中就会出现盲区。
后端团队可以把这些证据写成排障模板:服务不可访问时先看入口流量,再看实例就绪,再看容器状态,再看日志和端口;容器重启时先看退出码和 OOM,再看最近发布和配置变更;配置异常时先看运行时环境变量和应用配置摘要,再看配置中心和 Secret 版本。模板不是为了束缚排障,而是为了让新同学也能按证据推进。
8.3 证据体系最好自动化沉淀
证据体系最好自动化沉淀。发布系统记录镜像 digest 和配置版本,日志系统自动打上 service/version/instance/environment 标签,监控系统记录容器重启次数和 OOM,告警消息带最近发布信息,事故复盘链接到对应日志和指标。这样排障不再依赖某个资深同学记得哪些命令,而是整个系统默认提供线索。
9. 什么时候用,什么时候不用
9.1 本地和测试环境必须掌握
只要你在本地或测试环境运行容器,就需要掌握本篇命令和思路。它们是 Docker、Compose、Kubernetes 排障的共同基础。不要等到线上 Pod 重启才第一次理解退出码、日志和资源限制。
9.2 生产环境不能照搬单机操作
但也不要把单机 Docker 排障经验直接等同于生产运维。生产中很多对象由 Kubernetes 或平台管理,不能随意 docker stop、docker rm、docker exec 修改现场。单机命令用于理解模型,生产操作要遵守平台流程和权限边界。
9.3 运行排障能力的学习顺序
建议先掌握状态和日志,再掌握 inspect,再掌握 exec,最后掌握资源限制和信号。状态和日志能解决大多数启动失败;inspect 能确认运行事实;exec 能补充现场观察;资源和信号决定生产稳定性。学习顺序不要反过来,一上来就研究复杂调试工具,反而容易忽略最基本证据。
当你能看到一个异常容器后,在几分钟内说清“它有没有启动、主进程为什么退出、日志最后发生什么、运行参数是否符合预期、端口是否真的监听、资源是否触顶”,就说明本篇基本功已经过关。
10. 下一篇衔接
本篇解决的是单个容器运行后的生命周期和排障。你现在应该能用状态、退出码、日志、inspect、exec 和资源限制建立排障路径,也知道容器不是服务器,不能靠进入容器手工热修维持系统。
下一篇会进入 Docker 网络与数据卷。容器能启动只是第一步,后端服务还需要访问数据库、Redis、其他 API,也需要处理临时文件、持久化数据和挂载路径。网络和数据卷会把单容器排障扩展到服务之间的连接和状态管理。