K8s Pod 的生命周期——从 kubectl apply 到 Running 全链路拆解(浅谈)

K8s Pod 的生命周期------从 kubectl apply 到 Running 全链路拆解(浅谈)

大家好,这里是彩妙呀~

在云原生时代,Kubernetes(K8s)已经成为了容器编排的事实标准。不管你是做运维还是做开发,都绕不开一个最基础的概念------Pod

很多小伙伴学了 kubectl apply -f pod.yaml ,Pod 跑起来了,但不知道这条命令背后到底发生了什么。当你知道了Pod底层运行的原理,排查问题的时候才不会抓瞎。

下面,彩妙将会带着大家从零开始,追踪 Pod 从创建到删除的完整生命周期。

官方文档解释 Pod https://kubernetes.io/docs/concepts/workloads/pods/


一、Pod 是什么?------先搞懂基本概念

什么是 Pod?

官方文档的解释:

A Pod is the smallest deployable unit of computing that you can create and manage in Kubernetes. A Pod is a group of one or more containers, with shared storage and network resources, and a specification for how to run the containers.

翻译:Pod 是 K8s 里最小的调度单位 ,它不是容器,而是容器的"壳"。一个 Pod 里面可以放一个或多个容器,这些容器共享网络(同一个 IP)和存储(同一个 Volume)。

为什么要这样设计?因为有些容器天生就该"绑在一起"------比如你的应用容器和它的日志采集容器,它们需要共享网络和存储,但又各自独立运行。


二、Pod 生命周期总览------一张图看清全貌

Pod 的一辈子可以分为 7 个阶段:

<注意:上面每一个箭头都对应着一件具体的事。下面我们一个一个拆开看。>


阶段①:kubectl apply 敲下去,第一站发生了什么?

kubectl 做了什么

kubectl 把你的 YAML 文件打包成一个 HTTPS 请求,发给了 API Server。

我们先准备一个最简单的 Pod 定义,后面所有讨论都基于它:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
bash 复制代码
# 提交这个 Pod
kubectl apply -f pod.yaml

API Server 的"三道门"

请求到了 API Server,需要过三关才能入库:

复制代码
认证(Authentication)
  └─ 你是谁?→ 检查证书/Token,没通过返回 401
     ↓ 通过
鉴权(Authorization)
  └─ 你有权限做这个操作吗?→ 检查 RBAC 权限,没通过返回 403
     ↓ 通过
准入控制(Admission Control)
  └─ 你的请求合规吗?→ 检查是否设了资源限制、是否违反安全策略
     ↓ 通过
写入 etcd,状态标记为 Pending

<注意:准入控制这一步很关键------Istio 就是利用 MutatingAdmissionWebhook,在你提交的 Pod 里自动注入一个 Envoy Sidecar 容器。这也是后续 Service Mesh 章节的重点内容。>

用生活中的例子类比:

你进公司大楼------先刷工卡(认证),前台看你的权限能不能进这个楼层(鉴权),安检看你有没有带违禁品(准入)。三关全过,你才能进去(写入 etcd)。


四、阶段②:谁来给 Pod 选机器?------Scheduler 调度

Pod 写入了 etcd,但此时它还不知道该去哪台机器------.spec.nodeName 是空的。

Scheduler(调度器)一直在 Watch etcd,发现来了一个新的、还没分配节点的 Pod,立刻开始工作:

调度分两步

第一步:预选(Filtering)------海选淘汰

把不满足条件的 Node 全部过滤掉:

  • 资源不够的(CPU/内存不足)→ 淘汰
  • 端口冲突的 → 淘汰
  • 打上了污点(Taint)且你没有声明容忍(Toleration)的 → 淘汰

第二步:优选(Scoring)------决赛打分

剩下的 Node,给每台打分,哪台资源最空闲,分数最高,就选哪台。

bash 复制代码
# 看看你的 Pod 被调度到了哪台机器
kubectl describe pod my-first-pod | grep "Node:"
# 输出示例:Node: vm-0-7-ubuntu/10.0.0.7

人话版:Scheduler 就像房产中介。你告诉他"我要租房子(Pod)",他先过滤掉不满足要求的(面积不够、没电梯),再从剩下的里面挑最合适的一套,告诉你"就这套了"。

新手常见坑:Pod 一直 Pending

bash 复制代码
# 错误场景:提交了 Pod,但一直是 Pending 状态
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
my-first-pod   0/1     Pending   0          5m

排查思路

bash 复制代码
# 看 Events,找原因
kubectl describe pod my-first-pod
# 如果看到 "0/1 nodes are available"
# → 要么资源不够,要么 Node 有污点不让调度

五、阶段③:容器真正跑起来------Kubelet 创建容器

Scheduler 选好 Node 后,更新 Pod 的 .spec.nodeName。接下来,目标 Node 上的 Kubelet 登场了。

Kubelet 的调用链

Kubelet 自己不直接创建容器,它有一条调用链:

复制代码
Kubelet
  → CRI(gRPC 接口)
    → containerd(容器管理)
      → containerd-shim(进程守护)
        → runc(真正创建容器)

