Pod

2020-10-02T17:43:02+08:00 | 10分钟阅读 | 更新于 2020-10-02T17:43:02+08:00

@

学习目标

学完本章你应该能够:

  1. 说清 Pod 是什么:用"逻辑主机 / 宿舍套间"的类比解释 Pod 作为最小可部署单元的定位,以及它为什么把"紧密协作"的容器放在一起。
  2. 讲清共享资源:解释 Pod 内容器如何共享网络命名空间(同一 IP/端口空间、localhost 互通)和存储卷,并说清这带来的便利与限制。
  3. 画出生命周期状态机:描述 Pending → ContainerCreating → Running → Succeeded/Failed/Unknown 的迁移,以及 postStart / preStop 钩子、Liveness / Readiness 探针各自在哪起作用。
  4. 理解调度两阶段:说清调度器的"预选(Predicate)过滤不合格节点"和"优选(Priority)给合格节点打分"分别在挑什么。
  5. 排查调度失败:知道 Pod 一直 Pending 时该用 kubectl describe 看哪类原因(资源不足、标签不匹配、污点不容忍)。

前置知识

  • 容器基础(Docker 镜像、容器是什么)。
  • 对 Kubernetes 有个粗浅概念(知道它是容器编排系统即可)。
  • YAML 基本语法(能看懂 kind: Pod 这类声明)。

本章你会动手做的事

  1. 用文中 lifecycle-hook-pod 的 YAML 起一个 Pod,分别观察 postStart 写出的文件和 preStop 优雅退出行为。
  2. 给一个 Pod 加上 nodeSelector: disktype=ssd,在一个没有该标签的集群里看它卡在 Pending 并 describe 找出原因。
  3. 故意把 Liveness 探针路径写成不存在的地址,观察 Pod 被 kubelet 反复重启。

Pod 解析:

一、Pod 基础概念

1. 定义

在 Kubernetes(K8s)里,Pod 属于最基础且最小的可部署单元。它代表一组紧密关联的容器集合,这些容器会共享存储、网络以及运行环境。可以把 Pod 当作是运行特定应用程序的一个“逻辑主机”。

2. 设计理念

Pod 的设计是为了支持那些紧密耦合、相互协作的进程,这些进程可能会一起部署和管理。比如,一个主应用容器和一个用于日志收集的 Sidecar 容器就可以放在同一个 Pod 中。

类比:Pod 就像一个"宿舍套间"——套间里住着几个室友(容器),他们共享同一个门牌号(IP 地址)、同一个客厅 WiFi(网络命名空间)和同一个公共储物间(存储卷)。室友之间可以直接在客厅喊一声就沟通(通过 localhost 通信),但套间与套间之间是隔离的。把"主应用 + 日志收集 Sidecar"放进一个套间,是因为他俩要天天黏在一起、共享磁盘上的日志文件。

3. 共享资源

  • 网络:Pod 内的所有容器共享同一个网络命名空间,拥有相同的 IP 地址和端口空间。这意味着容器之间可以通过 localhost 直接通信,极大地简化了应用内部的网络交互。
  • 存储:Pod 能够定义一个或多个存储卷(Volume),这些存储卷可被 Pod 内的所有容器访问,方便实现数据的持久化或者容器间的数据共享。

下面这张图展示一个 Pod 内多个容器如何共享网络与存储:

flowchart TB
    subgraph Pod逻辑主机
        NET[共享网络命名空间
同一IP + 端口空间] VOL[共享存储卷 Volume] C1[容器A
主应用] C2[容器B
Sidecar日志收集] C1 --- NET C2 --- NET C1 --- VOL C2 --- VOL C1 -.->|localhost 互通| C2 end

二、Pod 的生命周期

1. Pending(挂起)

  • 状态说明:当你创建一个 Pod 时,它首先会进入 Pending 状态。此时,Kubernetes API Server 已经接收到创建请求,但 Pod 还未被完全创建好。
  • 可能原因
    • 调度器还没为 Pod 分配合适的节点。
    • 容器镜像正在下载,特别是大镜像时,下载时间可能较长。

2. ContainerCreating(容器创建中)

  • 状态说明:一旦 Pod 被调度到某个节点,kubelet 就会开始创建容器。
  • 主要操作
    • 从镜像仓库下载容器镜像(如果本地不存在)。
    • 创建容器的网络和存储等资源。
    • 启动容器进程。

3. Running(运行中)

  • 状态说明:当 Pod 内的所有容器都成功创建并启动后,Pod 进入 Running 状态。不过,这并不意味着应用程序一定能正常工作,还需通过健康检查来确认。
  • 相关操作:此时,Pod 中的应用程序开始提供服务,并且可以接收外部流量。

4. Succeeded(成功)

  • 状态说明:如果 Pod 中的所有容器都正常终止(退出码为 0),Pod 就会进入 Succeeded 状态。
  • 常见场景:这种情况常见于一次性任务,比如批处理作业,任务完成后容器就会正常退出。

