本文是自己的学习笔记
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 的原因)。它存在的唯一目的就是占坑:- 网络: K8s 会调用网络插件(如 Calico, Flannel)为这个 pause 容器创建 Network Namespace,并分配 Pod 的 IP 地址。之后启动的所有业务容器,都会通过 Linux 的 Network Namespace Join 机制,直接加入到 pause 的网络空间中。
- 存储: 共享的 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 Boot、Nginx、MySQL)。 |
OnFailure |
只有当容器异常退出 (返回码非 0)时,Kubelet 才会重启它;如果容器正常运行结束 (返回码为 0),则不重启。 |
适合定时任务或批处理作业(如 CronJob、Job),只在出错时重试。 |
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 允许你使用以下三种方式之一来探测容器的健康状况,我们可以通过配置来决定如何检查。
-
HTTP 请求 (httpGet)
最常用的方式。K8s 定期向容器的某个接口(如 /healthz 或 /ready)发送 HTTP GET 请求。如果返回码在 200-399 之间,算成功;否则算失败。这里是可以自定义的,比如
-
TCP 端口检查 (tcpSocket)
K8s 尝试与容器指定的端口建立 TCP 连接。如果能三次握手成功建立连接,算成功;如果连接被拒绝或超时,算失败。通常用于没有 HTTP 接口的服务(如 MySQL、Redis)。
-
执行命令 (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的优雅实践
- 引入 Actuator 依赖
首先,在你的 pom.xml 中引入 Actuator 的 Maven 依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<groupId>spring-boot-starter-actuator</groupId>
</dependency>
- 在 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
- 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