#mermaid-svg-64Vc2xmZSDtNr02s{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-64Vc2xmZSDtNr02s .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-64Vc2xmZSDtNr02s .error-icon{fill:#552222;}#mermaid-svg-64Vc2xmZSDtNr02s .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-64Vc2xmZSDtNr02s .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-64Vc2xmZSDtNr02s .marker{fill:#333333;stroke:#333333;}#mermaid-svg-64Vc2xmZSDtNr02s .marker.cross{stroke:#333333;}#mermaid-svg-64Vc2xmZSDtNr02s svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-64Vc2xmZSDtNr02s p{margin:0;}#mermaid-svg-64Vc2xmZSDtNr02s .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-64Vc2xmZSDtNr02s .cluster-label text{fill:#333;}#mermaid-svg-64Vc2xmZSDtNr02s .cluster-label span{color:#333;}#mermaid-svg-64Vc2xmZSDtNr02s .cluster-label span p{background-color:transparent;}#mermaid-svg-64Vc2xmZSDtNr02s .label text,#mermaid-svg-64Vc2xmZSDtNr02s span{fill:#333;color:#333;}#mermaid-svg-64Vc2xmZSDtNr02s .node rect,#mermaid-svg-64Vc2xmZSDtNr02s .node circle,#mermaid-svg-64Vc2xmZSDtNr02s .node ellipse,#mermaid-svg-64Vc2xmZSDtNr02s .node polygon,#mermaid-svg-64Vc2xmZSDtNr02s .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-64Vc2xmZSDtNr02s .rough-node .label text,#mermaid-svg-64Vc2xmZSDtNr02s .node .label text,#mermaid-svg-64Vc2xmZSDtNr02s .image-shape .label,#mermaid-svg-64Vc2xmZSDtNr02s .icon-shape .label{text-anchor:middle;}#mermaid-svg-64Vc2xmZSDtNr02s .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-64Vc2xmZSDtNr02s .rough-node .label,#mermaid-svg-64Vc2xmZSDtNr02s .node .label,#mermaid-svg-64Vc2xmZSDtNr02s .image-shape .label,#mermaid-svg-64Vc2xmZSDtNr02s .icon-shape .label{text-align:center;}#mermaid-svg-64Vc2xmZSDtNr02s .node.clickable{cursor:pointer;}#mermaid-svg-64Vc2xmZSDtNr02s .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-64Vc2xmZSDtNr02s .arrowheadPath{fill:#333333;}#mermaid-svg-64Vc2xmZSDtNr02s .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-64Vc2xmZSDtNr02s .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-64Vc2xmZSDtNr02s .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-64Vc2xmZSDtNr02s .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-64Vc2xmZSDtNr02s .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-64Vc2xmZSDtNr02s .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-64Vc2xmZSDtNr02s .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-64Vc2xmZSDtNr02s .cluster text{fill:#333;}#mermaid-svg-64Vc2xmZSDtNr02s .cluster span{color:#333;}#mermaid-svg-64Vc2xmZSDtNr02s div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-64Vc2xmZSDtNr02s .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-64Vc2xmZSDtNr02s rect.text{fill:none;stroke-width:0;}#mermaid-svg-64Vc2xmZSDtNr02s .icon-shape,#mermaid-svg-64Vc2xmZSDtNr02s .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-64Vc2xmZSDtNr02s .icon-shape p,#mermaid-svg-64Vc2xmZSDtNr02s .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-64Vc2xmZSDtNr02s .icon-shape .label rect,#mermaid-svg-64Vc2xmZSDtNr02s .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-64Vc2xmZSDtNr02s .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-64Vc2xmZSDtNr02s .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-64Vc2xmZSDtNr02s :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 没有

Kubelet Watch 到

Pod 分给了自己
通过 CRI 调用 containerd
本地有 nginx 镜像?
去 Registry 拉镜像

(分层下载,只拉本地没有的层)
直接创建容器
containerd-shim 守护进程
runc 创建容器
namespace 隔离

(PID/NET/MNT/UTS/IPC)
cgroup 限制

(CPU/内存上限)
执行 Entrypoint
容器启动成功 ✅

<注意:K8s 1.24 之后默认不再使用 Docker 作为容器运行时,而是直接使用 containerd。调用链上没有 Docker Engine 这一层了。>

容器的本质:namespace + cgroup

如果你有 Linux 基础,这个概念应该不陌生。容器不是什么"轻量级虚拟机",它只是 Linux 两个老功能的组合:

bash 复制代码
# namespace:隔离------让容器以为自己独占了整台机器
lsns  # 查看系统上的 namespace

# cgroup:限制------让容器不能超量使用资源
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us  # 查看 CPU 配额
机制 作用 人话版
namespace 进程/网络/文件系统隔离 每人一个独立房间,互不干扰
cgroup CPU/内存/IO 限制 每个房间装了水电表,用超了就限流

namespace 的五种类型