5. Failed(失败)

  • 状态说明:若 Pod 中的任何一个容器以非零退出码终止,或者在创建、运行过程中出现错误,Pod 就会进入 Failed 状态。
  • 可能原因
    • 镜像下载失败。
    • 容器启动脚本出错。
    • 应用程序内部错误。

6. Unknown(未知)

  • 状态说明:当 kubelet 无法向 API Server 汇报 Pod 的状态时,Pod 会处于 Unknown 状态。
  • 可能原因:通常是由于节点与 API Server 之间的网络问题,或者 kubelet 进程崩溃导致。

7. 生命周期钩子(Lifecycle Hooks)

Kubernetes 允许为 Pod 中的容器定义生命周期钩子,这些钩子会在容器的特定阶段执行。

  • postStart:容器启动后立即执行的操作,可用于初始化工作,例如创建必要的文件或目录。
  • preStop:容器终止前执行的操作,可用于优雅关闭应用程序,比如保存数据、释放资源等。

以下是包含生命周期钩子的 Pod 示例:

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-hook-pod
spec:
  containers:
    - name: my-container
      image: nginx:1.14.2
      lifecycle:
        postStart:
          exec:
            command:
              ["/bin/sh", "-c", "echo 'Container started' > /tmp/start.log"]
        preStop:
          exec:
            command: ["/usr/sbin/nginx", "-s", "quit"]

8. 健康检查(Probes)

为了确保 Pod 中的应用程序正常运行,Kubernetes 提供了健康检查机制,主要有两种类型的探针:

  • 存活探针(Liveness Probe):用于检测容器是否存活。如果存活探针失败,Kubernetes 会重启容器。
  • 就绪探针(Readiness Probe):用于检测容器是否准备好接收流量。如果就绪探针失败,Kubernetes 会将该 Pod 从服务的负载均衡中移除。

以下是包含健康检查的 Pod 示例:

apiVersion: v1
kind: Pod
metadata:
  name: probe-pod
spec:
  containers:
    - name: my-container
      image: nginx:1.14.2
      ports:
        - containerPort: 80
      livenessProbe:
        httpGet:
          path: /healthz
          port: 80
        initialDelaySeconds: 15
        periodSeconds: 5
      readinessProbe:
        httpGet:
          path: /ready
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 10

类比:Liveness 探针像"心跳监测"——人还活着(进程没卡死)才不管,死了就重启;Readiness 探针像"营业招牌"——没挂出"营业中"就不派客上门(摘出负载均衡),但不会因为暂时没挂牌就把店拆了(不会重启)。两者职责不同:一个决定"要不要救",一个决定"要不要接客"。

下面这张状态机图,把 Pod 从创建到终态的迁移,以及钩子/探针在哪些阶段起作用,串到一起:

stateDiagram-v2
    [*] --> Pending
    Pending --> ContainerCreating: 调度到节点
    ContainerCreating --> Running: 全部容器启动成功
    ContainerCreating --> Failed: 镜像/启动失败
    Pending --> Failed: 调度失败/资源不足
    Running --> Succeeded: 所有容器退出码0
    Running --> Failed: 任意容器非0退出
    Running --> Unknown: kubelet 失联
    Running --> Running: Liveness失败则重启容器
    note right of Running
        postStart: 启动后立即执行
        preStop: 终止前执行
        Liveness: 失败重启
        Readiness: 失败摘流量
    end note
    Succeeded --> [*]
    Failed --> [*]

三、Pod 的调度

1. 调度器的作用

Kubernetes 中的调度器(Scheduler)负责将 Pod 分配到合适的节点上运行。调度器会综合考虑节点的资源状况、Pod 的资源请求以及各种调度策略来做出决策。

2. 调度过程

类比:调度器像个"分宿舍管理员"。它先把所有不符合条件的楼过滤掉(比如你要 SSD 硬盘的楼,没标的直接出局——这是预选),再对剩下的楼打分(谁负载低、离得近就高分——这是优选),最后把人安排进最高分的那间。

调度过程分两个阶段,下面这张图给出全貌:

flowchart TB
    P[新Pod待调度] --> PRE[预选 Predicate
过滤不合格节点] PRE -->|资源不足/标签不符/污点不容忍| OUT[节点被排除] PRE --> PRI[优选 Priority
对合格节点打分] PRI -->|负载低/亲和性高者得分高| BIND[绑定到最高分节点] BIND --> DONE[Pod 进入调度完成]

预选阶段(Predicate)

调度器会根据一系列规则过滤掉不满足条件的节点,主要规则如下:

  • 资源检查:检查节点的可用 CPU、内存等资源是否满足 Pod 的请求。如果节点资源不足,该节点会被排除。
  • 节点选择器(NodeSelector):如果 Pod 定义了 nodeSelector,调度器会只考虑带有相应标签的节点。
  • 节点亲和性与反亲和性(Node Affinity and Anti - Affinity):可以定义 Pod 与节点之间的亲和或反亲和关系。例如,要求 Pod 必须调度到特定区域的节点上,或者避免调度到某些节点上。
  • 污点与容忍度(Taints and Tolerations):节点可以设置污点,防止某些 Pod 被调度到该节点上;而 Pod 可以设置容忍度,表示它可以容忍哪些污点。

