本文是自己的学习笔记



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只使用本地镜像。即使本地没有,也绝不尝试去远程拉取,而是直接报错。离线集群、网络隔离环境,或者镜像已经提前手动导入到所有节点的场景。

下面是配置示例

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 服务配置示例:

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 示例:

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 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <groupId>spring-boot-starter-actuator</groupId>
</dependency>
  1. 在 application.yml 中开启探针端点
    默认情况下,K8s 的探针端点是关闭的(或者只有在部署到 K8s 集群时才会自动激活)。为了方便本地开发测试和显式控制,建议在配置文件中明确开启:
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 清单中,将这两个路径分别配置到对应的探针上:
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

更多推荐