K8S学习笔记:Pod

本文是自己的学习笔记


1、pod的基本概念

在 Kubernetes (K8s) 中,Pod 是你可以创建和管理的最小、最基础的部署单元。

如果你把 Kubernetes 当作一个大型的货运码头,那么 Pod 就是里面的集装箱。虽然容器(比如 Docker 容器)是真正运行应用程序的地方,但 Kubernetes 并不直接管理单个容器,而是管理 Pod。

一个 Pod 可以只包含一个容器(最常见),也可以包含多个紧密关联的容器。

无论 Pod 里有几个容器,它们都会共享相同的:

  • 网络命名空间(Network Namespace): Pod 内的所有容器共享同一个 IP 地址和端口空间。它们之间可以通过 localhost 直接通信。
  • 存储卷(Volumes): 可以定义共享的存储卷,挂载到 Pod 内的不同容器中,方便它们共享文件。

2、Pause根节点

在 Kubernetes 中,如果你去查看一个正在运行的 Pod 底层的容器(比如在 Node 节点上执行 docker ps 或 crictl ps),你会发现除了你自己在 YAML 里定义的业务容器之外,永远多出来一个叫作 pause 的容器。

这个 pause 容器被称为 Infra Container(基础设施容器)。它是每个 Pod 的根容器,扮演着极其关键的"幕后功臣"角色。

2.1、Pause存在的意义

要理解 pause 的存在,我们需要先看一个核心矛盾:

在 Linux 操作系统中,容器的本质只是一个进程组,它通过 Namespace(命名空间)实现隔离。如果一个 Pod 里有多个容器,它们怎么做到共享相同的网络和存储?

如果其中一个业务容器崩溃重启了,它怎么保证重启后还能拿到原先的 IP 和网络状态?如果让业务容器之间互相依赖(比如容器 B 共享容器 A 的网络),一旦容器 A 挂了,容器 B 的网络也就断了,拓扑结构就会混乱。

为了解决这个问题,Kubernetes 引入了 pause 容器。

2.2、pause容器的两大核心原则

  • 孤儿进程的"收尸队"(PID 1 进程):在 Linux 系统中,当一个父进程退出后,它的子进程会变成"孤儿进程",并被移交给 PID 1(通常是 systemd 进程)来回收,防止它们变成僵尸进程(Zombie Processes)占用系统资源。
    在 Pod 开启了 PID 命名空间共享(shareProcessNamespace: true)的情况下:
    pause 容器作为 Pod 的第一个容器启动,天然拥有 PID 1。
    当 Pod 里的业务容器产生孤儿进程时,pause 容器会负责接管并定期回收这些僵尸进程,保持 Pod 内部环境的整洁。
  • 网络和存储的"锚点"
    pause 容器启动后,它什么都不做,只是进入"暂停/睡眠"状态(这就是它叫 pause 的原因)。它存在的唯一目的就是占坑:
    1. 网络: K8s 会调用网络插件(如 Calico, Flannel)为这个 pause 容器创建 Network Namespace,并分配 Pod 的 IP 地址。之后启动的所有业务容器,都会通过 Linux 的 Network Namespace Join 机制,直接加入到 pause 的网络空间中。
    2. 存储: 共享的 Volume 也是先挂载到 pause 容器上,再共享给其他业务容器。
      这意味着,只要 pause 容器不倒,Pod 的 IP 地址、网络配置和卷挂载就永远不会丢失。即便你的业务容器(比如 Tomcat、Spring Boot)崩溃重启了一万次,它们重启后依然能无缝回到这个网络生态中。

可以将 Pod 比喻成一个四合院:

  • pause 容器是这个四合院的地基和围墙(提供了固定的门牌号/IP,和公用的院子/存储)。
  • 业务容器是住进四合院的居民。居民可以换(容器重启),但只要围墙(pause)还在,这个四合院(Pod)的地址和结构就永远不变。

3、镜像拉取原则

Kubernetes 中,配置 Pod 的镜像拉取策略(ImagePullPolicy)决定了 Kubelet 在启动容器时,是每次都从远程镜像仓库(如 Docker Hub、私有仓库)下载镜像,还是优先使用本地已经缓存的镜像。

策略值 (imagePullPolicy) 行为模式 适用场景
Always 每次启动 Pod 或重启容器时,必定去远程仓库检查并拉取最新镜像。 生产环境、测试环境中使用 latest 标签或频繁更新的镜像。
IfNotPresent 优先使用 Node 节点本地缓存的镜像。如果本地没有,才去远程仓库拉取。 推荐的最佳实践 。适合版本号固定的镜像(如 v1.2.3),可大幅缩短启动时间。
Never 只使用本地镜像。即使本地没有,也绝不尝试去远程拉取,而是直接报错。 离线集群、网络隔离环境,或者镜像已经提前手动导入到所有节点的场景。