优选阶段(Priority)

在通过预选的节点中,调度器会对每个节点进行打分,选择得分最高的节点。打分的依据有很多,例如:

  • 节点负载情况:优先选择负载较低的节点,以保证 Pod 能稳定运行。
  • 与其他 Pod 的亲和性:为了提高性能或实现高可用,可能会优先选择与其他相关 Pod 在同一节点或不同节点的节点。

3. 调度策略示例

节点选择器

apiVersion: v1
kind: Pod
metadata:
  name: node-selector-pod
spec:
  containers:
    - name: my-container
      image: nginx:1.14.2
  nodeSelector:
    disktype: ssd

上述示例中,Pod 会被调度到带有 disktype: ssd 标签的节点上。

节点亲和性

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity-pod
spec:
  containers:
    - name: my-container
      image: nginx:1.14.2
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: region
                operator: In
                values:
                  - us-west

此示例中,Pod 必须被调度到带有 region: us-west 标签的节点上。

污点与容忍度

apiVersion: v1
kind: Pod
metadata:
  name: taint-toleration-pod
spec:
  containers:
    - name: my-container
      image: nginx:1.14.2
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "special-user"
      effect: "NoSchedule"

该 Pod 可以容忍带有 dedicated: special-user 污点且效果为 NoSchedule 的节点。

4. 调度失败处理

如果调度器无法为 Pod 找到合适的节点,Pod 会一直处于 Pending 状态。可以通过查看 kubectl describe pod <pod-name> 的输出信息来定位调度失败的原因,常见原因包括节点资源不足、节点标签不匹配等。

⚠️ 新手必踩的坑:Pod 一直 Pending 不一定是程序错了。很多时候是调度阶段被卡住——比如 nodeSelector 写的标签集群里根本不存在,或节点全被污点 NoSchedule 挡住而 Pod 没配对应 tolerations。第一反应应该是 kubectl describe podEvents 里的 “FailedScheduling” 原因,而不是去改业务代码。

四、总结

Pod 作为 Kubernetes 中最核心的概念之一,其生命周期和调度机制对于有效管理和运行容器化应用程序起着至关重要的作用。通过合理配置 Pod 的生命周期钩子、健康检查和调度策略,能够显著提高应用程序的可用性和性能。同时,深入理解 Pod 的相关知识,有助于更好地应对 Kubernetes 集群中的各种场景和问题。


自测题与动手练习

自测题(合上书能答出来,才算懂)

  1. Pod 和"单个容器"的区别是什么?为什么说它是"逻辑主机"而不是"容器"?
  2. 同一个 Pod 内的两个容器,直接用 localhost 能互通吗?它们看到的是同一个 IP 吗?
  3. Liveness 探针和 Readiness 探针失败之后的处理有什么不同?一个会重启容器,一个会怎样?
  4. 调度器的"预选"和"优选"两个阶段分别在干什么?如果预选就把所有节点排除了会怎样?
  5. 一个 Pod 一直 Pending,除了"镜像没下完",还可能是什么原因?第一步该看什么命令?

动手练习(建议真做一遍)

  1. 用文中 probe-pod 的 YAML 创建 Pod,然后 kubectl exec 进容器删掉 /healthz 路径对应的服务,观察 Liveness 失败导致容器被重启的次数(kubectl get pod 的 RESTARTS)。
  2. 给一个 Pod 加 nodeSelector: disktype=ssd,在一个没有该标签的节点上部署,用 kubectl describe podFailedScheduling 事件,再给节点打上标签验证它能被调度。
  3. 给节点打一个 dedicated=special-user:NoSchedule 污点,再分别用"无 tolerations 的 Pod"和"有对应 tolerations 的 Pod"测试,观察前者 Pending、后者能调度。

本章小结

  • Pod 是最小部署单元:它把"紧密协作"的容器打包成一个逻辑主机,容器间共享网络命名空间(同 IP、localhost 互通)和存储卷。
  • 生命周期是状态机:Pending → ContainerCreating → Running,终态是 Succeeded / Failed / Unknown;postStart / preStop 钩子在启动后 / 终止前触发。
  • 探针分工明确:Liveness 失败→重启容器(救活),Readiness 失败→摘出负载均衡(暂不接客),二者不要混用。
  • 调度分两阶段:预选(Predicate)按硬规则过滤节点,优选(Priority)对剩余节点打分选最高分;策略有 nodeSelector、亲和性、污点/容忍度。
  • Pending 先查调度:Pod 卡在 Pending 多半是调度问题(标签/污点/资源),kubectl describe 看 Events 是第一步。
  • 过渡:理解了 Pod,下一步可以看 Deployment / ReplicaSet 如何用"多个 Pod 副本"实现高可用与滚动发布。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!