Kubernetes Pod 基本原理:全面详解

引言

Pod 是 Kubernetes 中最重要、最核心的概念,是应用部署的原子单元。理解 Pod,是掌握 Kubernetes 的基石。

Pod 是 Kubernetes 集群中最基本的调度单元,我们平时在集群中部署的应用都是以 Pod 为单位的,而并不是我们熟知的容器,这样设计的目的是什么呢?为何不直接使用容器呢?


一、为什么需要 Pod?(为什么不能直接使用容器?)

在容器技术已经存在的背景下,Kubernetes 为什么还要引入 Pod 这一新概念?答案在于,Pod 解决了直接调度单个容器无法满足的复杂应用场景。

其实在我们理解 Pod 的时候,有一个比较好的类比的方式就是把 Pod 看成我们之前的**"虚拟机"**,而容器就是虚拟机中运行的一个用户程序,这样就可以很好的来理解 Pod 的设计。

假设 Kubernetes 中调度的基本单元就是容器,对于一个非常简单的应用可以直接被调度直接使用,没有什么问题,但是往往还有很多应用程序是由多个进程组成的,有的同学可能会说把这些进程都打包到一个容器中去不就可以了吗?理论上是可以实现的,但是不要忘记了容器运行时管理的进程是**pid=1** 的主进程,其他进程死掉了就会成为僵尸进程,没办法进行管理了,这种方式本身也不是容器推荐的运行方式,一个容器最好**只干一件事情**,所以在真实的环境中不会使用这种方式。

那么我们就把这个应用的进程进行拆分,拆分成一个一个的容器总可以了吧?但是不要忘记一个问题,拆分成一个一个的容器后,是不是就有可能出现一个应用下面的某个进程容器被调度到了不同的节点上呀?往往我们应用内部的进程与进程间通信(通过 IPC 或者共享本地文件之类)都是要求在本地进行的,也就是需要在同一个节点上运行。

所以我们需要一个更高级别的结构来将这些容器绑定在一起,并将他们作为一个基本的调度单元进行管理,这样就可以保证这些容器始终在同一个节点上面,这也就是 Pod 设计的初衷。

从容器到 Pod 的演进

直接调度容器的局限性

对于只有一个进程的简单应用,单个容器足矣。但现实世界中的应用通常由多个紧密协作的进程组成。如果将它们强行塞进一个容器,会带来诸多问题:

  • 违背最佳实践:破坏了"一个容器只做一件事"的原则,容器变得臃肿且难以维护。
  • 进程管理难题:容器内只有 PID=1 的主进程能被容器运行时有效管理。其他进程一旦成为孤儿,就可能变成僵尸进程,难以清理。
多容器独立调度的挑战

如果将应用拆分成多个容器独立调度,又会面临新的问题。这些容器可能被调度到集群中的不同节点上,导致依赖本地通信(如 IPC、共享内存、本地文件)的进程无法工作。

核心矛盾: 我们既想利用容器实现进程隔离,又希望这些进程能像在同一台机器上一样方便地通信和共享资源。

Pod 作为解决方案

Pod 正是为了解决这一矛盾而生。它是一个更高级别的逻辑结构,可以将一个或多个紧密协作的容器组合成一个原子单元。Kubernetes 始终将 Pod 作为一个整体进行调度和管理,确保其内部的容器:

  • 始终被调度到同一个节点上。
  • 共享网络和存储资源。
  • 拥有共同的生命周期。

你可以将 Pod 想象成一个"逻辑主机",而其中的容器则是运行在这台主机上的进程。这个比喻贯穿了 Pod 设计的方方面面。在一个 Pod 下面运行几个关系非常密切的容器进程,这样一来这些进程本身又可以收到容器的管控,又具有几乎一致的运行环境,也就完美解决了上面提到的问题。


二. Pod 的核心原理


Pod 本身不运行任何东西,它是一个为内部容器提供共享运行环境的"逻辑外壳"。其底层技术基石是 Linux 的 NamespacesCgroups

网络共享机制:Infra 容器(pause 容器)

Pod 如何实现内部所有容器共享同一个网络协议栈(IP 地址、端口空间)?答案是引入一个特殊的"Infra 容器"(也叫 Pause 容器)。Infra 容器是 Pod 中第一个创建的容器,其他容器都加入到它的 Network Namespace 中。

为什么需要 Infra 容器?

  • 解决容器启动顺序问题:其他容器需要依赖 Infra 容器的网络配置
  • Pod 的启动流程如下:
  1. Kubelet 首先创建 Infra 容器 。这个容器非常小(其镜像是 k8s.gcr.io/pause:3.x,需要科学上网,否则可能导致 Pod 启动失败),它的唯一作用就是创建并持有网络命名空间(Network Namespace)。
  2. 随后,Pod 中定义的其他业务容器被逐一创建。
  3. 这些业务容器在创建时,不会建立自己的网络命名空间,而是加入到 Infra 容器已经创建好的命名空间中。