类型 隔离什么 没了它会怎样
PID 进程树 容器里 ps aux 能看到宿主机所有进程
NET 网卡/IP/端口 容器没有独立 IP
MNT 文件系统挂载点 容器和宿主机共享文件系统
UTS hostname 容器改 hostname 会影响宿主机
IPC 进程间通信 容器间信号/共享内存不隔离

容器 vs 虚拟机:面试高频对比题

#mermaid-svg-zM5fjsDgCTeLpBKx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zM5fjsDgCTeLpBKx .error-icon{fill:#552222;}#mermaid-svg-zM5fjsDgCTeLpBKx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zM5fjsDgCTeLpBKx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zM5fjsDgCTeLpBKx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zM5fjsDgCTeLpBKx .marker.cross{stroke:#333333;}#mermaid-svg-zM5fjsDgCTeLpBKx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zM5fjsDgCTeLpBKx p{margin:0;}#mermaid-svg-zM5fjsDgCTeLpBKx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster-label text{fill:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster-label span{color:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster-label span p{background-color:transparent;}#mermaid-svg-zM5fjsDgCTeLpBKx .label text,#mermaid-svg-zM5fjsDgCTeLpBKx span{fill:#333;color:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx .node rect,#mermaid-svg-zM5fjsDgCTeLpBKx .node circle,#mermaid-svg-zM5fjsDgCTeLpBKx .node ellipse,#mermaid-svg-zM5fjsDgCTeLpBKx .node polygon,#mermaid-svg-zM5fjsDgCTeLpBKx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zM5fjsDgCTeLpBKx .rough-node .label text,#mermaid-svg-zM5fjsDgCTeLpBKx .node .label text,#mermaid-svg-zM5fjsDgCTeLpBKx .image-shape .label,#mermaid-svg-zM5fjsDgCTeLpBKx .icon-shape .label{text-anchor:middle;}#mermaid-svg-zM5fjsDgCTeLpBKx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zM5fjsDgCTeLpBKx .rough-node .label,#mermaid-svg-zM5fjsDgCTeLpBKx .node .label,#mermaid-svg-zM5fjsDgCTeLpBKx .image-shape .label,#mermaid-svg-zM5fjsDgCTeLpBKx .icon-shape .label{text-align:center;}#mermaid-svg-zM5fjsDgCTeLpBKx .node.clickable{cursor:pointer;}#mermaid-svg-zM5fjsDgCTeLpBKx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zM5fjsDgCTeLpBKx .arrowheadPath{fill:#333333;}#mermaid-svg-zM5fjsDgCTeLpBKx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zM5fjsDgCTeLpBKx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zM5fjsDgCTeLpBKx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zM5fjsDgCTeLpBKx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zM5fjsDgCTeLpBKx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zM5fjsDgCTeLpBKx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster text{fill:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx .cluster span{color:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zM5fjsDgCTeLpBKx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zM5fjsDgCTeLpBKx rect.text{fill:none;stroke-width:0;}#mermaid-svg-zM5fjsDgCTeLpBKx .icon-shape,#mermaid-svg-zM5fjsDgCTeLpBKx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zM5fjsDgCTeLpBKx .icon-shape p,#mermaid-svg-zM5fjsDgCTeLpBKx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zM5fjsDgCTeLpBKx .icon-shape .label rect,#mermaid-svg-zM5fjsDgCTeLpBKx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zM5fjsDgCTeLpBKx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zM5fjsDgCTeLpBKx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zM5fjsDgCTeLpBKx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 容器
虚拟机
应用
依赖库
Guest OS(完整操作系统)
Hypervisor
应用
依赖库
容器引擎
宿主机内核(共享)
物理硬件

维度 虚拟机 容器
启动速度 分钟级 秒级
资源占用 每 VM 一套完整 OS(GB 级) 共享宿主机内核(MB 级)
隔离程度 硬件级(更强) 进程级
单机可运行数 几十个 成百上千
内核 每个 VM 独立内核 共享宿主机内核

六、阶段④:Pod 的 IP 从哪来?------CNI 网络

容器创建好了,但它还没有 IP。这时候 CNI(Container Network Interface)插件出来干活了。

CNI 做什么?

