第 2 章:彻底搞懂 K8s Pod——从 YAML 到调度全流程

第 2 章:彻底搞懂 K8s Pod------从 YAML 到调度全流程

上一章认识了 Pod、Deployment、Service 三个核心对象。本章聚焦 Pod:它到底是什么?生命周期是怎样的?怎么写 YAML?kubelet 又是怎么把它跑起来的?

2.1 为什么 K8s 的最小单位是 Pod,不是容器

Docker 容器有很多好用的特性:隔离、可移植、快速启动。但容器之间是强隔离的------两个容器默认不共享网络、文件系统和进程空间。

实际业务中,有些场景需要容器"亲密无间":

场景 为什么需要同一个 Pod
主容器 + 日志 sidecar 共享日志卷,sidecar 读主容器写的日志
主容器 + 监控 agent 共享进程命名空间,agent 能读到主容器的运行时信息
Init 容器准备数据 在主容器启动前完成初始化工作
适配器模式 把老协议转换成新协议,主容器无感知

用一个具体例子感受(典型的 sidecar 模式):

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web-with-logger
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    volumeMounts:
    - name: log-vol
      mountPath: /var/log/nginx
  - name: log-collector
    image: fluent/fluent-bit:latest
    volumeMounts:
    - name: log-vol
      mountPath: /logs
  volumes:
  - name: log-vol
    emptyDir: {}

这里 Nginx 写日志到 /var/log/nginx,fluent-bit 从 /logs 读,两个路径指向的是同一个 emptyDir 卷。只有同一个 Pod 内的容器才能这样共享存储

2.2 Pod 的生命周期

一个 Pod 从创建到销毁,会经历这些阶段:

sql 复制代码
Pending → ContainerCreating → Running → Succeeded/Failed → Terminating
阶段 含义
Pending 已创建对象,但容器还没运行起来(可能在拉镜像、等调度)
ContainerCreating 正在创建容器 sandbox(pause 容器 + 网络)
Running 至少一个容器正在运行
Succeeded 所有容器都正常退出(适用于 Job)
Failed 有容器异常退出
Terminating 正在删除,kubelet 执行优雅停机

真实查看:

bash 复制代码
$ kubectl get pod web-xxx
NAME      READY   STATUS              RESTARTS   AGE
web-xxx   0/1     ContainerCreating   0          5s

READY 0/1 表示这个 Pod 期望 1 个容器,目前 0 个 Ready。

2.2.1 优雅停机:preStop 与 terminationGracePeriodSeconds

Pod 被删除时,默认有 30 秒优雅停机时间:

arduino 复制代码
kubectl delete pod web-xxx
  → kubelet 收到删除事件
    → 发送 SIGTERM 给主进程
      → 等 30 秒(terminationGracePeriodSeconds)
        → 进程未退出 → 发送 SIGKILL 强制终止

可以通过 preStop 钩子在收到 SIGTERM 前执行一些清理:

yaml 复制代码
spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: web
    image: nginx:alpine
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "nginx -s quit; sleep 10"]

为什么要先 sleep 10?为了让 Service 的 Endpoints 从列表中移除,避免新流量再进来, graceful shutdown 更干净。

2.3 写一个完整的 Pod YAML

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: dev
  labels:
    app: myapp
    tier: frontend
spec:
  containers:
  - name: myapp
    image: nginx:alpine
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      requests:
        cpu: "100m"
        memory: "128Mi"
      limits:
        cpu: "500m"
        memory: "256Mi"
    env:
    - name: LOG_LEVEL
      value: "info"
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 5
    volumeMounts:
    - name: cache
      mountPath: /cache
  volumes:
  - name: cache
    emptyDir: {}
  restartPolicy: Always

逐字段解释:

字段 作用
metadata.labels 标签,供 Service/Deployment 选择器使用
imagePullPolicy Always 每次拉;IfNotPresent 本地没有才拉;Never 只使用本地镜像
resources.requests 调度时判断资源是否够用的基础值
resources.limits 实际运行时的硬上限
livenessProbe 存活探针,失败则重启容器
readinessProbe 就绪探针,失败则不接流量
restartPolicy Always/OnFailure/Never

2.4 探针:Pod 级自愈的触发器

Pod 有两个核心探针,必须分清楚:

探针 作用 失败后果
livenessProbe "这个容器还活着吗?" 容器被 kubelet 重启
readinessProbe "这个容器准备好接流量了吗?" 从 Service Endpoints 列表移除,不被访问

为什么需要分开?

  • 应用死锁了:liveness 失败 → 重启
  • 应用正在启动,还没连上数据库:readiness 失败 → 暂时不接入流量,但不重启