Pod 中容器通信特点

  • 本地通信 :容器之间可以通过 localhost 直接访问对方的服务,就像同一台机器上的两个进程。
  • 端口冲突:因为共享端口空间,同一个 Pod 内的不同容器不能监听相同的端口。
  • 共享 IP:Pod 内所有容器共享同一个 IP 地址。从集群外部看来,它们是一个整体。
  • 生命周期关联 :Pod 的网络生命周期与 Infra 容器绑定,与其他容器无关。Infra 容器一旦退出,网络命名空间就会被销毁,Pod 也随之消亡。这也是为什么当 Infra 镜像拉取失败时,Pod 会一直处于 Pending 状态。

三、存储共享机制:卷 (Volume)

默认情况下,容器的文件系统是相互隔离的。为了实现文件共享,Kubernetes 引入了 Volume 的概念。

在 Pod 层面定义的 Volume,可以被 Pod 内的任何容器挂载到其文件系统的任意路径。这为容器间的数据交换和数据持久化提供了强大支持。

YAML 示例:日志写入与读取

以下示例定义了一个 Pod,包含两个容器:一个持续向日志文件写入内容,另一个则读取并打印该文件的内容。它们通过共享一个 Volume 来实现协作。

复制代码
apiVersion: v1
kind: Pod
metadata:
  name: shared-volume-demo
spec:
  # 1. 在 Pod 层面定义一个名为 "shared-logs" 的 Volume
  # 这里使用 emptyDir,表示这是一个随 Pod 生命周期存在的临时目录
  volumes:
  - name: shared-logs
    emptyDir: {}

  containers:
  # 容器1:日志写入者
  - name: writer-container
    image: busybox
    command: ["/bin/sh", "-c"]
    args:
      - >
        i=0;
        while true; do
          echo "$(date) - Log entry $i" >> /var/log/app.log;
          i=$((i+1));
          sleep 2;
        done
    # 2. 将 "shared-logs" Volume 挂载到容器内的 /var/log 目录
    volumeMounts:
    - name: shared-logs
      mountPath: /var/log

  # 容器2:日志读取者
  - name: reader-container
    image: busybox
    command: ["/bin/sh", "-c", "tail -f /data/app.log"]
    # 3. 将同一个 "shared-logs" Volume 挂载到容器内的 /data 目录
    volumeMounts:
    - name: shared-logs
      mountPath: /data

在这个例子中,writer-container 写入 /var/log/app.log 的内容,会立刻被 reader-container 在其 /data/app.log 路径下读取到,因为它们指向的是底层同一个由 Volume 管理的目录。

四、 Pod 的典型应用场景与设计模式

理解了 Pod 的原理后,我们来看看如何在实践中利用它来设计更优秀的云原生应用。

1. 设计模式:Sidecar 边车模式(最常见应用)

定义:在同一个 Pod 中运行一个主容器和一个辅助容器(sidecar),辅助容器提供额外功能。

Sidecar 是最著名也是最强大的 Pod 设计模式。其核心思想是:将主应用的核心业务逻辑与辅助功能(如日志、监控、网络代理)解耦,将辅助功能放到一个独立的"边车"容器中,与主应用容器一起运行在同一个 Pod 里。

**典型场景->**场景1:日志收集

主应用容器只负责将日志输出到标准输出或写入共享卷。一个 Sidecar 容器(如 Fluentd, Logstash)负责从共享卷中读取日志,进行处理,然后发送到集中的日志存储后端(如 Elasticsearch)。

场景2:网络代理与服务网格

利用 Pod 共享网络的特性,可以部署一个网络代理 Sidecar(如 Envoy, Linkerd-proxy),它会拦截主应用容器的所有进出流量。这使得我们可以在不修改主应用代码的情况下,实现流量控制、服务发现、熔断、遥测、安全加密等高级功能。这正是服务网格(Service Mesh)技术如 Istio 的核心实现原理。

场景3:多容器协同工作
  • 主应用容器 + 辅助工具容器(如数据处理、监控等)
  • 例如:Web 服务器容器 + 用于处理静态资源的容器

如何合理划分 Pod:聚合还是拆分?

"哪些容器应该放在同一个 Pod 中?"是设计应用时经常遇到的问题。一个常见的错误是将完全独立的组件放在同一个 Pod 中。

反面教材:Wordpress 与 MySQL

将 Wordpress(Web 服务器)和 MySQL(数据库)放在同一个 Pod 中是典型错误。因为它们:

  • 生命周期不同:数据库通常比前端应用更稳定,不应随前端的频繁发布而重启。
  • 伸缩需求不同:当网站流量增大时,你可能需要 3 个 Wordpress 副本,但通常只需要 1 个 MySQL 主库。如果它们在同一个 Pod,扩容 Pod 会创建出 3 个 Wordpress 和 3 个无用的、数据不一致的 MySQL 副本。

正确做法:将 Wordpress 和 MySQL 分别部署在不同的 Deployment 中,它们各自拥有自己的 Pod。Wordpress Pod 通过 Service 来访问 MySQL Pod。