下面是配置示例

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: my-java-app
  labels:
    app: backend
spec:
  containers:
  - name: java-container
    image: openjdk:17-jdk-slim
    # 🌟 在这里配置镜像拉取策略(支持 Always, IfNotPresent, Never)
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080

4、Pod资源限制

4.1、基本概念和配置

在 Kubernetes 中,如果你不对 Pod 的资源进行限制,某个容器可能会因为代码遇到死循环或内存泄露,把整个 Node 节点的物理资源全部耗尽,从而导致整个节点上的其他 Pod 统统崩溃。

所谓Node节点可能是一个真实的物理机或者云虚拟机,总之是一个能提供资源来跑程序的东西。

为了实现集群的稳定性和资源隔离,Kubernetes 提供了 Resources(资源限制) 配置,主要针对 CPU 和 Memory(内存)进行管理。

2

在 YAML 中配置资源限制时,你必须分清两个高频名词:requests(需求量)和 limits(限制量)。简单来说requests是期望得到的资源,而limits是最多能拿到的资源

资源限制需要配置在 spec.containers\[\].resources 中。以下是一个标准的 Java 服务配置示例:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: java-service-pod
spec:
  containers:
  - name: java-app
    image: openjdk:17-jdk-slim
    resources:
      # 🌟 1. 调度所需的初始/最小资源
      requests:
        memory: "1Gi"      # 期望分到 1GB 内存
        cpu: "500m"        # 期望分到 0.5 个 CPU 核心
      # 🌟 2. 运行时的硬性最高上限
      limits:
        memory: "2Gi"      # 最高允许使用 2GB 内存,超过则 OOMKilled
        cpu: "1000m"       # 最高允许使用 1 个 CPU 核心,超过则限流

4.2、超限惩罚

超限后的不同下场:

  • CPU 超过 limits: 不会触发崩溃,但容器会被限流(Throttling)。操作系统的 cgroups 机制会限制其 CPU 时钟周期,导致你的服务响应变慢、吞吐量急剧下降。
  • 内存(Memory)超过 limits: 无法像 CPU 那样压缩。一旦超过,容器会立刻被操作系统执行 OOMKilled(Out Of Memory Killed) 强行杀掉,通常会在 Pod 状态中看到 CrashLoopBackOff。

5、重启机制

5.1、重启策略

在 Kubernetes 中,重启策略(RestartPolicy) 决定了当 Pod 中的容器因为各种原因退出(无论是正常运行结束,还是代码报错崩溃)时,Kubernetes 该如何处理这个容器。

合理配置重启策略能够保证你的批处理任务不会无休止地死循环,同时也能确保你的常驻服务(如 Java 微服务)在崩溃后能自动复活。

Kubernetes 提供了三种重启策略值,所有的策略都是基于节点(Node)本地的 Kubelet 自动执行的,不涉及跨节点的漂移。

策略值 (restartPolicy) 行为模式 适用场景
Always 无论 容器是以什么状态退出(不管是正常结束返回码 0,还是异常崩溃),Kubelet 都会无条件重新启动该容器。 推荐的最佳实践 。适合常驻内存的后台服务(如 Spring BootNginxMySQL)。
OnFailure 只有当容器异常退出 (返回码非 0)时,Kubelet 才会重启它;如果容器正常运行结束 (返回码为 0),则不重启。 适合定时任务或批处理作业(如 CronJobJob),只在出错时重试。
Never 无论 容器是正常结束还是异常崩溃,Kubelet绝不重启它。 适合一次性的数据探索、日志收集脚本,或者不希望自动恢复的排查任务。

5.2、配置

在实际生产中,高级控制器对 restartPolicy 有硬性限制:

Deployment / StatefulSet: 它们的职责是保证服务一直在线,因此其模板里的 restartPolicy 必须且只能是 Always(如果不写,默认就是 Always)。

Job / CronJob: 它们的职责是完成任务,因此其模板里的 restartPolicy 只能是 OnFailure 或 Never,绝对不能是 Always(否则任务结束了也会被不断重启,导致死循环)。

Job 示例:

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: data-migration-job
spec:
  template:
    spec:
      # 🌟 Job 类型必须配置为 OnFailure 或 Never
      restartPolicy: OnFailure
      containers:
      - name: migration-tool
        image: my-registry.com/tools/db-migrate:v1.0

6、健康检查

6.1、三个核心探针

K8s 主要提供了两种最核心的检查机制:存活检查(Liveness Probe) 和 就绪检查(Readiness Probe)。另外还有一个用于解决启动慢问题的启动检查(Startup Probe)。

在 Kubernetes 中,健康检查(Probes,探针)是保证应用高可用和自动化运维的核心机制。简单来说,它是 K8s 用来了解容器当前"活得怎么样"和"能不能干活"的手段。