#mermaid-svg-APc5M67Fak1XX704{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-APc5M67Fak1XX704 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-APc5M67Fak1XX704 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-APc5M67Fak1XX704 .error-icon{fill:#552222;}#mermaid-svg-APc5M67Fak1XX704 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-APc5M67Fak1XX704 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-APc5M67Fak1XX704 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-APc5M67Fak1XX704 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-APc5M67Fak1XX704 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-APc5M67Fak1XX704 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-APc5M67Fak1XX704 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-APc5M67Fak1XX704 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-APc5M67Fak1XX704 .marker.cross{stroke:#333333;}#mermaid-svg-APc5M67Fak1XX704 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-APc5M67Fak1XX704 p{margin:0;}#mermaid-svg-APc5M67Fak1XX704 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-APc5M67Fak1XX704 .cluster-label text{fill:#333;}#mermaid-svg-APc5M67Fak1XX704 .cluster-label span{color:#333;}#mermaid-svg-APc5M67Fak1XX704 .cluster-label span p{background-color:transparent;}#mermaid-svg-APc5M67Fak1XX704 .label text,#mermaid-svg-APc5M67Fak1XX704 span{fill:#333;color:#333;}#mermaid-svg-APc5M67Fak1XX704 .node rect,#mermaid-svg-APc5M67Fak1XX704 .node circle,#mermaid-svg-APc5M67Fak1XX704 .node ellipse,#mermaid-svg-APc5M67Fak1XX704 .node polygon,#mermaid-svg-APc5M67Fak1XX704 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-APc5M67Fak1XX704 .rough-node .label text,#mermaid-svg-APc5M67Fak1XX704 .node .label text,#mermaid-svg-APc5M67Fak1XX704 .image-shape .label,#mermaid-svg-APc5M67Fak1XX704 .icon-shape .label{text-anchor:middle;}#mermaid-svg-APc5M67Fak1XX704 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-APc5M67Fak1XX704 .rough-node .label,#mermaid-svg-APc5M67Fak1XX704 .node .label,#mermaid-svg-APc5M67Fak1XX704 .image-shape .label,#mermaid-svg-APc5M67Fak1XX704 .icon-shape .label{text-align:center;}#mermaid-svg-APc5M67Fak1XX704 .node.clickable{cursor:pointer;}#mermaid-svg-APc5M67Fak1XX704 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-APc5M67Fak1XX704 .arrowheadPath{fill:#333333;}#mermaid-svg-APc5M67Fak1XX704 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-APc5M67Fak1XX704 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-APc5M67Fak1XX704 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-APc5M67Fak1XX704 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-APc5M67Fak1XX704 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-APc5M67Fak1XX704 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-APc5M67Fak1XX704 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-APc5M67Fak1XX704 .cluster text{fill:#333;}#mermaid-svg-APc5M67Fak1XX704 .cluster span{color:#333;}#mermaid-svg-APc5M67Fak1XX704 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-APc5M67Fak1XX704 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-APc5M67Fak1XX704 rect.text{fill:none;stroke-width:0;}#mermaid-svg-APc5M67Fak1XX704 .icon-shape,#mermaid-svg-APc5M67Fak1XX704 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-APc5M67Fak1XX704 .icon-shape p,#mermaid-svg-APc5M67Fak1XX704 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-APc5M67Fak1XX704 .icon-shape .label rect,#mermaid-svg-APc5M67Fak1XX704 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-APc5M67Fak1XX704 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-APc5M67Fak1XX704 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-APc5M67Fak1XX704 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 宿主机
veth pair
veth pair
cni0 网桥
eth0(物理网卡)
Pod A

10.42.0.5
Pod B

10.42.0.6
外部网络

CNI 做三件事:

  1. 分配 IP :从 Pod CIDR 地址池(比如 10.42.0.0/16)里取一个
  2. 创建 veth pair:一对虚拟网线,一头插 Pod 的网络空间,一头插宿主机网桥
  3. 配路由:确保其他 Pod 能找到这个 IP
bash 复制代码
# 进 Pod 里看你自己的 IP
kubectl exec -it my-first-pod -- ip addr
# 你会看到 eth0@ifxx: 10.42.0.x
# 这就是 CNI 分配给你的 IP

<注意:Flannel、Calico 都是 CNI 的具体实现------就像不同品牌的交换机,功能都是插网线分配 IP,但实现细节不同。>


七、阶段⑤:外面怎么访问 Pod?------Service 代理

Pod IP 的问题

Pod 会死、会重建------死了重建后 IP 就变了。用户不可能每天记住 Pod 的新 IP。

Service 解决的就是这个问题:给一组 Pod 一个固定的"电话号码"(ClusterIP)。

Service 怎么找到 Pod?

