Appearance
第5章 Docker 容器状态与排障
对应简历:技能第 5 条「具备 Kubernetes / Docker 生产环境背景,能完成容器平台状态检查与问题初步定位,并按'一线处理—二线转派—原厂协同'流程分级处理」/ 项目一 P1-1「FusionCompute 虚拟化底座、Kubernetes 集群(含多 master 高可用改造)……部署与验收」(Docker 18.09 aarch64)/ 项目三 P3-1~P3-2(containerd 1.7.8 + K8s v1.28.2)。 支持版定位:以运维版 05 为底稿改造——容器状态检查、日志读取、重启策略、常见报错初判是主梁;Dockerfile 手写压缩为 1 节;镜像离线导入保留(银行内网真实经历)。本章一切答案的落点是"容器类工单我能初判、能分诊、能转对方向",K8s 层的分级转判详见第 6 章。
术语约定:Kubernetes(以下简称 K8s,仅正文简写)。内网 IP、仓库域名一律脱敏为"内网私有仓库""某银行内网环境"。
5.1 三大概念与容器 vs 虚拟机:给客户解释的第一课 【必背】
标准回答: 容器三大概念我用"类与实例"的类比给客户讲,一讲就懂:镜像 Image 是只读模板,把应用、依赖、运行环境分层打包在一起,相当于编程里的"类";容器 Container 是镜像的运行实例,在只读层之上叠一个可写层,相当于"实例",启动是秒级的——因为它不引导内核,只是起一组被隔离的进程;仓库 Registry 是集中存取镜像的服务,Docker Hub、Harbor、各云厂商私有仓库都算,push 和 pull 的对象就是它。容器和虚拟机的区别本质一句话:容器是"隔离的应用进程",虚拟机是"完整的计算机"。展开两条:隔离级别上,容器共享宿主机内核,靠 Namespace 隔离视图、Cgroup 限制资源配额,所以体积 MB 级、启动秒级、单机密度高;虚拟机通过 Hypervisor 模拟硬件、每个 VM 跑独立内核,隔离最彻底但体积 GB 级、启动分钟级。我在银行项目是"虚拟机里跑 K8s 容器"的两层叠加用法——FusionCompute 发虚拟机做资源底座,虚拟机里再跑容器平台,这个真实形态正好把两者定位讲活:底层虚拟化解决资源池化和强隔离,上层容器解决交付一致性和快速发布,互补不替代。
追问链:
- 追问1:容器靠什么隔离? → 答:两套内核机制配合,一句话总结"Namespace 管隔离,Cgroup 管限额"。Namespace 解决"看得见什么":pid 让容器有自己的进程树视图、net 给独立网络栈、mnt 独立挂载点、uts 独立主机名、ipc 隔离进程间通信、user 做用户映射;Cgroup 解决"能用多少":限制 CPU、内存、IO、进程数配额。支持岗记这条的价值是解释现象:容器里 ps 只能看到自己的进程、容器里 free 看到的是宿主机内存总量——客户问"为什么我这容器显示 64G 内存但我只申请了 2G",用 Namespace 和 Cgroup 就能讲明白,这是我在现场真实被问到过的问题。
- 追问2:客户问"你们平台为什么用容器不用虚拟机?"怎么答? → 答:先认可虚拟机的价值再说分工,不能贬任何一边。我的话术:虚拟机负责底座——资源池化、强隔离、每台业务有独立操作系统;容器负责应用层——同一套镜像在任何环境跑起来都一样,升级回滚以容器为单位秒级完成,出了问题重建一个就行,不用重装系统。我们银行项目就是两层都用:FusionCompute 虚拟化出 15 台国产化虚拟机,里面跑 K8s 容器平台。效果数据:环境交付从按天算变成按分钟算,发布回滚从"停机重装"变成"换容器"。给客户讲架构,永远讲"为什么这么配"而不讲"哪个更先进"——这是支持岗和技术岗说话方式的区别。
- 追问3:客户环境既有 Docker 又有 K8s,你怎么帮客户决定"什么放容器、什么放虚机"? → 答:用三类分界帮客户理清。进容器的:无状态应用、Web 前端、API 服务、微服务——看重的是快速发布、弹性伸缩、环境一致性;留虚拟机的:强隔离要求的业务、依赖特定内核或操作系统的系统、重量级有状态服务(数据库类我们坚持外置);都要的:我的银行项目就是两层叠加的真实样板。判断的深层逻辑一句话:容器卖的是"交付效率",虚拟机卖的是"隔离强度"——按业务最在乎什么来选。这类咨询在容器化改造的客户现场非常高频,我能用自己两个项目的形态举例,说服力比讲概念强得多。
5.2 Docker 架构与 containerd:一节版 【理解】
标准回答: Docker 是 C/S 架构,调用链一句话讲完:docker 客户端命令 → dockerd 守护进程做镜像、容器、网络、卷的管理 → containerd 负责容器生命周期 → 底层 runc 按 OCI 标准真正创建容器进程。客户端和 daemon 通信:同机默认走 Unix socket(/var/run/docker.sock),普通用户免 sudo 用 docker 就是被加进了 docker 组——本质是拿到 socket 读写权,权限近似 root,客户环境做安全检查时这一项经常被点名。和 K8s 的关系要主动讲清:K8s 1.24 之前 kubelet 经 dockershim 调 Docker,1.24 之后移除适配层、直接通过 CRI 接口调 containerd——所以我经历里两个运行时都出现过:银行项目受华为 BCS 配套约束用 Docker 18.09(aarch64 静态包),移动云新集群用 containerd 1.7.8。对支持岗的实际意义在排障分层:docker 命令没响应,先分清是 daemon 挂了(systemctl restart docker 的事)还是权限问题(docker 组的事),别上来就重启容器——层次分清才不会在客户环境乱试。
追问链:
- 追问1:Docker 和 containerd 什么关系?为什么 K8s 不用 Docker 了? → 答:containerd 本来就是 Docker 捐给 CNCF 的核心运行时组件,两者是上下游不是竞品。K8s 时代调用链是 kubelet → dockershim → dockerd → containerd,链路长、每层有维护负担;1.24 移除 dockershim 后 kubelet 直接 CRI 调 containerd,少一层转发更轻更稳。用户最关心的兼容问题答案是"能用":Docker 构建的镜像遵循 OCI 规范,containerd 直接跑,不用重建。我在移动云项目落地 containerd 时踩过三个配置点:SystemdCgroup 要和 kubelet 驱动对齐、pause 镜像换国内源、crictl 显式绑定 socket——这三个点就是我"生产接触过"的证据,不是只背结论。
- 追问2:K8s 节点上还能用 docker 命令吗? → 答:分环境。containerd 时代的节点上 docker 命令看不到 K8s 的容器——crictl 才是"站在节点上看 K8s 容器"的工具;docker 命令在独立构建机上仍然用(我的移动云体系里,构建机负责 pull/tag/push,K8s 节点归 crictl)。支持岗要避免的经典错误:客户说"容器挂了",支持人员上节点敲 docker ps 发现空的,就答复"容器都不在了"——其实容器在 crictl 视角里活得好好的。两个工具的适用边界必须形成条件反射,这是我在双轨体系里实际踩过分清的。
5.3 容器状态字典:docker ps -a 的第一眼 【必背】
标准回答: 容器类工单到我手上,第一条命令永远是 docker ps -a(K8s 场景是 kubectl get pods -o wide),-a 必须带上——不带只显示运行中的,挂掉的容器直接隐身,这是新手最常见的第一步失误。状态字典我按"看到什么、想什么、查什么"三段背:Up 正常运行,如果客户还报故障,问题多半不在容器本体,往上查网络和应用;Exited 已退出,重点看退出码——137 先怀疑内存超限、1 是应用自身异常、143 是收到终止信号正常退出;Restarting 反复重启,基本就是 CrashLoop 的前奏,别等它稳定,直接抓日志;Created 创建了没起来,看事件——多半卡在镜像或配置;Dead 异常死亡残留,配合 inspect 查清理。状态列后面还有两列信息量大:STATUS 里的 "Up 2 minutes (healthy)" 带健康检查结果,"(unhealthy)" 说明进程活着但应用已经不能服务——这是"看起来活着实际死了"的假健康形态;"Exited (137) 5 minutes ago" 把退出码和时间一起给你,直接对故障时间线。把状态字典背熟,工单响应的第三句话就能给客户初步方向,而不是"我看看啊"。
追问链:
- 追问1:容器状态是 Exited (0),应用也没报错,为什么业务不可用? → 答:退出码 0 是"正常退出",恰恰是最迷惑的形态。三种常见原因:一是容器的主进程执行完就退了——比如镜像的 CMD 写了个一次性脚本,跑完就退,而用户以为它是常驻服务;二是应用收到配置信号主动退出——配置文件里 daemonize 或前台模式开关配反了;三是入口脚本前置检查失败后主动 exit 0 退出(写错的 healthy check)。排查顺序:先 docker logs 看它说了什么,再 inspect 看 Cmd/Entrypoint 到底执行了什么,最后对照镜像文档确认 CMD 语义。我处理过一次"容器起来几秒就退"的工单,根因就是 CMD 被覆盖成了交互式命令,没有输入就退出——状态和退出码加上日志,十分钟内给出结论。
- 追问2:Restarting 状态和 K8s 里的 CrashLoopBackOff 是什么关系? → 答:本质是同一现象在不同编排层的名字。docker --restart=always 策略下,容器退出被 daemon 立即拉起,ps 里看到 Restarting;K8s 里 kubelet 按 restartPolicy 重启失败容器,退避时间指数拉长(10 秒、20 秒、40 秒……),Pod 状态显示 CrashLoopBackOff。共同点是"起、崩、再起"的循环,排查思路也一致:抓崩溃前的日志(docker logs / kubectl logs --previous)、看退出码分方向(137 内存、1 应用错)、查探针是否误杀。区别是 K8s 的退避机制给了我们抓日志的窗口——越早介入越能拿到崩溃遗言,这也是我响应 CrashLoop 类工单要快的原因。
5.4 日志读取三板斧:logs / --previous / 日志轮转 【必背】
标准回答: 容器日志是排障的第一证据源,我的三板斧按顺序打。第一斧 docker logs -f --tail=200 容器名:--tail 控制只看最近两百行(全量日志可能几十万行,直接刷屏淹没重点),-f 挂着实时看复现过程;时间过滤用 docker logs --since 30m 容器名,对故障时间线特别有用——客户说"十分钟前开始报错",我只看这十分钟,证据密度高。第二斧是 K8s 场景的关键参数 kubectl logs --previous Pod名:容器崩溃重启后,当前日志可能是空的(新进程还没输出),--previous 看的是上一次崩溃前的日志——CrashLoopBackOff 类问题的"遗言"全在这里,这个参数忘了用,就会得出"日志里什么都没有"的错误结论。第三斧是日志到底存哪:容器 stdout/stderr 落在宿主机 /var/lib/docker/containers/<id>/<id>-json.log(K8s 是 /var/log/containers/ 下的软链),logs 命令就是读它——所以这个文件必须配轮转上限,daemon.json 里 log-opts 设 max-size 50m、max-file 3,否则一个狂刷日志的容器能把宿主机磁盘写满,我在现场处理过这个真实事故,之后"新节点初始化必配日志限额"写进了基线。三板斧的口诀:先看它说了什么(logs)、再看死前说了什么(--previous)、最后确认日志本身没把机器搞死(轮转)。
追问链:
- 追问1:docker logs 是空的,容器在跑,怎么继续查? → 答:两个最常见原因分头查。第一,应用日志没写 stdout 而是写到了容器内文件——这是最经典的容器化改造坑:应用按传统习惯把日志写进 /var/log/app.log,docker logs 永远是空的。处理:进容器 tail 那个文件(docker exec 容器 tail -f /var/log/app.log),长期方案是把日志输出改到 stdout 或把日志目录挂出来;第二,应用有输出缓冲——Python 没设 PYTHONUNBUFFERED=1 时,stdout 攒一批才真正写出,日志"延迟甚至看起来是空的",加环境变量解决。判断方法很简单:docker exec 进容器 ps 看进程、ls 看日志文件在哪,两分钟定位。这个"日志去哪了"的问题是我被同事问频率最高的容器问题之一,我在知识库里专门写了条 FAQ。
- 追问2:kubectl logs 报 "previous terminated container not found" 是什么情况? → 答:说明这个 Pod 里不存在"上一次崩溃的容器"可供查看——两种情况:一是 Pod 刚被调度到新节点,kubelet 还没在本节点保留过任何历史容器实例,--previous 自然没有对象;二是崩溃次数太多,最老的那次容器日志和实例已被清理(kubelet 默认保留有限个已结束容器)。处理:别纠结 --previous,改看三处——当前日志(logs 不加参数)、describe 的 Last State 和 Exit Code(那里永远记录着最后一次退出的码和原因)、节点侧 journalctl -u kubelet 拿 kubelet 视角的记录。我给团队的原则是:--previous 有就用(它最直接),没有就退而求其次拿退出码分诊,永远不要因为一条命令不可用就停下来。
实操模板(容器日志排查命令组):
bash
docker logs -f --tail=200 <容器> # 实时看最近 200 行
docker logs --since 30m <容器> # 只看最近 30 分钟(对时间线)
kubectl logs <pod> -n <ns> --previous # 上一次崩溃的日志(CrashLoop 必用)
kubectl logs <pod> --tail=200 -f # 当前日志尾部
docker exec <容器> tail -100 /var/log/app.log # 应用写文件日志时直接进容器看
# 日志轮转(daemon.json,防磁盘写满)
# {"log-driver":"json-file","log-opts":{"max-size":"50m","max-file":"3"}}5.5 进入容器与 inspect:现场取证动作 【必背】
标准回答: 日志看完还定位不了,就进容器现场取证,我的固定动作四步。第一步 docker exec -it 容器 sh 进去(极简镜像没有 bash 就用 sh,再不行用 docker exec 容器 ps 单命令模式)——进去先 ps 看主进程和子进程都在不在,ss -lntp(或 netstat)看应用真的在监听吗、监听的是不是 0.0.0.0,env 看环境变量注入对不对,curl 127.0.0.1:端口/健康路径 从容器内部自测服务——容器内自测通了、容器外不通,方向立刻转向网络和端口映射,这就是"内外对照法"切分问题域。第二步 docker inspect 容器 对配置:IP 地址、端口映射 HostConfig.PortBindings、挂载 Mounts、环境变量 Env、重启策略 RestartPolicy、State 里的 ExitCode 和 OOMKilled 标志——九成"配置类疑问"inspect 一条命令有答案。第三步 docker stats 看实时资源,CPU 有没有顶到 --cpus 限额、内存有没有逼近 --memory 限额,逼近就是 OOM 前兆。第四步 docker top 容器 从宿主机视角看容器内进程,和容器内 ps 对照。这四步的方法论是"从外到内、从状态到配置":先确认死活,再看它自己说了什么,再进现场看环境,最后对配置——和第 9 章网络排查树是同一套分层思想。
追问链:
- 追问1:exec 进容器改了配置,重启后丢了,客户不理解怎么办? → 答:这是容器"可写层"机制的天然结果,给客户解释我用一个类比:容器就像一台"一键还原"的电脑,每次重建都回到镜像出厂状态,只有挂载出来的目录(相当于外接硬盘)和镜像本身的内容能留下来。所以正确姿势从来不是"进容器改文件",而是三种:改 Dockerfile 重建镜像(变更代码化)、改挂载的宿主机配置文件(配置在卷里)、改环境变量重新创建容器。我会顺手把对应的正确操作步骤发给客户,并解释"这次我帮您临时改了,但下次发布重建还会丢,建议按我发的步骤改到配置源"——支持岗的价值不只是修好这一次,是把客户引导到不会复发的路上。注意 docker restart 不会丢(同一个容器重启保留可写层),但删除重建、K8s Pod 重建都会丢——这个"什么时候丢什么时候不丢"的边界要讲准,不然客户被二次坑。
- 追问2:容器里没有 ps、ss 这些命令,怎么办? → 答:三层替代。第一层,单命令模式不进交互:
docker exec 容器 ps aux、docker exec 容器 cat /etc/resolv.conf,有什么用什么;第二层,从宿主机侧拿信息:docker top 容器看进程、docker inspect看网络和挂载、nsenter进入容器进程的命名空间用宿主机自己的工具(nsenter -t <pid> -n ss -lntp,pid 从 docker inspect 拿)——这招对极简镜像(distroless、alpine)特别好用;第三层,换一个带工具箱的容器共享网络命名空间排查:docker run --rm --network container:<目标容器> nicolaka/netshoot ss -lntp,netshoot 里有全套网络工具,用完即删不留痕迹。第二、三层是我处理"生产容器缺工具"的标准后手,客户环境里装不了东西时尤其管用。
5.6 重启策略与"重启能不能解决问题" 【必背】
标准回答: Docker 的重启策略三种要分清:--restart=no 默认不重启;on-failure[:N] 只在异常退出(退出码非零)时重启,最多 N 次——适合跑完就退的批处理任务防误伤;always 无条件重启,且 Docker 服务或宿主机重启后也会自动拉起容器——常驻服务一律用它,我给客户部署单机服务的标准起手式就是 docker run -d --name xx --restart=always -p 80:80 -v /data:/data 镜像。但要主动讲一个支持岗的专业判断:重启是恢复手段,不是解决方案。容器反复崩,重启策略会把它按在循环里反复拉起,表象是"一直在重启",根因一点没动。所以我接到"容器一直重启"的工单,第一动作不是重启而是抓证据:logs --previous 拿崩溃遗言、inspect 拿退出码、describe 拿事件——先分诊(OOM?配置错?依赖连不上?探针误杀?),对症处理后才谈恢复。还有一条纪律:对客户环境里"谁在管重启"要心里有数——docker 的 restart 策略、systemd 的 Restart、K8s 的控制器三层可能同时存在,如果 systemd unit 管着 docker start 而 docker 又配了 always,两边会打架(systemd stop 时 docker 又拉起来)。一个对象一个管理者,排查"怎么停都停不掉"的容器先查这条。
追问链:
- 追问1:客户要求"先重启试试",你重启前做什么? → 答:三件套,30 秒内完成。第一,快照现场:
docker logs --tail=100 容器 > /tmp/reboot-before.log、docker inspect 容器 > /tmp/inspect-before.json——重启可能清掉崩溃现场,先留证据;第二,确认影响面:这个容器上还跑着什么、有没有用户正在用、重启中断窗口多久(秒级),必要的话跟客户对一句"我现在重启它,服务会中断几秒钟,可以吗"——支持岗重启生产对象必须先告知,这是纪律不是客套;第三,想好重启失败的后手:如果重启后还是崩,下一步看什么(logs/退出码/资源)。这三步练熟之后不增加体感时间,但让每次重启都"可解释、可追溯、可回退"。我在银行环境养成的这个习惯,到移动云集群管理时同样适用——kubelet 重启、节点重启都是同套路。 - 追问2:K8s 的 restartPolicy 和 Docker 的 --restart 有什么区别? → 答:两个层面的东西。Docker 的 --restart 是单机 daemon 行为,管的是"这台机器上这个容器崩了要不要拉起";K8s 的 restartPolicy(Always/OnFailure/Never)是 Pod 级声明,且配合控制器工作——Deployment 下的 Pod 崩了,kubelet 按策略重启容器,节点挂了整个 Pod 还会被控制器调度到别的节点重建,这是"集群级自愈",比单机重启策略高一个维度。另外 K8s 不允许在 Pod 里再依赖 docker 的 --restart(容器由 kubelet 接管),所以迁移到 K8s 后 --restart 参数是无效的,这个点给从 Docker 转来的客户讲清楚能避免误会。我的口径:单机工具用 docker --restart,进了集群一切听控制器的——两套语义不混用。
5.7 退出码分诊:137 / 143 / 1 / 0 【必背】
标准回答: 退出码是容器排障的"化验单",四个高频码我形成了条件反射。137 = 128+9,进程被 SIGKILL 杀死——两种来源:内存超限被 OOMKilled(inspect 的 State.OOMKilled 为 true,最常见),或被人为 docker kill;所以看到 137 第一步 docker inspect 容器 --format '{{.State.OOMKilled}}' 确认是不是 OOM,是的话查内存限额和应用内存行为(内存泄漏的曲线是缓慢爬升,突发尖峰是配置不足)。143 = 128+15,收到 SIGTERM 优雅退出——通常是有人执行了 stop、或编排层主动终止(滚动更新、缩容、驱逐),如果客户没做任何变更却出现 143,就要查是谁在动它(自动化任务、误操作)。1 = 应用自身异常退出,答案在日志里——配置错误、连不上数据库、端口占用、启动参数非法,logs 崩溃段逐行看。0 = 正常退出,配合上一节讲的"跑完就退"三种原因排查。分诊口诀:137 查内存、143 查谁动的手、1 看遗言、0 想它是不是本来就打算退。支持岗的价值在于把退出码翻译成人话同步给客户和二线:"137、OOMKilled 为 true、内存限额 2G、应用 RSS 峰值 2.1G"——一句话把现象、证据、嫌疑全部交代,这就是工单记录里的高质量描述。
追问链:
- 追问1:OOMKilled 之后你怎么处理? → 答:三步走。第一步确认证据链:inspect 看 OOMKilled 和 ExitCode 137、docker stats 或监控看内存曲线——分清"缓慢爬升到顶"(内存泄漏,转开发)和"启动即顶"(限额太小或堆参数没配对)。第二步对症处理:限额不足就调大 --memory 或 K8s 的 limits(同时评估节点总容量,别把一台机器挤爆);Java 应用要检查 -Xmx 是否显式配置——老版本 JVM 按宿主机内存算默认堆,在 2G 限额的容器里默认堆可能直接超限,显式配 Xmx 或升级容器感知的 JDK 是根治;Python/Node 类检查是否有缓存无上限增长。第三步预防:给该服务加"内存使用率超限额 80%"的预警告警,赶在 OOM 之前发现;把本次的限额值和依据记进工单,下次评估容量有据可查。OOM 是我处理频率前三的容器问题,这套动作已经是肌肉记忆。
- 追问2:容器里 top 显示的内存和限额对不上,怎么解释? → 答:这是容器"资源感知"的经典坑:容器里 top/free 看到的是宿主机的 CPU 核数和内存总量(默认没有 cgroup 感知),2G 限额的容器里 free 显示 64G 非常误导。看"事实"要直接读 cgroup:
cat /sys/fs/cgroup/memory/memory.limit_in_bytes(cgroup v1)或cat /sys/fs/cgroup/memory.max(v2),这个值才是限额真相。后果最典型的是 JVM:老版本 JDK 按宿主机内存算默认堆,2G 限额里默认堆可能按 64G 的四分之一算,启动就 OOM——解法是显式 -Xmx 或用 8u191+ 的容器感知 JDK。客户问"我这应用到底还能用多少内存",我就用这套"别信显示、去读 cgroup"的口径回答——既能解释现象,也显得真懂底层。
5.8 常见报错速查集:支持岗初判词典 【必背】
标准回答: 容器类报错我按"关键词→一句话根因→第一动作→归谁处理"整理成速查词典,这是我的初判底气来源。ImagePullBackOff / ErrImagePull:镜像拉取失败——细分三种:unauthorized(凭据问题:secret 名错、不在同一 namespace、密码过期)、manifest not found(镜像名或 tag 写错、架构不匹配)、timeout(网络不通或仓库挂了);第一动作 describe 看 Events 里的具体报错串,凭据类我一线能修(核对 secret 和 namespace),仓库挂了或网络策略问题转二线。CrashLoopBackOff:容器起又崩循环——退出码分诊(137 OOM、1 应用错)加 logs --previous,配置类一线修,代码类转开发。OOMKilled:见 5.7,限额调整一线处理,内存泄漏转开发。exec format error:镜像架构和节点架构不匹配(x86 镜像跑在 ARM)——我在银行 ARM 项目专门处理过,查 inspect 的 Architecture 字段,换 arm64 镜像。port is already allocated / address already in use:端口被占,宿主机 ss -lntp 找占用者,换端口或停占用服务。permission denied connecting to docker.sock:当前用户不在 docker 组或权限没给,加组或 sudo——客户环境权限申请要走流程,别私自提权。no space left on device:节点磁盘满(镜像层或容器日志堆积),docker system df 找大头。每个报错对应"一句话根因+一条解法+一个归属",报障响应速度就来自这本词典。
追问链:
- 追问1:报错信息你不认识怎么办? → 答:我的三层方法。第一层,精读报错原文——容器生态的报错信息大多直说原因,allocated、denied、not found 都是明示,先把每个单词读一遍再动手,这个习惯解决了八成问题;第二层,看完整上下文——describe 的 Events 是按时间排的事件流,失败动作前后的几条事件往往比报错本身信息量更大;第三层,拿报错关键字查官方文档或 issue——过滤近一年、带解法(workaround)的结果。要强调我的纪律:我不会在客户面前"现场百度",而是先说"这个报错我需要 10 分钟查证确认根因,避免误操作,稍后给您准确结论"——支持岗宁可慢半步给准确答案,不给快一步的错误答案,这是银行驻场两年刻进习惯的原则。
- 追问2:这些报错里哪些属于"转二线/原厂"的信号? → 答:按我的分级口径(第 6 章展开):现象清晰、有对应 SOP、改动局限在应用配置层的,一线处理——OOM 调限额、端口冲突改规划、凭据过期换 secret、日志轮转补配置;需要进入运行时深层或节点系统层排查的转二线——containerd 本身异常、内核参数问题、存储驱动故障、网络插件深层问题;产品级缺陷协同原厂——华为 BCS 平台组件自身的异常、FC 虚拟化平台告警这类,我负责打包完整证据包转原厂并跟踪闭环。归属判断的底线是:改之前想清楚"这个动作动了谁的边界",容器里的应用配置我可以动,运行时和平台层的配置动之前必须和二线确认——这条边界感是分级处理的核心,也是客户敢把环境交给我的原因。
5.9 数据卷与"重启就丢"高频工单 【理解】
标准回答: 容器数据管理的原则一句话:"容器无常、数据有常"——可写层随容器删除而消失,要持久的数据必须挂出来。三种挂载方式按场景选:命名卷 -v data:/var/lib/mysql,由 daemon 统一管理、跨容器复用方便,数据库等有状态服务首选;绑定挂载 -v /host/path:/container/path,直接映射宿主目录,配置文件分发、日志目录外挂常用,注意宿主目录属主要让容器内进程可读写;匿名卷是 Dockerfile 里 VOLUME 声明的,没起名容易变孤儿垃圾,定期 docker volume prune 清理。判断标准很朴素:数据库 datadir、应用日志这类必须出容器层的一律挂卷,无状态应用随便重建。支持场景最高频的工单形态就是"客户在容器里改了配置/存了文件,容器一重建全没了"——处理口径在 5.5 追问 1 里:先解释机制(一键还原类比)、当场帮客户把数据从旧容器拷出来(docker cp 在容器删除前抢救,停止的容器也能 cp)、再引导到"挂卷或改镜像"的正确路径。我的预防动作是交付检查单里固定一条:有状态目录必须挂卷——这条检查项让"数据丢失"类工单在我交付的环境里基本绝迹。
追问链:
- 追问1:docker cp 能从停止的容器里拿数据吗?K8s 呢? → 答:能——docker cp 不要求容器在运行,只要容器对象还在(哪怕是 Exited 状态)就能双向拷贝,这是抢救"忘了挂卷"数据的最后窗口;容器被 rm 之后就真没了。K8s 对应
kubectl cp ns/pod:路径 本地路径,但有个经典坑:kubectl cp 本质是 exec tar 的封装,Pod 里必须有 tar 命令,极简镜像没有 tar 会直接报错——绕法是kubectl exec pod -- cat 文件 > 本地文件单文件导出,或者起一个带工具的临时 Pod 挂同样的卷去读。还有个细节:CrashLoop 中的 Pod 时机窗口很短,cp 可能赶不上容器重启,用kubectl debug或临时改 command 为 sleep 让容器活着再拷——这些"抢数据"的技巧我在实际工单里用过,讲出来就是实战痕迹。 - 追问2:挂载宿主目录后容器里写不进去(permission denied)? → 答:按三个嫌疑查。第一,属主与 uid:容器内进程的运行 uid(很多镜像非 root 运行)对宿主目录没有写权限——
ls -ln看目录的数字 uid,和镜像里的运行用户对上,chown 或调整目录权限解决;第二,SELinux:CentOS/麒麟这类启用 SELinux 的系统,挂载目录类型不是容器可访问类型会被拦,现象和普通权限问题一模一样极具迷惑性——ls -Z看类型,挂载参数加:z(共享)或:Z(私有)让 Docker 自动改标签,ausearch -m avc -ts recent有 denied 记录就是它干的;第三,只读挂载:命令里误写成-v /host:/container:ro。我在银行麒麟环境处理过 SELinux 这类问题,第一直觉就是先看 avc 日志再动权限——按证据处理,不瞎改。
5.10 容器资源限制与 OOM 行为 【理解】
标准回答: docker run 层面的资源限制三件套:--memory 内存上限、--cpus CPU 配额(1.5 就是 1.5 核)、--memory-swap 内存加 swap 总上限。两类资源超限的行为差异必须讲清,因为处置方向完全不同:CPU 超限是"节流"——进程被限制在配额内运行,表现是变慢(throttle),服务还活着,可以观察、可以优化、可以扩容;内存超限是"击杀"——cgroup 内存硬顶,内核 OOM killer 直接杀进程,容器退出码 137,服务直接死。所以评估限额时内存必须留足余量(Java 还要算堆外内存),CPU 可以贴着需求甚至超卖。配套的认知是 K8s 里 requests/limits 的对应关系:request 决定调度(节点承诺给你多少),limit 决定运行上限——CPU limit 太低,Java 应用启动期 JIT 编译被节流,启动从 30 秒拖到几分钟,探针超时被杀又进重启循环,这是我见过的"启动死循环"高频根因。给客户的容量建议我固定一个口径:内存 request 按日常 P90 用量、limit 按峰值加 30% 余量;CPU limit 给 request 的 2 到 3 倍留启动突发。这套数字不是拍脑袋,是我在移动云集群上按监控曲线调出来的经验值。
追问链:
- 追问1:容器 OOMKilled 和宿主机 OOM 有什么区别? → 答:级别不同、影响面不同、排查动作也不同。容器 OOMKilled 是 cgroup 级:这个容器用量触到自己 limit 的天花板,内核只杀该 cgroup 内的进程,宿主机和其他容器安全,inspect 里 OOMKilled 标志为 true——排查就查自己的限额和应用内存行为。宿主机 OOM 是系统级:物理内存整体耗尽,内核按 oom_score 全局挑牺牲者,可能杀掉任何进程包括 sshd,dmesg 里 grep "out of memory" 找记录——排查要治理整机内存超卖、找出内存大户。两层都要防:容器层靠合理的 limit 和内存趋势告警,宿主机层靠节点级内存水位告警(我配 80%)。K8s 还有一层是节点压力驱逐——kubelet 在节点内存/磁盘紧张时按 QoS 等级驱逐 Pod(BestEffort 最先),所以"不设 limits 的 Pod 最危险"这句话在 K8s 里是字面意思。
- 追问2:怎么判断应用是"快 OOM 了"? → 答:三个信号提前预警。一是用量逼近限额:docker stats 或 K8s 的 container_memory_working_set_bytes 接近 limit 的 80%——working_set 是内核 OOM 判定依据的口径,比 RSS 更准,告警要用它;二是增长形态:RSS 随时间缓慢爬升不回落是内存泄漏的典型曲线(和突发尖峰区分),趋势告警比阈值告警更早发现;三是 swap/换页行为:宿主机 vmstat 的 si/so 持续非零说明内存压力已经传导。我的落地做法:给关键容器配"内存用量/限额 > 80% 持续 5 分钟"的预警,配合每周看一次内存曲线趋势——把 OOM 从"深夜救火"变成"白天排期处理",这是监控前置的价值(第 7 章的告警分级会再呼应)。
5.11 镜像供给与离线导入:银行内网真实经历 【必背】
标准回答: 镜像拉取类问题在内网环境是家常便饭,我的完整解法有真实项目背书。命令对先分清:docker save -o web.tar web:v1 导出镜像(保留完整分层),cat web.tar.gz | docker load 导入(tar.gz 直接喂不用先解压);另一对 export/import 是针对容器的,丢分层丢历史一般不用。我的真实场景:某银行内网完全无法访问海外镜像仓库,而且 TaiShan 是 ARM 架构——所有镜像必须 arm64 版本,走"外网准备+离线分发":外网机器下载各镜像的 arm64 tar.gz 包带进内网,我写了批量加载脚本——for 循环遍历镜像目录、排除 edge-manager 开头的 BCS 管理面专用镜像(那些由管理面节点加载),其余全部 cat 进 docker load,加载完 docker images 核对清单,十几类镜像一次铺满 9 个数据面节点,BCS 纳管一次通过。这套经历沉淀了两个可迁移的能力:一是"镜像架构清单"——ARM 环境交付前逐个核对镜像的 architecture 字段,把 exec format error 扼杀在导入前;二是移动云项目把它升级成"镜像双轨体系"——公网拉取、内网私有仓库托管、集群 ImagePullSecret 认证拉取,运行时三层策略(内网仓库优先、公共仓库备选、二进制兜底),从根上消灭了 NotReady 类故障。
追问链:
- 追问1:客户内网环境拉不到镜像,你的一般处理流程? → 答:四步流程。第一步确认环境约束:能不能出公网、有没有内部私有仓库、架构是 x86 还是 ARM(ARM 是最常见的隐藏坑,很多镜像历史版本没有 arm64 构建);第二步镜像准备:有私有仓库就走"外网拉取→tag→push 到内网仓库→集群认证拉取"的标准链路,没有仓库就用 save/load 离线导入;第三步验证:load 完 docker images 核对名称、tag 和架构(inspect 的 Architecture 字段),先起一个测试容器跑通再铺生产;第四步固化:把镜像清单、来源、版本写成交付文档,下次升级照单准备——"清单化"是我从银行项目带出来的习惯,它把一次性救火变成可复制的流程。如果客户问"为什么不直接配个代理或者改源",我会评估后给方案——但生产环境改网络出口策略这类动作,一定先和客户网络组确认,不私自变更。
- 追问2:save/load 和私有仓库两种方式怎么选? → 答:按规模和频率选。一次性、几台机器、应急场景用 save/load——最快最直接,缺点是没有版本管理、每台机器都要手动导、升级要重新走一遍;长期、多节点、持续交付场景必须私有仓库——一次 push 所有节点共享、有账号权限审计、配合 ImagePullSecret 做认证拉取、还能配清理策略。我的两个项目正好是两种形态的样本:银行是纯离线(连仓库都没有,save/load 加脚本),移动云是双轨体系(私有仓库为主离线兜底)。支持岗回答这类选型题的价值在"我都干过":不是背优缺点表,是能讲出各自的真实坑——离线导入的坑是架构不匹配和版本对不上账,私有仓库的坑是凭据分发和证书信任(自签 CA 要下发到节点)——细节里见真章。
5.12 Dockerfile 一节版:手写模板与必背指令 【必背】
标准回答: 支持岗的 Dockerfile 要求比开发低一档:能看懂、能改、能手写一个标准模板,不必追求极致优化。指令我按必考三组背。第一组构建执行:FROM 指定基础镜像必须是第一条;RUN 构建时执行、每条一层,所以要合并并清缓存——RUN yum install -y xx && yum clean all 一条搞定。第二组启动命令(最高频):CMD 是默认命令、被 docker run 后面的参数整体覆盖;ENTRYPOINT 是固定入口、run 参数追加为它的参数;组合用法 ENTRYPOINT ["java","-jar"] + CMD ["app.jar"]——平时直接跑、换包时 run 后面给新 jar 名。格式铁律:CMD/ENTRYPOINT 用 exec 格式(JSON 数组)让主进程直接当 PID 1、能收到 SIGTERM 优雅停止;shell 格式隔了一层 sh,SIGTERM 被 sh 吞掉,容器只能等超时被强杀——这是"容器停得慢"的经典根因。要变量展开时用 ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar app.jar"],sh -c 是 exec 数组里吃环境变量的标准写法。第三组文件与配置:COPY 纯拷贝首选;ADD 会自动解压拉 URL 语义不透明不推荐;ENV 运行期生效(时区 TZ=Asia/Shanghai 必配,否则日志时间差 8 小时);EXPOSE 只是声明,真映射靠 -p;缓存铁律"变的放后面"——先 COPY 依赖清单装依赖、再 COPY 源码,改代码不重装依赖。
追问链:
- 追问1:现场手写一个 Java 应用的 Dockerfile? → 答:六行加两个注释我能默写,每个指令都带"为什么":FROM eclipse-temurin:8-jre 只带运行时精简安全(对应银行项目毕昇 JDK 的 ARM 语境就说 ARM 版基础镜像);WORKDIR /opt/app 定工作目录;COPY app.jar 改名统一;ENV TZ=Asia/Shanghai 和 JAVA_OPTS 堆参数——TZ 解决容器默认 UTC 时区日志差 8 小时的高频坑,JAVA_OPTS 配合 sh -c 展开;EXPOSE 8080 声明端口;ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar app.jar"]。写完我会主动补两句运维视角的话:日志要输出到 stdout 让 docker logs 能抓到;HEALTHCHECK 或 K8s 探针的路径要在镜像里就有——这些是"镜像好不好运维"的分界线。模板见下方实操模板,面试要求手写时按这个背。
- 追问2:.dockerignore 是干什么的?构建慢怎么治? → 答:.dockerignore 排除不进构建上下文的文件——node_modules、.git、日志、数据目录这些动辄几百兆的东西不排除,每次 build 都会把整个目录打包传给 daemon,构建慢的元凶通常不是 RUN 而是"上下文太大"。构建慢的完整治理四招:.dockerignore 缩上下文、依赖层前置吃缓存(变的放后面)、RUN 合并清缓存减层数、多阶段构建(编译阶段用全量 Maven 镜像、运行阶段只 COPY 产物到精简镜像,体积从 GB 级降到百 MB 级)。这些属于"知道且能讲清楚"的层面,我在支持岗的定位是能指导开发写出合格镜像,深度构建调优不是我的主战场——这个边界我会主动说清。
实操模板(两个必背手写模板):
dockerfile
# Java 后端模板(银行项目语境,ARM 环境换 aarch64 基础镜像)
FROM eclipse-temurin:8-jre
WORKDIR /opt/app
COPY app.jar app.jar
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8080
ENTRYPOINT ["sh","-c","java $JAVA_OPTS -jar app.jar"] # sh -c 才能展开 $JAVA_OPTS
# Python 应用模板
FROM python:3.11-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 # 不缓冲=日志实时可见
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
COPY . .
EXPOSE 8000
CMD ["gunicorn","-w","4","-b","0.0.0.0:8000","app:app"]5.13 容器类工单支持流程:从报障到闭环 【必背】
标准回答: 把前面所有点串成我的支持流程,这条线是我面试支持岗的核心武器。第一步接单定级:客户报"容器/服务异常",我先问四要素——现象(打不开?报错截图?)、时间(什么时候开始的)、范围(一个人还是所有人、一个服务还是全部)、变更(最近发过版改过配置吗)——四要素里"变更"命中率最高,六成问题和变更相关。第二步平台判断:先分清是 Docker 单机环境还是 K8s 集群——命令体系完全不同(docker ps 对 kubectl get pods),口径别串。第三步分层初判:按 5.3 状态字典→5.4 日志→5.7 退出码→5.8 报错词典的顺序收敛,五分钟内给出"哪一层、什么性质、我处理还是转派"的判断。第四步记录与转派:全过程写进工单——现象、时间线、已查证据(logs/退出码/事件原文)、已做动作、初步结论;要转二线或原厂的按第 6 章的证据包格式补全。第五步跟踪闭环:处理完验证业务恢复(不只是容器 Running,要客户实际功能可用),客户确认后关单,高频问题沉淀 FAQ 进知识库(第 11 章的运营闭环)。这套流程在银行驻场近两年跑了无数单,它的价值是:任何一单我都能讲出"当时怎么判的、为什么这么判"——支持岗面试官要听的就是这个判断过程,不是命令背诵。
追问链:
- 追问1:你处理容器工单的速度大概什么水平?举一个例子? → 答:给一个真实量级和一单示例:常规容器状态类、日志类问题我一般 10 分钟内定位给结论,复杂 CrashLoop 类 30 分钟到 1 小时闭环。举例:客户报"发布后服务时好时坏",我先 kubectl get pods 看到 RESTARTS 在涨、状态 CrashLoopBackOff;logs --previous 抓到崩溃遗言是数据库连接失败;describe 确认退出码 1、非 OOM;查配置发现发布把数据库连接串里的服务名改错了——回滚发布,服务恢复,全程约 20 分钟;随后把"发布检查单加一条:连接串服务名与 K8s Service 对照"沉淀进 FAQ。这单的价值点是:--previous 这个参数和"先回滚再改配置"的顺序决策——恢复业务永远优先于修根因,这条原则贯穿我所有故障处理。
- 追问2:怎么避免同类容器工单反复出现? → 答:三道闸。第一道,事后沉淀:每单闭环时自问"这问题会不会再来",会——就写 FAQ 或 SOP(现象、定位命令、解决步骤、归属),我维护的 IT 知识库就是这么长起来的;第二道,事前预防:把高频根因变成检查项——日志轮转限额、有状态目录挂卷、restart=always、TZ 时区,这四项写进部署检查单,新环境交付时逐项过;第三道,自助化:重复问题进 RAG 知识库(我的项目二),用户先问知识库再报障——我实测过重复答疑量明显下降。三道闸的本质是同一句话:支持岗的进阶不是处理得更快,而是让问题根本不必发生——这也是我简历"擅长知识库与自助化支持"那条的来路。
5.14 节点侧与磁盘治理:容器平台的第一杀手 【理解】
标准回答: 容器环境的磁盘问题有个特点:凶手藏的位置和传统环境不一样,我按"两个只涨不缩的目录+一个统计入口"讲。统计入口是 docker system df(containerd 环境用 crictl 对应命令),一条命令看镜像、容器、卷各占多少——这是治理的第一眼。凶手一,镜像堆积:反复构建拉取留下的悬空镜像和废弃层能堆几十 G,docker image prune -a --filter "until=168h" 清一周未用的(-a 会删所有未被引用的镜像,生产先确认再跑),K8s 节点用 crictl rmi --prune 清理;凶手二,容器日志:json-file 驱动默认无上限,一个狂刷日志的容器能写满磁盘,根治是 daemon.json 配 max-size/max-file(5.4 讲过),改完重启 docker 生效。磁盘满是节点级灾难的前奏:K8s 场景触发 kubelet 驱逐、镜像拉取失败、组件连锁异常,所以我的水位纪律是 80% 预警、85% 处理、90% 升级——磁盘告警在我所有告警里优先级排前三。还有一个隐蔽形态:lsof | grep deleted 找已删除但进程仍持句柄的文件——rm 了日志 df 不降的原因,重启对应进程或 truncate 才真正释放。这套治理动作我在移动云 7 台节点上常态化执行,"磁盘 85% 告警+日志限额+定期清未用镜像"三件套是标配。
追问链:
- 追问1:prune 类命令有什么风险,你怎么控制? → 答:风险点两个。一是"停止的容器"可能包含你想留现场的事故容器——prune 前先 docker ps -a 扫一眼,有排查中的容器先标记跳过;二是深度清理(-a、--volumes)会连"暂时没被引用"的镜像卷一起删——卷里可能是某个服务的数据,删了就找不回来,所以 --volumes 我在生产从不轻易加。我的控制纪律三条:先 df 看分布再精准清(image 和 container 分开)、批量删除前先 dry-run 或列清单、大动作放变更窗口做。一句话:批量删除类命令永远"先看清单再执行"——这条我写进自己的操作习惯,也常拿来培训新同事,因为它防的是最不可逆的错误。
- 追问2:节点磁盘告警响了,你的第一动作? → 答:按"先保业务再治理"的顺序:第一,快速定位大头——df -h 看哪个分区、du -sh 逐层下钻、docker system df 看容器侧占比,通常 5 分钟内有结论;第二,先清安全项:容器日志里最大的文件 truncate 释放(比 rm 安全,不涉及句柄问题)、悬空镜像 prune——这两步不碰业务数据,做完磁盘一般能喘口气;第三,业务数据盘紧张就不能这么粗暴了——归档、扩容走客户流程,扩容前评估"还能撑几天"(用增长速率算)给客户预期;第四,事后固化:查为什么涨——日志没限额?镜像没清理策略?告警阈值太晚?把根因补进基线。这套顺序我在真实工单里走过:先释放、再归因、后固化——和第 14 章故障框架"先止损再定位后复盘"完全同构。
5.15 crictl 对照:K8s 节点侧初判 【理解】
标准回答: K8s 节点上排查容器,我的工具分层是"优先 kubectl、节点级用 crictl、运行时层用 ctr"三层。kubectl 解决 Pod 视角的一切:logs、exec、describe、get——它跨节点、带集群上下文、有 RBAC 和审计,是规范入口。crictl 是"站在节点上看容器"的工具:crictl ps -a 看本节点容器状态和退出码、crictl logs <id> 看容器输出、crictl images 看镜像、crictl pull 镜像 在节点上直接复现拉取问题(验证 ImagePullBackOff 的标准动作)、crictl inspect 看挂载和环境变量。两个组合拳场景:一是 Pod 起不来时,kubectl describe + journalctl -u kubelet + crictl ps 三视图对照——K8s 认为发生了什么、kubelet 报了什么、容器实际是什么;二是 apiserver 不可用(kubectl 全超时)时,上节点用 crictl 依然能看容器日志和状态——这是"控制面失联时的节点级后手",也是支持岗在极端场景下的兜底能力。我的移动云集群里 crictl 配置有一个标准动作:/etc/crictl.yaml 显式绑定 containerd 的 socket——crictl 默认按 docker/containerd/crio 顺序遍历端点,环境里有残留配置就报连接错误,显式写死一劳永逸,这是我 9 项避坑清单里的第 2 条,预置进全节点初始化脚本。
追问链:
- 追问1:crictl 和 docker 命令的对照关系? → 答:一组对照背下来就能在两种环境无缝切换:docker ps → crictl ps;docker images → crictl images;docker logs → crictl logs(或 kubectl logs);docker exec → crictl exec(或 kubectl exec);docker inspect → crictl inspect。两个易混点:一是 crictl 只能看到"通过 CRI 创建的容器"(K8s 的 Pod 和 pause),docker run 起的独立容器它看不到——所以构建机上的容器还是 docker 管,K8s 节点才是 crictl 的地盘;二是 crictl 的容器 id 和 kubectl 的 Pod 名不是一回事,Pod 名要到 crictl inspect 的 labels 里找(io.kubernetes.pod.name 标签)。我给团队培训时把这张对照表做成了一页速查卡——支持岗在新环境第一件事就是确认"这是什么运行时、用哪套命令",工具选错方向全错。
- 追问2:Pod 状态和容器状态怎么对应着看? → 答:Pod 是逻辑单元,里面有 pause 沙箱容器加业务容器两层,状态要拆开看。Pod 显示 Running 但业务不通——进容器看应用(可能是探针没配,假健康);Pod 显示 CrashLoopBackOff——kubelet 视角容器反复重启,crictl ps -a 能看到具体业务容器的重启次数和退出码;Pod 显示 ContainerCreating 卡住——kubelet 还在拉镜像/配网络/挂卷,crictl images 和 journalctl -u kubelet 看卡在哪一步。分不清"Pod 状态"和"容器状态"会导致排查入口错位:Pod 的问题先 kubectl describe 看事件(K8s 层的解释),容器的问题再 crictl 下钻(运行时层的证据)——两层信息互为印证,这就是我排查 K8s 容器问题的基本姿势,第 6 章会在这套姿势上展开完整的分级转判。
5.16 端口映射与"外部访问不通"工单 【必背】
标准回答: 容器发布后"外面访问不了"是支持岗的高频工单,我的排查按"三层访问验证"收敛。第一层,容器内自测:exec 进容器 curl 127.0.0.1:应用端口——通说明应用本身活着,不通就回到 5.5 的容器内排查(监听地址、应用配置);第二层,宿主机验证:宿主机 curl 127.0.0.1:映射端口——通说明 -p 端口映射和 docker-proxy 转发正常,不通先查映射配置(docker inspect 的 PortBindings)和宿主端口占用(ss -lntp | grep 端口,被占会起不来报 port is already allocated);第三层,外部访问验证:从用户网络 telnet 宿主机映射端口——前两层通、这层不通,问题在宿主机之外:防火墙(firewalld/iptables 的 FORWARD 链)、云上安全组、客户网络策略。三层验证的价值是把"访问不通"这个模糊报障精确切到一层,每层都给客户一句确定的话:"应用正常、映射正常,问题在你们的网络策略,需要申请放行 X 端口"——然后走客户的防火墙申请流程。K8s 场景的对应关系要会讲:docker -p 相当于 Service 的 NodePort(节点端口暴露),生产对外更推荐云负载均衡(LoadBalancer)或 Ingress 统一入口——移动云项目的 apiserver 入口就是 CLB,我在支持沟通里会用这个对照帮客户理解。
追问链:
- 追问1:容器内服务监听 127.0.0.1 导致映射后访问不通,怎么发现和解释? → 答:这是端口类工单的头号经典坑。发现方法就是三层验证的第二层抓到的:宿主机 curl 映射端口不通、但容器内自测通——两层结果一对比,唯一解释就是服务只听了容器内的 127.0.0.1,端口映射转发到容器 eth0 的流量没人接。验证:exec 进容器
ss -lntp或netstat -lntp看监听地址列,127.0.0.1:8080 实锤。给客户解释用一句话:"您的应用只对本机开门,没有对所有网卡开门"——修复是改应用/中间件的 bind 地址为 0.0.0.0 或容器网卡 IP 再重启。这个坑在 Tomcat、Java 应用、Node 应用上都高频出现,我把它列进了应用容器化交付检查单的固定项——发布前先验证监听地址,省掉一次发布后返工。 - 追问2:客户说"你们发布后端口访问不通",但你们平台侧验证全通,怎么继续? → 答:这是典型的"边界对不上"场景,我的做法是给客户可自助执行的验证命令,把两边的观测对齐。我提供三步:第一步请客户在他报障的机器上执行
telnet 平台IP 端口并截图——很多"访问不通"是客户端代理、hosts、DNS 造成的(第 9 章 9.10 的 curl 能通浏览器不通同款问题);第二步确认客户端到平台的路径——是办公网、VPN 还是专线,不同路径策略不同;第三步约定同时段的对照测试——客户机器 telnet 不通而同网段另一台通,问题精确到那台客户端。这样处理的价值:不是我单方面说"我们这边没问题",而是给客户证据和参与感,最后定位到客户端代理配置错误——客户自己修好还学了一招。支持岗处理跨边界的网络问题,"把验证步骤交给客户"比"反复口头拉锯"专业得多。
5.17 镜像 tag 规范与版本管理 【了解】
标准回答: 镜像 tag 看似小事,治理缺失会变成事故源,我的 tag 规范四条。禁用 latest——它不可追溯(同 tag 被覆盖后回滚都找不到原镜像)、缓存不可控(节点缓存了旧 latest 就不会重新拉,"发布了等于没发布"),一律显式版本号;版本语义化——v1.2.3 或日期构建号(20261024-1),一眼看出新旧和构建时间;仓库路径分层——仓库地址/项目/应用名:版本,权限和清理都能按项目治理;重要变更记录 digest——sha256 内容哈希不可变,回滚和审计以 digest 为准,"tag 给人看、digest 给机器信"。支持岗讲这个话题的落点是工单场景:客户问"线上跑的到底是哪个版本",规范化的环境一条 docker inspect 或仓库记录就答得出;没规范的环境,tag 全是 latest、镜像来历说不清,升级回滚全靠运气。我在移动云项目定的镜像规范就是这套:内网仓库统一"项目/应用:版本"格式重打 tag,配合双轨体系的清单管理,之后再也没有"这是哪个版本"的疑问。
追问链:
- 追问1:为什么回滚要认 digest 不认 tag? → 答:因为 tag 是可变的引用——有权限的人可以把 v2.1 这个 tag 重新指向另一个镜像内容,而 digest 是内容的哈希、不可变。极端场景:上午发布的 v2.1 有 bug,下午有人用修复后的镜像覆盖了 v2.1 这个 tag——此时"回滚到 v2.1"得到的已经不是上午那个坏版本,也说不清线上到底是什么。所以生产部署清单记录 digest、审计以 digest 为准。支持岗遇到"版本对不上账"的工单,digest 就是仲裁依据——这套观念我在镜像双轨体系里落地过,讲出来就是治理经验而不是背概念。
- 追问2:旧镜像堆积怎么治理? → 答:三条策略组合。清理策略:定期删旧版本但永远保留生产在用版本和上一个版本(回滚保底),云仓库配自动清理规则;准入策略:新镜像入库走"架构核对+版本规范"检查,从源头不制造垃圾;监控策略:仓库容量和节点镜像层数纳入巡检(第 5 章 5.14 的磁盘治理联动)。我的经验数字:规范前节点的镜像层能堆几十 G,规范加定期清理后稳定在几个 G——镜像治理是磁盘治理的上游,两件事一起做才闭环。
5.18 容器工单的客户沟通话术速查 【理解】
标准回答: 容器技术对很多客户是黑盒,我的沟通话术把术语翻译成"客户能复述给领导听的话"。解释容器重启:"容器相当于一个可随时更换的标准件,我们发现它内部程序异常后更换了标准件,服务已恢复——数据都在挂载的存储上没有受影响。"(客户最怕的"重启丢数据"一句话安抚)解释 OOM:"应用使用的内存超过了给它分配的上限,系统按规则把它停掉了,就像房间超员会被请出去——我们把房间扩大并加了用量预警。"解释 ImagePullBackOff:"新版程序包还没能从仓库取到,通常是仓库地址或网络策略的问题,正在核对,预计 X 点内解决。"解释"改的东西丢了":"容器每次重新发布都会回到标准状态,您临时改的文件不在标准里,我帮您把它挪到持久目录/写进标准配置,下次发布就不会丢了。"话术的原则有三条:先讲影响面和恢复(客户第一关心)、再讲原因(用比喻不用术语)、最后讲预防(显示闭环)——顺序反了就变成技术答辩。这些话术我从工单里攒了两套(对业务用户、对客户技术员),进了我维护的 FAQ——把话术也知识库化,是我做支持的一贯做法。
追问链:
- 追问1:客户的技术人员用专业术语和你对线(比如"你们是不是又 OOM 了"),怎么接? → 答:对技术型客户反而简单——切换到对等语言,用证据对话。他说 OOM,我就回退出码和 OOMKilled 标志、内存曲线:"您判断的方向对,我们看下是限额问题还是应用增长——inspect 显示 OOMKilled 为 true,限额 2G,应用峰值 2.1G,限额是半年前按当时用量定的,建议上调到 3G 并加 80% 预警。"技术客户要的不是安抚是准确——数据给足,他会成为你的盟友而不是质疑者。我的分寸感:对业务用户讲比喻和影响,对技术客户讲证据和参数,一人一套语言但事实只有一套——两套话术绝不能互相矛盾,这是诚信底线。
5.19 容器工单速查总表 【理解】
标准回答: 把全章方法压成一张"现象→第一命令→去向"的速查表,这是我容器工单响应速度的来源,也是我留给值班同事的交接资产。用法:接到报障先对现象入座,执行第一命令拿证据,按"去向"决定自己处理还是转派——表里每行都对应本章某一节的完整展开,面试被问任何一个现象,我都能顺着速查表入口讲出完整的排查故事。
| 现象 | 第一命令 | 常见根因 | 去向 |
|---|---|---|---|
| 容器反复重启/Restarting | kubectl logs --previous | 配置错/依赖连不上/OOM | 一线(137 转内存方向) |
| Exited (137) | inspect 看 OOMKilled | 内存超限 | 一线调限额;泄漏转开发 |
| Exited (1) | logs 看崩溃遗言 | 应用异常退出 | 配置类一线;代码转开发 |
| ImagePullBackOff | describe 看 Events 原因串 | 凭据/名字/架构/网络 | 凭据一线修;仓库网络转二线 |
| 容器在跑但服务不通 | 容器内+宿主双层 curl | 监听 127.0.0.1/映射错 | 一线 |
| 外部访问不通 | 宿主 telnet 映射端口 | 防火墙/安全组 | 一线查;客户网络策略转客户 |
| docker logs 是空的 | exec 进容器找日志文件 | 写文件/输出缓冲 | 一线 |
| 容器里改的东西丢了 | —(机制问题) | 可写层随重建丢弃 | 一线(引导挂卷) |
| 节点磁盘涨满 | docker system df + du | 日志/镜像层堆积 | 一线清理+限额固化 |
| exec format error | inspect 查 Architecture | x86 镜像跑 ARM | 一线换镜像 |
| port already allocated | ss -lntp 查占用 | 端口被占 | 一线改规划 |
5.20 本章案例
- 【真实项目·银行 ARM 离线镜像体系(STAR 精简版,完整版见第 14 章)】背景:某银行内网环境加 TaiShan ARM 架构,9 个 BCS 数据面节点需要全套 arm64 镜像,内网拉不了任何海外镜像。任务:把镜像安全高效铺到全部节点。行动:编写批量加载脚本——for 循环遍历 tar.gz、排除 edge-manager 管理面镜像、逐个 docker load 并核对清单;期间处理过一次 x86 镜像混入导致的 exec format error,之后固化"镜像架构清单"检查项。结果:十几类镜像一次铺完,BCS 数据面纳管一次通过,脚本沉淀进《智能合约引擎安装部署指南》成为公司标准流程。
- 【真实工单·容器日志写满磁盘】背景:客户环境一台 Docker 宿主机磁盘 90% 告警,业务数据目录看着不大。排查:df -h 锁定 /var 所在分区,docker system df 显示容器层巨大,逐个看 /var/lib/docker/containers 下日志文件——一个调试期应用单文件写出 40 多 G。处理:先 truncate 原地清空立即释放(不用 rm 防句柄问题),再配 daemon.json 的 max-size 50m/max-file 3 并重启 docker,最后按应用方建议修复应用侧疯狂打印的调试日志。结果:磁盘回落到 35%,"日志限额进基线"写进交付检查单,同类问题未再复发。这单教我的是:容器磁盘问题九成在日志和镜像层,统计入口 docker system df 要成为条件反射。
- 【真实工单·CrashLoopBackOff 20 分钟闭环】见 5.13 追问 1:发布改错 Service 名→logs --previous 拿到数据库连接失败遗言→退出码 1 非 OOM→回滚发布恢复→FAQ 沉淀发布检查项。这单是"先恢复业务再修根因"原则的标准演示,也是我讲容器工单时最顺手的例子。
- 【真实项目·移动云镜像双轨体系(指向第 6/14 章)】公网拉取→内网私有仓库托管→ImagePullSecret 认证拉取的三层设计,运行时兜底策略(内网仓库/公共仓库/二进制),从根上消灭 Flannel Pod ImagePullBackOff 导致的节点 NotReady——这是"镜像供给问题升级为平台级方案"的完整故事,第 6 章分级转判和第 14 章案例都会引用。
5.21 章末自测清单(快问快答)
- 容器三大概念类比? → 镜像是类(只读模板)、容器是实例(+可写层)、仓库存镜像。
- 容器 vs 虚拟机一句话? → 容器共享内核的隔离进程(MB/秒级),VM 独立内核完整机器(GB/分钟级),互补不替代。
- 隔离靠什么? → Namespace 管隔离(视图),Cgroup 管限额(配额)。
- docker ps -a 不加 -a 会漏什么? → 已退出的容器,故障现场直接隐身。
- 退出码 137/143/1/0 分别查什么? → 137 查内存(OOMKilled)、143 查谁发的终止信号、1 看日志遗言、0 想它是不是跑完就退。
- CrashLoopBackOff 第一条命令? →
kubectl logs --previous(拿上次崩溃遗言),配 describe 看 Exit Code。 - docker logs 是空的? → 应用写文件日志或输出缓冲,进容器找 /var/log,Python 加 PYTHONUNBUFFERED=1。
- 重启策略三种? → no / on-failure[:N](异常才重启)/ always(含宿主机重启后拉起),常驻服务用 always。
- 容器里改的东西为什么重启就丢? → 可写层随容器重建丢弃;持久数据必须挂卷;docker restart 不丢、rm 重建丢。
- ImagePullBackOff 三种细分原因? → unauthorized(凭据)、manifest not found(名字/架构错)、timeout(网络/仓库)。
- exec format error 是什么? → 镜像架构与节点不匹配(x86 镜像跑 ARM),换 arm64 镜像。
- save/load 和 export/import 区别? → save/load 管镜像(保分层),export/import 管容器(丢历史)。
- CMD 和 ENTRYPOINT 区别? → CMD 默认值可被 run 参数整体覆盖;ENTRYPOINT 固定入口参数追加;exec 格式才能优雅停机。
- 容器里 free 显示 64G 但限额 2G? → 容器默认看到宿主机资源,读 cgroup 文件才是真相;JVM 要显式 -Xmx。
- 容器磁盘两大凶手? → 悬空镜像层堆积 + json-file 日志无上限;统计入口 docker system df。
- truncate 和 rm 清日志的区别? → truncate 原地清空立即释放;rm 后进程持句柄空间不释放,要 lsof 查 deleted。
- K8s 节点上看容器用什么命令? → crictl(ps/logs/inspect/pull),kubectl 不可用时的节点级后手。
- crictl 报连接 socket 失败? → /etc/crictl.yaml 显式绑定 containerd.sock(避坑清单第 2 条)。
- 内网拉不到镜像的完整流程? → 确认约束(公网/仓库/架构)→ 外网准备 → save/load 或私有仓库 → 验证清单固化。
- 容器工单四要素问什么? → 现象、时间、范围、变更——"最近改了什么"命中率最高。
- 端口映射访问不通的三层验证? → 容器内自测 → 宿主机映射端口 → 外部 telnet——逐层切割责任边界。
- 容器内通宿主不通的头号原因? → 应用只监听 127.0.0.1,改 bind 0.0.0.0。
- 为什么禁用 latest tag? → 不可追溯、缓存不可控——显式版本 + digest 审计。
- 客户问"什么放容器什么放虚机"? → 容器卖交付效率(无状态/微服务),虚机卖隔离强度(强隔离/特定内核/重状态)——两层叠加是标准形态。
- 容器工单速查表的用法? → 现象入座→第一命令拿证据→按去向分流——每行对应本章一节的完整展开。