探针支持三种探测方式:

yaml 复制代码
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  # 或 exec
  # exec:
  #   command: ["cat", "/tmp/healthy"]
  # 或 tcpSocket
  # tcpSocket:
  #   port: 8080

真实排障:

bash 复制代码
$ kubectl describe pod myapp
...
Events:
  Warning  Unhealthy  Liveness probe failed: HTTP probe failed with statuscode: 500
  Normal   Killing    Container myapp failed liveness probe, will be restarted

2.5 Init 容器:在主容器之前干活

Init 容器在业务容器启动前按顺序执行,全部成功后业务容器才启动。典型用途:

  • 等待数据库就绪
  • 生成配置文件
  • 拉取外部依赖
yaml 复制代码
spec:
  initContainers:
  - name: wait-for-db
    image: busybox:1.31
    command: ['sh', '-c', 'until nc -z mysql 3306; do echo waiting; sleep 2; done']
  containers:
  - name: myapp
    image: myapp:v1

如果 init 容器失败,Pod 会反复重启 init 容器,业务容器永远不会启动。kubectl logs myapp -c wait-for-db 可以看到 init 容器的日志。

2.6 从 kubectl run 到 Pod 调度全流程

bash 复制代码
kubectl run mynginx --image=nginx:alpine

这条命令背后发生的事:

  1. kubectl 把 Pod 对象发给 apiserver
  2. apiserver 写入 etcd
  3. scheduler 发现这个 Pod 还没绑定节点,选优后写入 nodeName
  4. 对应节点的 kubelet watch 到分配给自己的 Pod
  5. kubelet 调用 containerd:创建 pause 容器 → 配置网络 → 拉 Nginx 镜像 → 起 Nginx 容器
  6. kubelet 持续执行 readiness/liveness 探针
  7. Pod Ready 后,Endpoints 控制器把它的 IP 加入相关 Service

注意:裸 Pod 没有控制器盯着。如果节点重启或 Pod 被删,它不会自动重建。所以生产环境用 Deployment 管理 Pod。

2.7 常见 Pod 状态排障

状态 排查方向
Pending 资源不够、节点有污点、镜像拉取慢
ContainerCreating CNI 插件问题、镜像拉取中、存储挂载失败
CrashLoopBackOff 容器启动后立刻退出,看 kubectl logs --previous
ImagePullBackOff 镜像名错误、没权限拉、网络超时
ErrImagePull 镜像不存在或网络不通
Terminating 卡住 kubelet 没收到删除完成信号,需要强制删除或检查节点

2.8 本章小结

  • Pod 是 K8s 最小调度单元,多个容器可共享网络和存储
  • Pod 生命周期:Pending → ContainerCreating → Running → Succeeded/Failed → Terminating
  • livenessProbe 管重启,readinessProbe 管流量接入
  • Init 容器在主容器前执行,适合做初始化和等待依赖
  • 裸 Pod 没有自愈能力,生产环境必须使用 Deployment 等控制器
  • 排障首选命令:kubectl describe pod 看 Events,kubectl logs 看日志

2.9 课后练习

  1. 创建一个包含两个容器的 Pod:一个写文件、一个读同一个文件并打印内容。
  2. 故意让 livenessProbe 失败,观察 Pod 的 RESTARTS 数字增长。
  3. 用 Init 容器实现"等待某个域名可以解析后再启动主容器"。
相关推荐
逐光老顽童1 小时前
第 1 章:Kubernetes 核心概念总览——Pod、Deployment、Service 一次搞懂
分布式·云原生
tang777892 小时前
分布式爬虫优化指南:如何用代理IP把采集效率提升300%
分布式·爬虫·python·tcp/ip·分布式爬虫·爬虫代理·代理ip
人间凡尔赛2 小时前
2026 云原生后端架构演进:事件驱动、虚拟线程与 AI Agent 内嵌,三驾马车如何重塑技术栈
后端·云原生·架构
江畔柳前堤11 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
学者猫头鹰16 小时前
分布式事务实战教程
java·分布式
阿里云云原生17 小时前
五层监控与运维数字孪生:畅捷通如何打造应对亿级数据的全栈可观测体系?
云原生
阿里云云原生18 小时前
从“救火”到“体检”:基于 STAROps 与 SysOM 的主机智能巡检闭环实战
云原生
笨蛋不要掉眼泪19 小时前
RabbitMQ消息队列:MQ的可靠性
分布式·rabbitmq·java-rabbitmq
阿里云云原生19 小时前
国内首批,阿里云 STAROps 通过《智能原生软件工程》系列标准认证
云原生