#mermaid-svg-mRbP6vipUaT9tcjm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mRbP6vipUaT9tcjm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mRbP6vipUaT9tcjm .error-icon{fill:#552222;}#mermaid-svg-mRbP6vipUaT9tcjm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mRbP6vipUaT9tcjm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mRbP6vipUaT9tcjm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mRbP6vipUaT9tcjm .marker.cross{stroke:#333333;}#mermaid-svg-mRbP6vipUaT9tcjm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mRbP6vipUaT9tcjm p{margin:0;}#mermaid-svg-mRbP6vipUaT9tcjm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mRbP6vipUaT9tcjm .cluster-label text{fill:#333;}#mermaid-svg-mRbP6vipUaT9tcjm .cluster-label span{color:#333;}#mermaid-svg-mRbP6vipUaT9tcjm .cluster-label span p{background-color:transparent;}#mermaid-svg-mRbP6vipUaT9tcjm .label text,#mermaid-svg-mRbP6vipUaT9tcjm span{fill:#333;color:#333;}#mermaid-svg-mRbP6vipUaT9tcjm .node rect,#mermaid-svg-mRbP6vipUaT9tcjm .node circle,#mermaid-svg-mRbP6vipUaT9tcjm .node ellipse,#mermaid-svg-mRbP6vipUaT9tcjm .node polygon,#mermaid-svg-mRbP6vipUaT9tcjm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mRbP6vipUaT9tcjm .rough-node .label text,#mermaid-svg-mRbP6vipUaT9tcjm .node .label text,#mermaid-svg-mRbP6vipUaT9tcjm .image-shape .label,#mermaid-svg-mRbP6vipUaT9tcjm .icon-shape .label{text-anchor:middle;}#mermaid-svg-mRbP6vipUaT9tcjm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mRbP6vipUaT9tcjm .rough-node .label,#mermaid-svg-mRbP6vipUaT9tcjm .node .label,#mermaid-svg-mRbP6vipUaT9tcjm .image-shape .label,#mermaid-svg-mRbP6vipUaT9tcjm .icon-shape .label{text-align:center;}#mermaid-svg-mRbP6vipUaT9tcjm .node.clickable{cursor:pointer;}#mermaid-svg-mRbP6vipUaT9tcjm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mRbP6vipUaT9tcjm .arrowheadPath{fill:#333333;}#mermaid-svg-mRbP6vipUaT9tcjm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mRbP6vipUaT9tcjm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mRbP6vipUaT9tcjm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mRbP6vipUaT9tcjm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mRbP6vipUaT9tcjm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mRbP6vipUaT9tcjm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mRbP6vipUaT9tcjm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mRbP6vipUaT9tcjm .cluster text{fill:#333;}#mermaid-svg-mRbP6vipUaT9tcjm .cluster span{color:#333;}#mermaid-svg-mRbP6vipUaT9tcjm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mRbP6vipUaT9tcjm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mRbP6vipUaT9tcjm rect.text{fill:none;stroke-width:0;}#mermaid-svg-mRbP6vipUaT9tcjm .icon-shape,#mermaid-svg-mRbP6vipUaT9tcjm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mRbP6vipUaT9tcjm .icon-shape p,#mermaid-svg-mRbP6vipUaT9tcjm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mRbP6vipUaT9tcjm .icon-shape .label rect,#mermaid-svg-mRbP6vipUaT9tcjm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mRbP6vipUaT9tcjm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mRbP6vipUaT9tcjm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mRbP6vipUaT9tcjm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求 → 10.96.0.1:80
kube-proxy

(查 iptables 规则)
Pod A

10.42.0.5

Ready ✅
Pod B

10.42.0.6

Ready ✅
Pod C

10.42.0.7

Not Ready ❌

(Readiness 没过)

Service 通过 Label(标签) 来匹配 Pod:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx      # 只要 Pod 带这个标签,就自动加入通讯录
  ports:
  - port: 80        # Service 对外的端口
    targetPort: 80  # Pod 上容器监听的端口
bash 复制代码
# 创建 Service
kubectl expose pod my-first-pod --port=80 --target-port=80 --name=nginx-service

# 看 Service 的"通讯录"(Endpoints)
kubectl get endpoints nginx-service
# 输出:nginx-service   10.42.0.5:80
# 这就是 kube-proxy 会把流量转发过去的 Pod IP

kube-proxy 的两种模式

模式 原理 适用场景
iptables 在每台机器上写 iptables 规则,做 DNAT 转发 中小集群
IPVS 用 Linux 内核的 IPVS 模块做四层负载均衡 大集群(性能更好)

四种 Service 类型

类型 访问范围 用途
ClusterIP 集群内部 服务间调用(默认,最常用)
NodePort 节点 IP + 固定端口(30000-32767) 测试/临时暴露
LoadBalancer 公网 IP 对接云厂商 LB
ExternalName DNS 别名 映射外部服务进来

友情小知识:南北向 vs 东西向流量

这两个词你可能经常听到,其实很简单:

  • 东西向 = 集群内部服务之间的调用 = Service 管
  • 南北向 = 从集群外面进来的流量 = Ingress / LoadBalancer 管

八、阶段⑥:Pod 挂了怎么办?------健康检查 + 自愈

三种探针

K8s 通过探针(Probe)来判断 Pod 是否健康:

yaml 复制代码
spec:
  containers:
  - name: nginx
    image: nginx:latest
    livenessProbe:          # 存活探针:你还活着吗?
      httpGet:
        path: /healthz
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 5
    readinessProbe:         # 就绪探针:你能接客吗?
      httpGet:
        path: /ready
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 5
    startupProbe:           # 启动探针:启动慢不慢?
      httpGet:
        path: /startup
        port: 80
      failureThreshold: 30
      periodSeconds: 10