探针类型 它的核心目的 失败后的动作 经典应用场景
存活检查 LivenessProbe 确定容器是否还在正常运行。如果死锁或卡死,及时重启。 重启容器 应用发生死锁(Deadlock)、内存溢出(OOM)导致僵死、或者无限死循环。
就绪检查 ReadinessProbe 确定容器是否已经准备好接收流量 将 Pod 从 Service 的后端中剔除,不再分发流量。 应用启动时需要加载大量缓存、连接数据库,或者在大流量下暂时过载。
启动检查 StartupProbe 保护启动极其缓慢的应用。在它成功前,禁用其他探针。 重启容器 老旧的单体应用、需要初始化数分钟的巨型服务。

下面这张流程图清晰地展示了三种探针(StartupProbe, LivenessProbe, ReadinessProbe)在 Pod 生命周期中的协作关系:

Pod 启动

┌──────────────┐

│ Startup │ ──(失败)──> 重启容器

│ Probe 检查 │

└──────────────┘

│ (成功)

┌────────────────────────┐

├─> Liveness Probe 监控 ─┼──(失败)──> 重启容器

│ │

├─> Readiness Probe 监控 ─┼──(失败)──> 流量隔离,断开 Service

└────────────────────────┘

6.2、探针的检测机制

K8s 允许你使用以下三种方式之一来探测容器的健康状况,我们可以通过配置来决定如何检查。

  1. HTTP 请求 (httpGet)

    最常用的方式。K8s 定期向容器的某个接口(如 /healthz 或 /ready)发送 HTTP GET 请求。如果返回码在 200-399 之间,算成功;否则算失败。这里是可以自定义的,比如

  2. TCP 端口检查 (tcpSocket)

    K8s 尝试与容器指定的端口建立 TCP 连接。如果能三次握手成功建立连接,算成功;如果连接被拒绝或超时,算失败。通常用于没有 HTTP 接口的服务(如 MySQL、Redis)。

  3. 执行命令 (exec)

    K8s 在容器内执行一条指定的 shell 命令(如 cat /tmp/healthy)。如果命令的退出状态码是 0,算成功;如果是其他数字,算失败。

6.3、探针的注意事项

绝对不要让 Liveness 依赖外部依赖:LivenessProbe 的失败动作是重启。如果你的应用因为数据库挂了导致 /health 接口报错,而你把它配成了 Liveness,K8s 会疯狂重启你的容器。这不仅解决不了数据库问题,还会让你的应用因为不断重启而彻底无法对外提供任何服务。数据库挂了,应该让 Readiness 失败,从而隔离流量,而不是重启容器。

区分 Liveness 和 Readiness 的接口:

Liveness 应该检查应用的内部状态(死锁、线程池爆炸)。

Readiness 应该检查应用的外部可达性(数据库连接、Redis 缓存、依赖的第三方服务是否就绪)。

合理配置超时(timeoutSeconds):如果下游接口偶尔波动导致响应变慢,而你的超时时间配得极短(如 1s),可能会引发大规模的 Pod 被判定为未就绪,从而导致服务雪崩。

6.4、探针和spring-actuator的优雅实践

  1. 引入 Actuator 依赖
    首先,在你的 pom.xml 中引入 Actuator 的 Maven 依赖:
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <groupId>spring-boot-starter-actuator</groupId>
</dependency>
  1. 在 application.yml 中开启探针端点
    默认情况下,K8s 的探针端点是关闭的(或者只有在部署到 K8s 集群时才会自动激活)。为了方便本地开发测试和显式控制,建议在配置文件中明确开启:
yml 复制代码
management:
  endpoint:
    health:
      probes:
        enabled: true  # 显式开启 Liveness 和 Readiness 探针端点
      show-details: always # 可选:在开发环境查看详细的健康组件状态
  endpoints:
    web:
      exposure:
        include: health # 确保健康检查端点允许被外部 Web 访问

配置完成后,Actuator 会为你自动生成两个专用的子路由:

Liveness(存活): /actuator/health/liveness

Readiness(就绪): /actuator/health/readiness

  1. Kubernetes 端的 YAML 配置
    在你的 Kubernetes Deployment 清单中,将这两个路径分别配置到对应的探针上:
yml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-app
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: spring-boot-app
    spec:
      containers:
      - name: app
        image: my-spring-app:v1
        ports:
        - containerPort: 8080
        
        # 1. 启动探针 (可选但推荐,防止应用启动慢被 Liveness 误杀)
        startupProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          failureThreshold: 12
          periodSeconds: 5  # 给应用最多 60 秒的启动时间
          
        # 2. 存活探针
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          periodSeconds: 10
          
        # 3. 就绪探针
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          periodSeconds: 10