简介《Kubernetes课件分享》是一份面向容器与云原生初学者、运维开发人员及课堂教学场景的入门级演示文稿围绕容器集群管理的基础知识展开帮助读者建立集群架构与核心对象体系的整体认知。压缩包内仅含1个pptx演示文稿文件大小约1.25MB篇幅精炼适合课堂讲授、自学梳理和面试前快速复盘。课件按章节讲解集群组件职责、最小部署单元、控制器、服务、标签与命名空间等核心对象并介绍集群状态查看、资源监控、组件与应用日志管理、应用创建升级回滚和弹性伸缩等常用操作。目录还延伸至调度、存储、集群安全等主题以概念图、命令示例和配置片段串联关键知识点便于对照实操查漏补缺。目前已有2191人学习适合按章节做笔记、课堂演示或搭建学习路线时参考。1. 控制循环为什么 k8s 里没有“安装完成”这个状态很多人第一次接触 k8s会带着装软件的心智敲一条命令等一个 done服务就“在那儿”了。这套课件里反复出现的一组 YAML 会打破这个直觉——同一份 Deployment 提交三次集群的反应可能完全不同第一次创建三个 Pod第二次什么都不做第三次因为有人手动删了一个 Pod 又补回来一个。k8s 不是部署工具它是一组长期运行的控制循环把实际状态往你声明的期望状态上拽。理解这一点之后后面所有看着奇怪的行为——滚动升级卡住、Pod 反复重启、删掉又被拉起来——都有统一解释。这份资料把架构、调度、存储、安全串成一条线正好可以从控制循环这个根上往下拆。2. 声明式 API 与 Pod一份 YAML 提交之后到底发生了什么2.1 Pod 是逻辑概念infra container 才是它真正的骨架课件里有一句话容易被略过Pod 是最小部署单元是一组容器的集合容器之间共享网络命名空间。问题在于容器天然是被 Namespace 隔离的那“共享”是怎么做到的Pod 本身没有任何实体它只是 etcd 里的一个对象、kubelet 眼中的一个编排单位真正把“一组容器”捏成一个整体的是那个先启动的小容器。常见的实现思路是kubelet 在创建 Pod 时先拉起一个极小的 infra container也叫 pause 容器它只做一件事——持有网络命名空间不放。随后业务容器以 join 这个命名空间的方式启动于是所有容器看到的是同一张网卡、同一个 IP、同一份路由表彼此之间用 127.0.0.1 就能通信。这也解释了两个现象为什么 Pod 内容器不能各自占用同一个端口以及为什么 Pod 重建后 IP 会变——IP 是挂在 infra container 的命名空间上的它没了网络栈就没了。存储共享走的是另一条路和命名空间无关同一个 Volume 被同时挂载到多个容器的挂载点上容器看到的路径各自独立但底层指向同一份数据。课件里给出的形态大致如下两个容器分工不同却共用一份目录apiVersion: v1 kind: Pod metadata: name: web spec: containers: - name: nginx image: demo/nginx-php:1.0 volumeMounts: - name: app-logs # 容器内的日志目录 mountPath: /usr/local/nginx/logs - name: exporter image: demo/log-agent:1.0 volumeMounts: - name: app-logs # 同一个 volume挂到另一个容器的不同路径 mountPath: /data/logs volumes: - name: app-logs # volume 名字必须与上面两个 mount 一一对应 emptyDir: {}这里的关键不是语法而是name这条隐式契约volumeMounts[].name必须能在volumes[].name里找到同名的条目否则 Pod 会一直卡在 ContainerCreating。后面避坑章节会专门讲这个错误因为课件里的示例本身就埋了一个这样的坑。现代 k8s 还允许通过shareProcessNamespace: true让 Pod 内容器共享 PID 命名空间这样容器 A 能看见容器 B 的进程。它默认关闭因为大多数“亲密性”需求只涉及网络和文件把进程视图也合并进来反而增加了误杀风险。2.2 一条 kubectl apply 的完整链路从 apiserver 到容器运行时初学者常有的错觉是 kubectl 直接连到了某台 Node 上命令它去启动容器。实际链路要绕得多而且每一段都是“看”而不是“推”。把课件里的组件图按时间顺序摊开大致是这么一条链第一步kubectl 读 kubeconfig 里的集群地址和客户端证书把 YAML 序列化后发到 kube-apiserver 的 REST 接口。apiserver 依次做三件事认证这张证书是谁、鉴权这个身份能不能在 default 命名空间创建 deployment、准入控制默认值填充、ResourceQuota/LimitRange 校验、ServiceAccount 挂载注入。全部通过后才写入 etcd。注意此时集群里还没有任何容器只有一条记录。第二步kube-controller-manager 里的 Deployment 控制器一直在 watch 这类对象发现新的 Deployment 后它并不直接建 Pod而是创建一个 ReplicaSet把副本数、Pod 模板抄过去。第三步ReplicaSet 控制器发现自己该有 3 个副本、实际 0 个于是创建 3 个 Pod 对象写入 etcd。此刻 Pod 的spec.nodeName是空的。第四步kube-scheduler watch 到这些“未绑定”的 Pod执行过滤和打分选定节点后写回一个 Binding 结果。这一步只改一个字段不碰容器。第五步目标节点上的 kubelet watch 到“分配给我的 Pod”开始真正干活调用 CRI 拉镜像、创建沙箱就是 2.1 的 infra container、调用 CNI 配网络、调用 CSI 挂卷、启动业务容器。每一步的成败都通过 Pod 的 status 上报回 apiserver。第六步kube-proxy watch Service 和 EndpointSlice 的变化在本机维护转发规则让 Service 的 ClusterIP 能落到具体的 Pod IP 上。整条链路里kubectl 只和 apiserver 说过话组件之间全部靠 watch etcd 或 apiserver 来感知变化。这就是声明式的代价与红利任何一环挂掉只要状态还在 etcd 里恢复后能接着收敛但也意味着“命令执行成功”和“服务真的可用”之间隔了好几跳中间任何一跳出问题你看到的都是同一句deployment created。想亲眼看见这个渐进过程用--watch盯住 Pod 的状态迁移最直观# 开启一个窗口持续观察创建后会依次看到 Pending - ContainerCreating - Running kubectl get pod -w # 只看某个 Deployment 下的 Pod并在滚动升级时观察新旧 Pod 的交替 kubectl get pod -l appweb -w # 查看某个 Pod 的完整状态流转原因Events 在输出末尾从下往上读 kubectl describe pod web-7d9c8f6b5-2xk4l-w的价值在于它把“离散的快照”变成了“连续的状态机”。很多问题在快照里看不出来比如一个 Pod 在 ContainerCreating 停留了 40 秒才变 Running快照只会告诉你 Running-w会告诉你中间等了多久——那 40 秒通常就是镜像在拉。2.3 Deployment、ReplicaSet、Pod三层控制器如何各管一段课件把 Deployment 归到“无状态应用部署”但没有解释为什么中间要夹一个 ReplicaSet。如果把这三层拆开看每一层的职责其实非常清晰Deployment 记录“我想要什么版本、要几个”ReplicaSet 记录“某个版本要几个”Pod 是真正的实例。多出这一层的全部意义在于版本切换时新旧两套副本需要同时存在。滚动升级时Deployment 控制器新建一个 ReplicaSet名字里带 Pod 模板的哈希把它从 0 扩到目标数同时把旧 ReplicaSet 从目标数缩到 0两边按maxSurge和maxUnavailable控制节奏。回滚就更简单了——旧 ReplicaSet 对象并没有被删只是副本数被缩到 0执行 undo 就是把它重新扩起来、把当前的缩下去。这也解释了为什么回滚几乎瞬间完成镜像早就拉过了模板也没变。# 两个 RS 并存DESIRED 为 0 的那个就是上一版本随时可以回滚 kubectl get rs -l appweb # 查看历史版本号与对应的镜像--to-revision 用的就是这个号 kubectl rollout history deployment/web # 只回滚到指定版本而不是无脑退回上一版 kubectl rollout undo deployment/web --to-revision2revisionHistoryLimit决定了旧 ReplicaSet 保留多少个默认 10。超过的会被清理掉清理后再想--to-revision回到那个版本就会报 not found。这是一个很容易被忽略的“后悔药有效期”。2.4 镜像拉取策略imagePullPolicy 的默认值比你想的更聪明课件列了三个取值IfNotPresent、Always、Never并说 IfNotPresent 是默认值。这句话对但不完整漏掉的那半句才是线上事故的高发区默认值并不是固定的它取决于镜像的 tag。镜像写法默认 imagePullPolicy实际行为demo/app:1.0IfNotPresent节点上有就复用没有才拉demo/app:latestAlways每次创建 Pod 都去仓库问一次demo/app省略 tagAlways等价于 latest同样每次拉demo/appsha256:...IfNotPresent按摘要校验内容天然不可变规则背后是一套朴素的假设带具体 tag 的镜像被认为内容不会变而 latest 被认为是流动的。问题在于很多人会用同一个 tag 反复覆盖推送比如永远推demo/app:test。这时 IfNotPresent 会让节点上的旧镜像一直生效代码怎么改都不生效重启 Pod、删 Pod 都没用只有换节点或者手动清镜像才行。这种问题的恶心之处在于它不报错只是行为“像上个版本”。课件里还提到了imagePullSecrets那是私有仓库的认证凭据。它的生效范围是 Pod 级别需要显式写在spec.imagePullSecrets里或者挂在 Pod 所用的 ServiceAccount 上——后者更省事因为同一个命名空间下的 Pod 会自动继承。3. 调度与资源配额requests 和 limits 为什么不能随便写3.1 scheduler 的两阶段Filter 先排除Score 再排序调度的输入是一个待绑定的 Pod 和一份节点清单输出是一个节点名。整个过程被拆成两个阶段过滤和打分。过滤阶段回答“这个节点能不能放”任何一条硬性条件不满足就出局。常见的过滤项包括节点剩余可分配资源是否覆盖 Pod 的 requests 之和、nodeSelector 与节点亲和性能否匹配、节点污点是否被 Pod 容忍、Pod 需要的 PVC 能否在该节点所在区域绑定、拓扑分布约束是否还允许再放一个。全部节点被过滤掉时Pod 就停在 Pending事件里会写清楚每个节点被排除的原因比如0/3 nodes are available: 3 Insufficient cpu。打分阶段回答“放哪个更好”对通过过滤的节点逐项打分再加权求和。常见的打分插件有资源均衡度让各节点使用率接近、镜像本地性节点上已有该镜像则加分能省一次拉取、Pod 间亲和性尽量和指定的 Pod 靠近、拓扑分布尽量打散到不同可用区。分数最高的节点胜出相同分数时随机挑一个这也是为什么同一份 Deployment 反复重建Pod 的分布看起来总在变——调度本身没有记忆。这里有个反直觉的点值得强调调度看的是 requests 的累加值不是节点的真实使用率。一台 16 核的机器上跑着一个吃满 15 核但 requests 只写了 1 核的 Pod调度器依然认为它还有 15 核可分配。反过来一个 requests 写得很夸张的 Pod 会让节点“看起来”满了实际 CPU 大量闲置。想看清这笔账得直接查节点的分配情况# Allocated resources 一段列出的是所有 Pod requests 之和与节点容量的比值 # 它和 kubectl top node 看到的实际使用率经常差很远两个数要分开看 kubectl describe node node-name | sed -n /Allocated resources/,/Events/p3.2 requests 决定调度与 QoSlimits 决定 cgroup 与生死这两个字段经常被混为一谈其实它们作用在不同的层面语义完全不能互换。requests是“我至少要这么多”它不限制任何东西只是给调度器看的账本同时决定 Pod 的 QoS 等级、HPA 的百分比基线、以及节点资源不足时谁先被驱逐。limits是“我最多只能用这么多”它被写进容器的 cgroup 配置是真正的硬约束。apiVersion: v1 kind: Pod metadata: name: web spec: containers: - name: java image: demo/java-app:1.0 resources: requests: memory: 64Mi # 调度账本节点必须至少有 64Mi 未分配 cpu: 250m # 250m 0.25 核调度按这个数扣减 limits: memory: 128Mi # cgroup memory.max超过就 OOM cpu: 500m # cgroup cpu.max超过就限流不杀进程CPU 和内存在这件事上表现完全不同这个差异必须记住。CPU 是“可压缩资源”超了 limits 只是被内核的 CFS 配额限流进程还在跑表现为响应变慢、延迟毛刺容器里能看到nr_throttled计数增长。内存是“不可压缩资源”超了 limits 就是 OOM Killer 直接杀进程容器退出码 137kubectl 里看到的是OOMKilled和不断上涨的 RESTARTS。requests和limits之间留多少余量是个纯经验问题。写得太紧Java 这类需要预留堆外内存的进程会被误杀写得太松集群整体装箱率下降成本上去了。比较稳的做法是内存的 requests 和 limits 设成相等避免被 OOM 的同时也拿到更高的 QoSCPU 的 requests 按稳态设、limits 按峰值设允许一定程度的超卖。3.3 三种 QoS 等级与节点压力下的驱逐顺序只要写了 requests 和 limitsk8s 就会给 Pod 打上一个隐含的标签——QoS 等级。判定规则很机械但组合起来容易记混QoS 等级判定条件oom_score_adj 大致取值节点压力时Guaranteed所有容器都设置了 requests 和 limits且 cpu、memory 两项都相等最低约 -997最后被驱逐Burstable至少一个容器设了 requests但不满足 Guaranteed按 requests 占比计算中间BestEffort所有容器都没设 requests 和 limits最高1000最先被驱逐oom_score_adj决定了系统级内存紧张时内核先挑谁下手数值越高越容易被杀。BestEffort 的 Pod 相当于把自己放在了最前面这在测试环境无所谓在生产环境里意味着节点一紧张最先死的就是那些没写 resources 的服务——而它们往往是最早被部署、最没人管的那批。节点压力驱逐是另一套机制由 kubelet 独立完成触发条件是节点资源低于evictionHard阈值比如可用内存跌破 100Mi。它按 QoS 从低到高挑 Pod同等级内再按“用量超出 requests 的程度”排序。这解释了为什么有时候明明是别的服务把内存吃光了被杀的却是你的 Pod——只要你的 requests 写得比实际用量低很多超出的比例就会很靠前。课件里没有展开 QoS 的判定细节但这是理解“为什么我的 Pod 老是被杀”绕不开的一环。一个实用的自检方式是直接查 Pod 的 qosClass 字段# 一眼看出这个 Pod 被判定成了哪个等级 kubectl get pod web -o jsonpath{.status.qosClass}{\n} # 批量检查某个命名空间下所有 Pod 的 QoS找出 BestEffort 的漏网之鱼 kubectl get pod -n default -o custom-columnsNAME:.metadata.name,QOS:.status.qosClass3.4 HPA 的百分比基线为什么没写 requests 就扩不了容自动扩缩容那一条命令看起来最省事kubectl autoscale deployment web --min10 --max15 --cpu-percent80。但这里的 80 不是“CPU 使用率 80%”而是“当前 CPU 用量达到 requests 的 80%”。也就是说HPA 的分母是 requests不是节点核数。后果很直接如果容器没写 requests或者写了但 HPA 拿不到指标kubectl get hpa的 TARGETS 一栏会显示unknown/80%扩缩容逻辑直接失效永远不会触发。这也是 metrics-server 必须先在集群里跑起来的原因——HPA 控制器是通过 metrics API 去取这些数的。还有两个容易被忽略的行为扩容是激进的缩容是有冷却窗口的默认 5 分钟避免流量抖动导致副本数反复横跳HPA 只调整副本数不负责加节点节点资源不够时新 Pod 会一直 Pending那是另一层节点自动扩缩要解决的问题。4. 监控与日志三条数据链路别指望一条命令看全4.1 metrics-server 的数据从哪来kubelet Summary API 与 cAdvisorkubectl top node能打出 CPU 和内存的百分比这个数不是 apiserver 自己算的也不是从 etcd 里读的。整条链路是这样的每个节点的 kubelet 内嵌了 cAdvisor持续采集本机所有容器的 cgroup 统计kubelet 把这些数据整理成 Summary API/stats/summarymetrics-server 定期向所有 kubelet 拉取聚合成一份集群级的资源视图最后通过 APIService 机制注册成metrics.k8s.io这个聚合 APIkubectl top 读的就是它。这条链路决定了 metrics-server 的定位它只提供近实时的瞬时值不存储历史。所以kubectl top只能回答“现在多少”回答不了“过去一小时峰值多少”。要看趋势就得另起一套——通常是 Prometheus 定期抓 kubelet 的/metrics/cadvisor把样本落进时序库。课件把 metrics-server 和 cAdvisor 放在一起讲其实是在提示这条链路的上下游关系。# 集群级信息apiserver 地址、DNS 服务地址等用来确认 kubectl 连的是哪个集群 kubectl cluster-info # 节点与 Pod 的实时资源占用数据源就是 metrics-server kubectl top node kubectl top pod --sort-bymemory # 确认聚合 API 是否注册成功没就绪时 top 会直接报错 kubectl get apiservice v1beta1.metrics.k8s.io最后那条命令是排查kubectl top报错的第一个动作。如果 APIService 的 AVAILABLE 是 False问题一定在 metrics-server 这一侧——Pod 没起来、证书没配好、或者 kubelet 的只读端口被禁用跟你要查的业务 Pod 一点关系都没有。4.2 kubectl top、describe、--watch 分别读的是什么这三个命令经常被当成同一类工具用其实它们读的是完全不同的数据源回答的也是不同的问题。kubectl top读聚合 API给的是数值用于判断“现在忙不忙”。kubectl describe读的是对象本身加上关联的 Event给的是状态和原因用于判断“为什么卡住了”。kubectl get --watch读的是对象的版本流给的是变化过程用于判断“它是什么时候变成这样的”。知道这个分层排查时就不会乱试。Pod 起不来的时候盯着 top 看没有意义因为没起来的容器根本没有 cgroup 数据反过来服务变慢的时候翻 describe 的 Events 也看不出什么因为事件只记录状态变更不记录性能拐点。describe输出的末尾那段 Events 是最有价值的部分但要按时间从下往上读——最新的事件在最下面。常见的几条信号值得记住FailedScheduling指向调度器FailedMount指向存储或 SecretFailed加Failed to pull image指向镜像仓库Unhealthy指向探针。每条事件的末尾还带着发事件的组件名能直接告诉你该去看哪个组件的日志。4.3 组件日志与应用日志journalctl 与 kubectl logs 的边界在哪课件把日志分成了“k8s 组件日志”和“k8s 应用日志”两类这个划分对应的是两套完全不同的采集路径混用会白费很多时间。kubelet 这类以 systemd 服务形式运行在节点上的组件日志进了 journald只能用journalctl -u kubelet看加上-f可以持续跟踪。这条路子在某些发行版上还要注意 journald 自己的持久化配置默认可能只存在内存里重启就没了。apiserver、controller-manager、scheduler、kube-proxy 这些通常是以静态 Pod 的形式跑在 kube-system 命名空间里的它们的日志要用kubectl logs看加上-n kube-system。kube-proxy 是 DaemonSet每个节点一个名字后面带随机后缀得先get pod找到具体那个。应用容器的日志是第三条路。kubectl logs读的是容器运行时写在节点磁盘上的标准输出文件路径形如/var/log/pods/ns_pod_uid/container/0.log。这意味着两件事它只能看到 stdout 和 stderr写进容器内文件里的日志抓不到它默认只看当前这一份容器崩溃重启后上一次的输出要靠--previous才能翻出来。后者在排查 CrashLoopBackOff 时几乎是必用技巧——正在跑的容器可能刚起来还没输出真正有用的堆栈在上一份里。# 跟踪某个多容器 Pod 里指定容器的日志不写 -c 时默认第一个 kubectl logs -f web-7d9c8f6b5-2xk4l -c nginx # 查看上一次崩溃实例的日志排查反复重启的关键入口 kubectl logs web-7d9c8f6b5-2xk4l --previous # 节点上的组件日志kubelet 走 journald加 -f 持续跟踪 journalctl -u kubelet -f --since 10 min ago4.4 日志落盘hostPath、emptyDir 与 sidecar 采集的取舍容器标准输出的日志由 kubelet 负责轮转参数是containerLogMaxSize和containerLogMaxFiles默认 10Mi 和 5 份。这个量级对付调试够了对付审计或者需要长期留存的业务日志远远不够。所以课件里给了一个把日志目录挂出来的例子思路是把容器内的日志路径映射到宿主机目录再由节点上的采集 agent 统一收走。那份示例里有个细节值得单独拎出来hostPath的type字段。写Directory表示“这个目录必须已经存在”不存在就挂载失败Pod 卡在 ContainerCreating写DirectoryOrCreate则会自动创建。生产上如果目录是人工预建的用Directory更安全能及早暴露配置错误如果希望自愈就用DirectoryOrCreate。这个选择在课件里没展开但它决定了你是想“快速失败”还是“自动兜底”。修正后的形态大致是这样注意 volumeMounts 和 volumes 两处的 name 必须严格对应apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: web image: demo/nginx-php:1.0 volumeMounts: - name: logs # 引用下面 volumes 里的同名条目 mountPath: /usr/local/nginx/logs volumes: - name: logs # 名字与上面的 volumeMounts 对应 hostPath: path: /tmp/logs type: DirectoryOrCreate # 目录不存在时自动创建避免挂载失败三种方案的取舍可以这么理解emptyDir生命周期跟 Pod 绑死Pod 一删日志全没适合临时缓存和容器间交换文件hostPath把日志留在节点上Pod 删了文件还在但换节点就找不到了且多个 Pod 写同一个目录容易互相覆盖sidecar 或 DaemonSet 采集则是把日志实时送出去代价是多一层组件和一份资源开销。选哪个取决于日志是要“事后翻一下”还是“必须长期可查”。5. 避坑与排查五类“部署成功却跑不起来”的现场5.1 五条踩坑记录卷名对不上Pod 卡在 ContainerCreating。现象是kubectl get pod一直显示 ContainerCreating几分钟不动。原因是volumeMounts[].name写的是logs而volumes[].name写的是data两个名字对不上kubelet 找不到要挂的卷只能一直重试。解决方式是kubectl describe pod看 Events会看到volume logs not found这类提示把两处名字改成一致即可。这个错误在复制粘贴 YAML 时特别高发因为名字不是必填校验项apiserver 不会在提交时拦你。同一个 tag 反复覆盖推送代码改了却没生效。现象是镜像明明重新构建推送了Pod 也重建了行为还是老样子。原因是非 latest 的 tag 默认imagePullPolicy: IfNotPresent节点上已经有同名 tag 的镜像就直接复用压根不去仓库问。解决方式是改用不可变 tag带构建号或 commit 短哈希或者临时把策略改成 Always 验证一次。长期看用 digest 引用是最稳的。requests 写得过大Pod 永远 Pending。现象是 Pod 一直 Pendingdescribe 里写着0/3 nodes are available: 3 Insufficient cpu。原因是 requests 是调度账本写多少就要扣多少跟实际用多少无关。解决方式是先看kubectl describe node里的 Allocated resources算出真实余量再回头调小 requests或者给节点池扩容。顺带一提如果 requests 完全没写Pod 能调度上去但 QoS 会掉到 BestEffort节点一紧张第一个被杀。内存 limits 卡太死容器反复 OOMKilled。现象是 RESTARTS 一路涨describe 的 Last State 显示Reason: OOMKilled、Exit Code: 137。原因是内存是不可压缩资源超过 cgroup 上限就是内核直接杀进程。解决方式是先用kubectl top pod观察稳态用量把 limits 提到峰值的 1.2 倍以上Java 应用还要注意堆内存只占进程内存的一部分堆外还有元空间、线程栈、直接内存把 limits 设成堆大小是必然翻车的。滚动升级卡住rollout status 一直 waiting。现象是kubectl rollout status挂在那里不动get pod能看到新 ReplicaSet 的 Pod 一直没 Ready。原因是新 Pod 没通过 readinessProbe可能是探针路径写错、启动时间不够、或者镜像根本拉不下来。解决方式是先describe新 Pod 看 Events 定位是探针还是镜像探针问题就补initialDelaySeconds或改用 startupProbe镜像问题就查仓库凭据。另外别忘了revisionHistoryLimit会把旧 ReplicaSet 清掉回滚窗口是有限的。5.2 从 Events 往上倒推的固定排查顺序出问题时的本能反应是到处敲命令效率很低。比较稳的顺序是自下而上先确认 Pod 处于什么阶段再看它为什么停在那儿最后才去看应用自己输出了什么。第一层看状态kubectl get pod的 STATUS 列已经能分流一大半问题。Pending 意味着还没调度ContainerCreating 意味着调度完了但容器没起来ImagePullBackOff 是镜像问题CrashLoopBackOff 是容器起来又挂了Running 但没流量则是 Service 或探针的问题。第二层看原因kubectl describe pod的 Events 段落给出具体原因按时间从下往上读。这一步基本能覆盖 80% 的故障因为 kubelet 会把每一次失败都记成事件。第三层看应用状态是 Running 但行为不对才轮到kubectl logs。别忘了--previous崩溃重启的场景下当前实例的日志往往是空的。第四层看节点Pod 被驱逐、节点 NotReady、磁盘压力这些要看kubectl describe node和节点上的journalctl -u kubelet。到了这一层问题通常已经不是单个 Pod 的事了。5.3 一套可以直接抄走的排查命令# 1. 定位找出异常的 Pod按重启次数排序最快暴露问题 kubectl get pod -A --sort-by.status.containerStatuses[0].restartCount # 2. 归因Events 从下往上读重点看 FailedScheduling / FailedMount / Unhealthy kubectl describe pod pod-name -n ns | sed -n /Events/,$p # 3. 应用侧当前日志看不出东西时翻上一次崩溃实例的输出 kubectl logs pod-name -n ns --previous --tail200 # 4. 节点侧确认是不是节点本身的问题磁盘压力、PLEG 异常等 kubectl describe node node-name | sed -n /Conditions/,/Addresses/p # 5. 组件侧kubelet 的日志里能看到 CRI 和 CNI 的原始报错 journalctl -u kubelet --since 15 min ago | grep -i -E error|failed这套顺序的价值在于它是有方向的每一步都在缩小范围而不是碰运气。养成习惯之后多数故障能在五分钟内定位到层剩下的时间才是解决。6. 存储与安全的设计取舍复杂度为什么留给了使用者把课件里存储和安全两块放在一起看会发现同一个设计取向k8s 只定义接口不规定实现。存储侧抽出 CSI网络侧抽出 CNI运行时抽出 CRI安全侧抽出 RBAC 和准入控制链。好处是生态能长出来代价是所有“默认值”只保证能跑不保证跑得好。存储这块的取舍最典型。hostPath简单直接但把 Pod 和节点绑死了Pod 换节点数据就丢还会带来权限和配额上的隐患——容器往宿主机目录里写什么宿主机就得接着。往上一层是 PV/PVC把“我要多大、什么访问模式”和“具体用哪块盘”解耦ReadWriteOnce和ReadWriteMany的差别会直接决定你的 Deployment 能不能水平扩容。再往上是 StorageClass 动态供给PVC 一提交就自动创建底层卷。有状态服务还要多一层StatefulSet 的volumeClaimTemplates让每个副本拥有独立的 PVC缩容时不删卷扩回来还能接上——这是 Deployment 做不到的。安全这块的取舍更隐蔽。Secret 只是 base64 编码不是加密任何有读权限的人kubectl get secret -o yaml就能还原真正的保护来自 etcd 的静态加密和 RBAC 的最小权限。ServiceAccount 是 Pod 访问 apiserver 的身份默认挂载的 token 让容器内的进程天然拥有一定权限不需要时应该显式关掉自动挂载。NetworkPolicy 定义了 Pod 间的流量规则但它依赖 CNI 插件实现装了策略却没有支持的插件规则会被静默忽略——这种“配了但没生效”比报错更危险。我一般会用一套很笨的验证方法来兜底把 Pod 的 spec 拉出来逐项确认 requests/limits 是否齐全、QoS 等级是什么、探针有没有配、卷的类型和访问模式对不对、ServiceAccount 是不是最小权限。这套动作做熟了大概两分钟但能挡掉绝大多数上线后才发现的问题。# 上线前的一次性体检QoS、卷、ServiceAccount、探针一次看全 kubectl get pod pod-name -n ns -o jsonpathQOS{.status.qosClass}{\n}SA{.spec.serviceAccountName}{\n}VOL{.spec.volumes[*].name}{\n}从那以后我每次提交工作负载前都会强制走一遍这个检查尤其是 requests 和探针这两项——它们不出问题时毫无存在感出问题时又都发生在半夜。这份课件把概念、组件、调度、存储、安全串在同一条线上当作随身查阅的底稿正好。希望帮到你。本文还有配套的精品资源点击获取