探针 作用 失败后果
Liveness 你还活着吗? 杀掉容器,重新创建
Readiness 你能接客了吗? 从 Service 通讯录移除(不重启
Startup 启动完了吗? 启动期间暂停 Liveness 检查(防误杀)

三种探测方式

bash 复制代码
# 方式 1:HTTP GET(最常用)
# 访问容器里指定的 URL,看返回码是不是 200-399

# 方式 2:TCP Socket
# 试试端口能不能连上

# 方式 3:Exec 命令
# 在容器里执行命令,看返回值是不是 0

自愈是怎么做到的?

K8s 的 Controller Manager 里跑着各种控制器,每一个都在做同一件事:

复制代码
无限循环:
  看 etcd 里的"期望状态"(我要几个 Pod?)
  → 看集群的"实际状态"(现在有几个 Pod?)
    → 不一样?调到一样

这个循环就叫 Reconciliation Loop(调谐循环)

bash 复制代码
# 亲自试试自愈:
# 删掉一个由 Deployment 管理的 Pod
kubectl delete pod my-first-pod
# 立刻 watch
kubectl get pod -w
# 你会看到 Pod 瞬间被重建------因为 Deployment Controller 发现"少了"就立刻补

九、阶段⑦:Pod 被删除时发生了什么?

容器进程 Pod Service API Server 你 容器进程 Pod Service API Server 你 #mermaid-svg-47bd5Gk38eft4iMj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-47bd5Gk38eft4iMj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-47bd5Gk38eft4iMj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-47bd5Gk38eft4iMj .error-icon{fill:#552222;}#mermaid-svg-47bd5Gk38eft4iMj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-47bd5Gk38eft4iMj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-47bd5Gk38eft4iMj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-47bd5Gk38eft4iMj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-47bd5Gk38eft4iMj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-47bd5Gk38eft4iMj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-47bd5Gk38eft4iMj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-47bd5Gk38eft4iMj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-47bd5Gk38eft4iMj .marker.cross{stroke:#333333;}#mermaid-svg-47bd5Gk38eft4iMj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-47bd5Gk38eft4iMj p{margin:0;}#mermaid-svg-47bd5Gk38eft4iMj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-47bd5Gk38eft4iMj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-47bd5Gk38eft4iMj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-47bd5Gk38eft4iMj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-47bd5Gk38eft4iMj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-47bd5Gk38eft4iMj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-47bd5Gk38eft4iMj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-47bd5Gk38eft4iMj .sequenceNumber{fill:white;}#mermaid-svg-47bd5Gk38eft4iMj #sequencenumber{fill:#333;}#mermaid-svg-47bd5Gk38eft4iMj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-47bd5Gk38eft4iMj .messageText{fill:#333;stroke:none;}#mermaid-svg-47bd5Gk38eft4iMj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-47bd5Gk38eft4iMj .labelText,#mermaid-svg-47bd5Gk38eft4iMj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-47bd5Gk38eft4iMj .loopText,#mermaid-svg-47bd5Gk38eft4iMj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-47bd5Gk38eft4iMj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-47bd5Gk38eft4iMj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-47bd5Gk38eft4iMj .noteText,#mermaid-svg-47bd5Gk38eft4iMj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-47bd5Gk38eft4iMj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-47bd5Gk38eft4iMj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-47bd5Gk38eft4iMj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-47bd5Gk38eft4iMj .actorPopupMenu{position:absolute;}#mermaid-svg-47bd5Gk38eft4iMj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-47bd5Gk38eft4iMj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-47bd5Gk38eft4iMj .actor-man circle,#mermaid-svg-47bd5Gk38eft4iMj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-47bd5Gk38eft4iMj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用处理完手头请求自己优雅退出 ✅ === 如果 30s 到了还没退 === kubectl delete pod xxx打 deletionTimestamp +启动 30s 倒计时立刻从 Endpoints 移除(新流量不再进来)执行 PreStop Hook(如果有)发送 SIGTERM(信号 15)SIGKILL(信号 9) 强杀 💀

一步一步看

  1. API Server 收到删除请求 → 给 Pod 打上 deletionTimestamp,启动 30 秒倒计时
  2. Service 立刻把这个 Pod 从通讯录移除------新流量不会再发给它
  3. 如果配置了 PreStop Hook,先执行(比如通知注册中心下线)
  4. Kubelet 发 SIGTERM(信号 15)给容器主进程------"收拾东西,该走了"
  5. 进程处理完手头请求,优雅退出
  6. 30 秒到了还没退出?SIGKILL(信号 9)强杀------保安架你出去

新手最常踩的坑:你的应用没处理 SIGTERM

c++ 复制代码
// ❌ 错误示范:程序完全不知道要退出
// 在 main 函数里面直接死循环跑业务逻辑
// Kubectl delete 后,30 秒后被 SIGKILL 强杀
// 正在处理的请求全部丢失

int main() {
    while (true) {
        handle_request();  // 如果正在处理请求时被强杀...
    }
}
c++ 复制代码
// ✅ 正确示范(C++ 版):捕获 SIGTERM,优雅退出
#include <csignal>
#include <atomic>

std::atomic<bool> running{true};

void signal_handler(int signum) {
    if (signum == SIGTERM) {
        std::cout << "收到 SIGTERM,准备退出..." << std::endl;
        running = false;  // 通知主循环停下来
    }
}

int main() {
    signal(SIGTERM, signal_handler);  // 注册信号处理
    
    while (running) {
        handle_request();  // 处理完当前请求再退出
    }
    
    std::cout << "优雅退出完成" << std::endl;
    return 0;
}
go 复制代码
// ✅ 正确示范(Go 版):捕获 SIGTERM,优雅退出
// 下半年学了 Go 之后你就能写这个了~

import (
    "os"
    "os/signal"
    "syscall"
)

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT)
<-sigCh  // 阻塞等待信号
// 收到信号后:停止接收新请求 → 等待现有请求处理完 → 退出