划分 Pod 的三大判断原则

在决定是否将多个容器放入一个 Pod 时,可以问自己以下三个问题:

  1. 共同生命周期?:这些容器是否必须一起启动、一起停止?
  2. 部署原子性?:它们是否像一个不可分割的整体?你能否容忍它们被调度到不同的节点上?
  3. 伸缩一致性?:它们是否需要以相同的副本数进行扩缩容?

如果三个问题的答案都是"是",那么它们很可能适合放在同一个 Pod 中。否则,就应该将它们拆分到不同的 Pod。


五、 Pod 生命周期与状态

Pod 的生命周期状态(如 Pending, Running, Succeeded, Failed)直接反映了其内部容器,特别是 Infra 容器的状态。

  • Pending: Pod 已被 apiserver 接受,但其内部一个或多个容器尚未创建。常见原因包括:调度器找不到合适的节点、或节点正在拉取容器镜像(尤其是 Infra 镜像)。
  • Running: Pod 已绑定到一个节点,所有容器都已创建。至少有一个容器仍在运行,或者正在启动/重启。
  • Succeeded: Pod 内所有容器都已成功执行并终止,且不会再重启。
  • Failed: Pod 内所有容器都已终止,但至少有一个容器是因失败而终止的。
容器类型与启动顺序

一个 Pod 内的容器可以分为两类,它们有明确的启动顺序:

  • Init 容器 (Init Containers): 在所有主应用容器启动之前按顺序运行。每个 Init 容器都必须成功完成后,下一个才会启动。常用于初始化配置、准备环境、等待依赖服务等。
  • 应用容器 (App Containers): Pod 中运行核心业务逻辑的容器。它们在所有 Init 容器成功完成后,并行启动。

六、 Pod 调度与资源管理

Pod 作为原子调度单元

Kubernetes 的调度器(Scheduler)的工作对象是 Pod,而不是容器。调度决策的核心目标是为整个 Pod 在集群中找到一个最合适的节点(Node),然后将 Pod "绑定"到该节点上,由该节点的 Kubelet 负责启动 Pod 内的容器。

资源配置 (高级主题)

你可以通过 resources 字段为每个容器设置资源需求和限制:

  • requests: 容器期望获得的资源量。这是调度器进行调度决策的重要依据。节点必须有足够的可用资源满足 Pod 内所有容器的 requests 总和。
  • limits: 容器被允许使用的资源上限。超出限制可能会导致容器被终止(CPU)或被驱逐(Memory)。
容器探针 (高级主题)

Kubelet 使用探针(Probe)来检查容器的健康状况:

  • Liveness Probe (存活探针): 判断容器是否还在正常运行。如果探针失败,Kubelet 会杀死并重启容器。
  • Readiness Probe (就绪探针): 判断容器是否准备好接收外部流量。如果探针失败,该 Pod 的 IP 会从关联的 Service 的端点列表中移除。
  • Startup Probe (启动探针): 用于启动时间较长的应用,防止它们在完全就绪前被存活探针过早地杀死。

七、常见问题与解决方案

1. Pod 一直处于 Pending 状态

原因:Infra 容器镜像拉取失败(k8s.gcr.io/pause:3.5 需要科学上网)

解决方案

  • 配置镜像仓库代理
  • 使用国内镜像(如 registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.5
  • 在 kubelet 启动参数中指定镜像:--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.5

2. 容器间无法通信

原因

  • 没有正确配置共享 Network Namespace
  • 容器没有正确挂载 Volume

解决方案

  • 确认容器是否在同一个 Pod 中
  • 检查容器是否正确挂载了共享 Volume

3. 同一 Pod 中容器无法绑定相同端口

原因:所有容器共享同一个 Network Namespace,端口冲突

解决方案

  • 避免在同一个 Pod 中的容器使用相同端口
  • 如果必须使用相同端口,考虑将它们拆分为不同的 Pod
相关推荐
PKNLP3 小时前
07.docker介绍与常用命令
运维·docker·容器
阿里云云原生3 小时前
评估工程正成为下一轮 Agent 演进的重点
云原生
掘根5 小时前
【Docker】网络
网络·docker·容器
高旭博6 小时前
10. kubernetes资源——statefulset有状态负载
云原生·容器·kubernetes
_Walli_6 小时前
k8s集群搭建(七)-------- 微服务间的调用
微服务·容器·kubernetes
马达加斯加D7 小时前
k8s --- resource: Pod, ReplicaSet and Deployment
云原生·容器·kubernetes
Candice_jy11 小时前
vscode运行ipynb文件:使用docker中的虚拟环境
服务器·ide·vscode·python·docker·容器·编辑器
CS创新实验室13 小时前
从穿孔卡片到云原生:批处理系统的不朽演进与核心思想
云原生·操作系统·批处理
檐下翻书17313 小时前
Spring Boot 深度剖析:从虚拟线程到声明式 HTTP 客户端,再到云原生最优解
spring boot·http·云原生