<注意:SIGTERM 和 SIGKILL 的区别是 Linux 基础面试高频题------SIGTERM(15)可以被捕获和处理,SIGKILL(9)不行,内核直接杀进程。你的 Linux 3 分基础完全够解释这个。>


十、Pod 生命周期核心接口速查

bash 复制代码
# push(创建 Pod)
kubectl apply -f pod.yaml

# top(查看当前 Pod 状态)
kubectl get pod -w          # -w 实时 watch
kubectl describe pod xxx   # 看详情 + Events
kubectl logs xxx           # 看日志
kubectl logs xxx --previous # 看上一次崩溃的日志

# front(进入容器)
kubectl exec -it xxx -- /bin/bash

# pop(删除 Pod)
kubectl delete pod xxx

# empty(判空)
kubectl get pod            # 看看有哪些 Pod

# size(查看 Pod 内部)
kubectl get pod xxx -o yaml  # 看完整配置
kubectl describe pod xxx     # 看详细信息

十一、新手最常踩的 5 个坑(附排查方式)

坑 1:Pod 一直 Pending

bash 复制代码
# ❌ 问题现象
$ kubectl get pod
NAME           READY   STATUS    RESTARTS   AGE
my-first-pod   0/1     Pending   0          5m

# ✅ 排查方式
kubectl describe pod my-first-pod
# 翻到 Events,看是不是:
# "0/1 nodes are available" → 资源不够或节点有污点
# "persistentvolumeclaim not found" → 挂载的 PVC 不存在

坑 2:Pod 反复重启(CrashLoopBackOff)

bash 复制代码
# ❌ 问题现象
$ kubectl get pod
NAME           READY   STATUS             RESTARTS   AGE
my-first-pod   0/1     CrashLoopBackOff   4          3m

# ✅ 排查方式
kubectl logs my-first-pod --previous
# 99% 的情况是应用启动后立刻退出了(缺少依赖、配置错误、panic)

坑 3:Pod Running 了但外面访问不到

bash 复制代码
# ✅ 排查方式
# 1. 检查 Readiness Probe
kubectl describe pod my-first-pod | grep -A5 Readiness

# 2. 检查 Service Endpoints 是否为空
kubectl get endpoints nginx-service
# 如果 ENDPOINTS 列是 <none> → Service 没找到 Ready 的 Pod

坑 4:ClusterIP ping 不通

bash 复制代码
# ❌ 错误测试方式
ping 10.96.0.1  # 不通!但这是正常现象

# ✅ 正确测试方式
curl http://10.96.0.1:80  # 通就对了
# 因为 ClusterIP 是虚拟 IP,不对应任何物理网卡
# ping 用的是 ICMP 协议,不走传输层,所以不通

坑 5:删 Pod 偶尔丢请求

bash 复制代码
# 原因:应用没处理 SIGTERM → 30 秒后 SIGKILL 强杀 → 正在处理的请求丢了
# ✅ 解决:在 Pod 配置中加 PreStop Hook,给流量排空留时间

spec:
  terminationGracePeriodSeconds: 60  # 把倒计时延长到 60 秒
  containers:
  - name: my-app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 5"]  # 等待 5 秒让流量排空

十二、动手验证(在你的 k3s 上跑一遍)

全部在 VS Code 连服务器的终端里操作:

bash 复制代码
# === 实验 1:创建 Pod,看它的诞生过程 ===
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
EOF

kubectl get pod -w
# 看到 Running 后 Ctrl+C 退出
# 你会看到:Pending → ContainerCreating → Running
bash 复制代码
# === 实验 2:看 Pod 的事件记录 ===
kubectl describe pod my-first-pod
# 翻到 Events 部分,你会看到:
# Scheduled → Pulling → Pulled → Created → Started
# 这就是阶段①~⑤的完整证据链
bash 复制代码
# === 实验 3:进容器看内部 ===
kubectl exec -it my-first-pod -- /bin/bash
# 进去后执行:
ps aux       # 看进程------只能看到容器自己的进程
ip addr      # 看自己 IP------能看到 CNI 分配的 10.42.0.x
env          # 看环境变量
exit         # 退出
bash 复制代码
# === 实验 4:创建 Service,看通讯录 ===
kubectl expose pod my-first-pod --port=80 --target-port=80 --name=nginx-service
kubectl get endpoints nginx-service
# 应该看到 ENDPOINTS 列有你的 Pod IP:80
bash 复制代码
# === 实验 5:优雅终止测试 ===
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: graceful-test
spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: nginx
    image: nginx:latest
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "echo 'PreStop: 开始清理...' && sleep 5 && echo 'PreStop: 清理完成'"]
EOF

kubectl delete pod graceful-test
kubectl get pod -w
# 观察从 delete 到 Pod 真正消失的时间

十三、总结

一条 kubectl apply -f pod.yaml,背后是 7 个组件在接力:

复制代码
你 → kubectl → API Server(三道门) → etcd(持久化)
  → Scheduler(调度) → Kubelet(创建容器) → CNI(分配网络)
    → Service(注册对外入口) → Controller Manager(持续自愈)

#mermaid-svg-ylbgSiK9RAa043vs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ylbgSiK9RAa043vs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ylbgSiK9RAa043vs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ylbgSiK9RAa043vs .error-icon{fill:#552222;}#mermaid-svg-ylbgSiK9RAa043vs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ylbgSiK9RAa043vs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ylbgSiK9RAa043vs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ylbgSiK9RAa043vs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ylbgSiK9RAa043vs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ylbgSiK9RAa043vs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ylbgSiK9RAa043vs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ylbgSiK9RAa043vs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ylbgSiK9RAa043vs .marker.cross{stroke:#333333;}#mermaid-svg-ylbgSiK9RAa043vs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ylbgSiK9RAa043vs p{margin:0;}#mermaid-svg-ylbgSiK9RAa043vs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ylbgSiK9RAa043vs .cluster-label text{fill:#333;}#mermaid-svg-ylbgSiK9RAa043vs .cluster-label span{color:#333;}#mermaid-svg-ylbgSiK9RAa043vs .cluster-label span p{background-color:transparent;}#mermaid-svg-ylbgSiK9RAa043vs .label text,#mermaid-svg-ylbgSiK9RAa043vs span{fill:#333;color:#333;}#mermaid-svg-ylbgSiK9RAa043vs .node rect,#mermaid-svg-ylbgSiK9RAa043vs .node circle,#mermaid-svg-ylbgSiK9RAa043vs .node ellipse,#mermaid-svg-ylbgSiK9RAa043vs .node polygon,#mermaid-svg-ylbgSiK9RAa043vs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ylbgSiK9RAa043vs .rough-node .label text,#mermaid-svg-ylbgSiK9RAa043vs .node .label text,#mermaid-svg-ylbgSiK9RAa043vs .image-shape .label,#mermaid-svg-ylbgSiK9RAa043vs .icon-shape .label{text-anchor:middle;}#mermaid-svg-ylbgSiK9RAa043vs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ylbgSiK9RAa043vs .rough-node .label,#mermaid-svg-ylbgSiK9RAa043vs .node .label,#mermaid-svg-ylbgSiK9RAa043vs .image-shape .label,#mermaid-svg-ylbgSiK9RAa043vs .icon-shape .label{text-align:center;}#mermaid-svg-ylbgSiK9RAa043vs .node.clickable{cursor:pointer;}#mermaid-svg-ylbgSiK9RAa043vs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ylbgSiK9RAa043vs .arrowheadPath{fill:#333333;}#mermaid-svg-ylbgSiK9RAa043vs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ylbgSiK9RAa043vs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ylbgSiK9RAa043vs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ylbgSiK9RAa043vs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ylbgSiK9RAa043vs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ylbgSiK9RAa043vs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ylbgSiK9RAa043vs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ylbgSiK9RAa043vs .cluster text{fill:#333;}#mermaid-svg-ylbgSiK9RAa043vs .cluster span{color:#333;}#mermaid-svg-ylbgSiK9RAa043vs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ylbgSiK9RAa043vs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ylbgSiK9RAa043vs rect.text{fill:none;stroke-width:0;}#mermaid-svg-ylbgSiK9RAa043vs .icon-shape,#mermaid-svg-ylbgSiK9RAa043vs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ylbgSiK9RAa043vs .icon-shape p,#mermaid-svg-ylbgSiK9RAa043vs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ylbgSiK9RAa043vs .icon-shape .label rect,#mermaid-svg-ylbgSiK9RAa043vs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ylbgSiK9RAa043vs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ylbgSiK9RAa043vs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ylbgSiK9RAa043vs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Pod生命周期的7个站
① 提交
② 调度
③ 创建
④ 网络
⑤ 服务
⑥ 自愈
⑦ 终止

这篇文章是 K8s 学习系列的总览地图------每个组件只讲了它"做什么"。后面的文章会一个一个深挖:

  • etcd 怎么存储数据、Raft 共识是怎么回事
  • Scheduler 的预选策略和优选打分算法
  • kube-proxy 的 iptables 规则到底长什么样
  • Istio Sidecar 是怎么注入的
  • Controller Manager 的自愈机制

当然,在后面文章中,当我们接触资源清单时,彩妙会进一步的为大家讲解Pod->从创建到销毁的全流程。

这些,彩妙会在后面的文章中逐个拆解~

本篇到这里就结束了,喜欢文章的小伙伴可以关注一下彩妙,我们下一篇再见~