25 KiB
tags, create time
| tags | create time | |||
|---|---|---|---|---|
|
2026-07-07 15:48 |
Pod — Kubernetes 最小调度单元
概述
Pod 是 Kubernetes 中最小的可部署和调度单元。它不是一个容器,而是一组(一个或多个)共享网络和存储资源的容器的逻辑封装。为什么 Kubernetes 不直接调度容器,而是引入了 Pod 这一抽象层?因为在实际业务中,多个紧密协作的进程往往需要共享同一个 IP 地址、端口空间和卷挂载——比如一个 Web 应用容器和一个负责收集日志的 sidecar 容器。Pod 正是为了解决这种「亲密耦合进程协同运行」的需求而设计的。理解 Pod,是掌握 Kubernetes 一切上层抽象(Deployment、StatefulSet、DaemonSet 等)的基础。
Pod 的本质
共享网络命名空间
同一个 Pod 内的所有容器共享同一个网络命名空间(Network Namespace),这意味着:
- 它们拥有相同的 IP 地址
- 它们可以通过 localhost 互相通信
- 端口空间是共享的,因此同一 Pod 内的容器不能绑定相同的端口
[!question] 为什么不直接调度容器? 因为单个容器通常只运行一个进程(这是容器设计的最佳实践)。但在很多场景下,多个进程需要紧密协作——比如 Nginx + 日志收集器、主应用 + 配置热加载 sidecar。如果让调度器去理解「哪些容器应该放在一起」,复杂度会急剧上升。Pod 将这个决策交给了用户:你来决定哪些容器属于同一个 Pod。
Pause 容器 — Pod 的「灵魂」
每个 Kubernetes Pod 启动时,都会先创建一个特殊的Pause 容器(也叫 infra 容器),它是 Pod 的第一个容器,也是整个 Pod 的「根进程」。其他业务容器通过加入 Pause 容器的 Network Namespace 和 PID Namespace 来实现共享。
graph TB
subgraph Pod
P["Pause 容器<br/>(infra container)"]
C1["业务容器 A"]
C2["业务容器 B<br/>(sidecar)"]
C3["业务容器 C"]
end
P -->|"提供 Network NS"| C1
P -->|"提供 Network NS"| C2
P -->|"提供 Network NS"| C3
P -->|"提供 PID NS (可选)"| C1
P -->|"提供 PID NS (可选)"| C2
P -->|"提供 PID NS (可选)"| C3
Pause 容器的职责非常简单:
- 持有 Network Namespace — 确保 Pod 的 IP 地址在所有容器生命周期内保持不变
- 充当 PID 1 — 回收僵尸进程(zombie reaping)
- 极轻量 — 通常只有几百 KB,使用
registry.k8s.io/pause镜像
[!info] Pause 容器的代码只有几百行 它的核心逻辑就是一个无限循环的
pause()系统调用。正因为足够简单,它几乎不会出问题,可以稳定地为 Pod 内所有容器提供基础运行环境。
容器与 Pod 的关系(1:N)
一个 Pod 可以包含多个容器,典型模式如下:
| 模式 | 说明 | 示例 |
|---|---|---|
| 单容器 Pod | 最常见的场景,一个 Pod 运行一个应用容器 | 一个 Go Web 服务 |
| 多容器 Pod (sidecar) | 主容器 + 辅助容器,辅助容器扩展主容器功能 | Nginx + Filebeat 日志收集 |
| 多容器 Pod (adapter) | 辅助容器将主容器的输出转换为标准格式 | 应用日志 → Fluentd 标准格式 |
| 多容器 Pod (ambassador) | 辅助容器代理网络请求 | 应用 → Envoy Proxy → 外部服务 |
# 一个多容器 Pod 的简单示例:Web 服务 + 日志收集 sidecar
apiVersion: v1
kind: Pod
metadata:
name: web-with-logging
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
- name: log-collector
image: fluent/fluent-bit:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
readOnly: true
volumes:
- name: log-volume
emptyDir: {}
两个容器共享 log-volume 卷,Web 容器写入日志,Fluent Bit 容器读取并转发——这是经典的日志收集 sidecar 模式。
Pod 生命周期
Pod 的生命周期由**阶段(Phase)和条件(Condition)**共同描述。从创建到销毁,Pod 经历以下主要阶段:
stateDiagram-v2
[*] --> Pending: kubectl apply / API 创建
Pending --> Running: 至少一个容器启动成功
Pending --> Failed: 拉取镜像失败 / 调度失败
Running --> Succeeded: 所有容器正常退出(exit 0)
Running --> Failed: 容器异常退出 / OOMKilled
Running --> Unknown: Node 失联, 无法获取状态
Succeeded --> [*]
Failed --> [*]
Unknown --> Running: Node 恢复通信
Unknown --> Failed: 确认 Pod 已丢失
Init Containers
Init Containers 是在主容器启动之前按顺序运行的容器。它们主要用于完成一些前置条件的检查和准备工作。
与主容器的区别:
| 特性 | Init Container | 主容器 (App Container) |
|---|---|---|
| 执行顺序 | 严格按顺序依次执行 | 并行启动 |
| 失败行为 | 失败后 Pod 重启(遵循 restartPolicy) | 失败后根据探针配置重启 |
| 探针支持 | 不支持 liveness/readiness probe | 支持 |
| 用途 | 前置条件检查、等待依赖、初始化配置 | 运行业务逻辑 |
| 资源计算 | 独立于主容器的资源请求 | 作为整体调度依据 |
[!tip] 何时使用 Init Container? 当你需要在主容器启动前完成某些「只执行一次」的任务时——比如等待数据库就绪、下载配置文件、执行数据库迁移等——Init Container 是最佳选择。
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
# 第一个 Init Container:等待数据库就绪
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z mysql-service 3306; do
echo "Waiting for MySQL..."
sleep 2
done
echo "MySQL is ready!"
# 第二个 Init Container:执行数据库迁移
- name: db-migration
image: myapp/migration:v1.2
env:
- name: DB_HOST
value: "mysql-service"
containers:
- name: app
image: myapp/api:v1.2
ports:
- containerPort: 8080
上述示例中,两个 Init Container 严格按顺序执行:先等 MySQL 可达,再执行数据库迁移。只有两者都成功(exit 0)后,主容器 app 才会启动。
容器探针
Kubernetes 提供三种探针来监控容器的健康状态:
| 探针类型 | 作用 | 失败后果 | 典型场景 |
|---|---|---|---|
livenessProbe |
检测容器是否存活 | 杀死容器并重启 | 进程死锁、无限循环 |
readinessProbe |
检测容器是否就绪接收流量 | 从 Service 的 Endpoints 中移除 | 启动预热、临时过载 |
startupProbe |
检测容器是否启动完成 | 杀死容器并重启 | 启动耗时长的应用(如 Java) |
[!warning] startupProbe 与 livenessProbe 的关系 当配置了
startupProbe时,在它成功之前,livenessProbe和readinessProbe都不会执行。这是为了防止启动时间较长的应用被误判为不健康。startupProbe的failureThreshold * periodSeconds应该大于应用的最大启动时间。
探针支持的检测方式:
- httpGet — 发送 HTTP GET 请求,2xx/3xx 视为成功
- tcpSocket — 尝试 TCP 连接,端口可达视为成功
- exec — 执行命令,exit code 0 视为成功
- grpc — gRPC 健康检查协议(Kubernetes 1.24+)
apiVersion: v1
kind: Pod
metadata:
name: probes-demo
spec:
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
# 启动探针:最多给 60 秒启动时间 (10 次 x 6 秒)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 10
periodSeconds: 6
# 存活探针:每 15 秒检测一次,连续 3 次失败则重启
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # startupProbe 成功后才开始
periodSeconds: 15
failureThreshold: 3
timeoutSeconds: 5
# 就绪探针:每 5 秒检测一次,失败则从 Endpoints 移除
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 3
[!tip] 最佳实践 生产环境中建议同时配置三种探针:
startupProbe保护慢启动应用、livenessProbe检测死锁、readinessProbe控制流量切换。readinessProbe的检测频率可以比livenessProbe更高,因为它的失败不会触发重启。
Pod 阶段(Phase)与条件(Condition)
Pod Phase 是对 Pod 在其生命周期中所处位置的高层概括:
| Phase | 说明 |
|---|---|
Pending |
Pod 已被 API Server 接受,但尚未调度或容器镜像未拉取完成 |
Running |
Pod 已绑定到节点,且至少有一个容器处于运行状态 |
Succeeded |
所有容器正常退出(exit 0),不会再重启 |
Failed |
所有容器已终止,且至少有一个容器是非正常退出 |
Unknown |
无法获取 Pod 状态,通常是与 Node 的通信中断 |
Pod Conditions 提供了更细粒度的状态信息,是一个数组,每个 Condition 包含以下字段:
conditions:
- type: PodScheduled # 条件类型
status: "True" # True / False / Unknown
lastProbeTime: null # 上次探测时间
lastTransitionTime: "2026-07-07T07:00:00Z" # 上次状态转换时间
reason: "SchedulerCompletedSchedulingReason"
message: "Successfully assigned default/my-pod to node-1"
常见的 Condition Type:
| Type | 说明 |
|---|---|
PodScheduled |
Pod 已被调度到某个节点 |
Initialized |
所有 Init Container 已成功完成 |
ContainersReady |
所有容器都已就绪 |
Ready |
Pod 可以对外提供服务,被纳入 Service Endpoints |
graph LR
A["PodScheduled<br/>status: True"] --> B["Initialized<br/>status: True"]
B --> C["ContainersReady<br/>status: True"]
C --> D["Ready<br/>status: True"]
[!info] Condition vs Phase Phase 是一个全局性的、互斥的状态值(一个 Pod 在同一时刻只有一个 Phase)。而 Conditions 是一组独立的布尔标志,一个 Pod 可以同时有多个 Condition 为 True。Conditions 通常用于精确排查 Pod 启动过程中的具体卡点。
资源管理
Requests 与 Limits
Kubernetes 通过 requests 和 limits 两个维度来管理容器的资源:
- requests(请求量) — 调度器依据此值决定 Pod 放到哪个节点。是容器运行所需的最小资源量
- limits(限制量) — 容器可使用的最大资源量。超出限制时的行为取决于资源类型
resources:
requests:
cpu: "250m" # 0.25 核
memory: "128Mi" # 128 MiB
limits:
cpu: "500m" # 0.5 核
memory: "256Mi" # 256 MiB
[!question] CPU 超限和 Memory 超限的处理方式为什么不同? CPU 是可压缩资源(compressible)——超出 limit 时会被节流(throttle),进程不会被杀死。而内存是不可压缩资源(incompressible)——超出 limit 时进程会被 OOMKilled。这是因为 CPU 可以通过时间片轮转来降级服务,但内存一旦不足,系统无法「回收」已分配的内存页面。
QoS(Quality of Service)三级分类
Kubernetes 根据 requests 和 limits 的配置将 Pod 分为三个 QoS 等级:
| QoS 等级 | 条件 | OOM 优先级 | 调度优先级 |
|---|---|---|---|
| Guaranteed | 每个容器的 requests == limits | 最低(最后被杀) | 最高 |
| Burstable | 至少一个容器设置了 requests,且 requests < limits | 中等 | 中等 |
| BestEffort | 所有容器均未设置 requests 和 limits | 最高(最先被杀) | 最低 |
# Guaranteed — requests 等于 limits
containers:
- name: critical-app
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "1"
memory: "1Gi"
---
# Burstable — requests 小于 limits
containers:
- name: normal-app
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "1"
memory: "512Mi"
---
# BestEffort — 未设置任何 requests / limits
containers:
- name: batch-job
image: busybox
# 未配置 resources 字段
[!warning] 生产环境严禁 BestEffort BestEffort Pod 在节点资源紧张时会被最先驱逐。在生产环境中,至少应为所有容器设置合理的
requests。
OOM(Out of Memory)行为
当节点内存不足时,kubelet 会按照以下顺序选择 Pod 进行驱逐:
- BestEffort Pod(OOM Score 最高)
- Burstable Pod 中,实际内存使用量超过 requests 最多的
- Guaranteed Pod(OOM Score 最低,最后被杀)
同时,Linux 内核的 OOM Killer 也会根据进程的 oom_score_adj 值来决定杀哪个进程。Kubernetes 为不同 QoS 等级的容器设置了不同的 oom_score_adj:
| QoS 等级 | oom_score_adj |
|---|---|
| Guaranteed | -998 |
| Burstable | 2 ~ 1000(按比例) |
| BestEffort | 1000 |
CPU 节流机制
当容器的 CPU 使用超过 limit 时,Linux 内核的 CFS(Completely Fair Scheduler)会通过 CFS Bandwidth Throttling 机制限制容器的 CPU 时间片分配。
表现症状:
- 容器进程运行变慢,响应延迟升高
container_cpu_cfs_throttled_periods_total指标持续增长- 应用程序的 P99 延迟出现周期性毛刺
[!tip] 避免 CPU 节流 如果发现应用延迟升高且 CPU throttling 严重,考虑:① 提高 CPU limit;② 将 limit 设置为与 request 相同(Guaranteed QoS,不会被节流但调度更严格);③ 使用
CPU Manager Policy: static获得独占 CPU 核心。
Sidecar 模式
Sidecar 模式是 Pod 多容器设计中最常用的架构模式。通过在 Pod 中添加辅助容器(sidecar),可以在不修改主应用代码的前提下扩展其功能。
日志收集
apiVersion: v1
kind: Pod
metadata:
name: app-logging-sidecar
spec:
containers:
- name: app
image: myapp:v2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: log-shipper
image: fluent/fluent-bit:2.2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
readOnly: true
- name: fluentbit-config
mountPath: /fluent-bit/etc/
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
volumes:
- name: shared-logs
emptyDir: {}
- name: fluentbit-config
configMap:
name: fluentbit-config
服务网格代理(Service Mesh Proxy)
这是 Istio、Linkerd 等服务网格的典型数据面模式——每个应用 Pod 旁注入一个 Envoy sidecar 代理:
apiVersion: v1
kind: Pod
metadata:
name: app-with-proxy
annotations:
sidecar.istio.io/inject: "true"
spec:
containers:
- name: app
image: myapp:v3
ports:
- containerPort: 8080
# 以下通常由 Istio 自动注入,这里手动展示
- name: istio-proxy
image: envoyproxy/envoy:v1.28
args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
ports:
- containerPort: 15001 # Envoy 入口
- containerPort: 15006 # Envoy 出口
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
配置热更新(Reloader Sidecar)
某些应用不支持热加载配置文件,此时可以用一个 sidecar 监听 ConfigMap 变化并通知主容器:
apiVersion: v1
kind: Pod
metadata:
name: app-config-reload
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
- name: config-reloader
image: jimmidyson/configmap-reload:v0.13
args:
- --volume-dir=/config
- --webhook-url=http://localhost:8080/-/reload
volumeMounts:
- name: config-volume
mountPath: /config
readOnly: true
volumes:
- name: config-volume
configMap:
name: nginx-config
Pod 调度基础
Kubernetes 调度器(kube-scheduler)负责将 Pod 分配到合适的节点上。Pod 本身可以通过以下字段影响调度决策:
nodeSelector
最简单的调度约束,通过标签匹配将 Pod 调度到特定节点:
apiVersion: v1
kind: Pod
metadata:
name: gpu-task
spec:
nodeSelector:
gpu: "true" # 必须调度到带有 gpu=true 标签的节点
disktype: "ssd"
containers:
- name: ml-trainer
image: pytorch:v2
nodeName
直接指定节点名称,跳过调度器:
spec:
nodeName: node-3 # 直接绑定到 node-3,不经过调度器
[!warning] 慎用 nodeName 使用
nodeName会绕过调度器的资源检查、亲和性计算和污点容忍判断。如果目标节点资源不足,Pod 会一直处于 Pending 状态。一般仅用于测试或特殊运维场景。
亲和性(Affinity)简介
亲和性提供了比 nodeSelector 更丰富的调度规则:
- nodeAffinity — 节点亲和性,支持硬性(required)和软性(preferred)规则
- podAffinity — Pod 亲和性,将 Pod 调度到与其他 Pod 相近的位置
- podAntiAffinity — Pod 反亲和性,将 Pod 调度到与其他 Pod 远离的位置
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好
- weight: 80
preference:
matchExpressions:
- key: zone
operator: In
values:
- cn-east-1a
[!info] 深入阅读 调度器的完整机制(包括 Taint/Toleration、PriorityClass、拓扑分布约束等)将在 k8s-scheduler 中详细展开。
静态 Pod(Static Pod)
静态 Pod 是由 kubelet 直接管理的 Pod,不通过 API Server 创建。kubelet 会监视指定目录(默认为 /etc/kubernetes/manifests/)中的 YAML 文件,并自动创建和维护对应的 Pod。
特点
| 特性 | 静态 Pod | 普通 Pod |
|---|---|---|
| 管理者 | kubelet | API Server / Controller Manager |
| 存储位置 | 节点本地文件系统 | etcd |
| 是否出现在 API Server 中 | 是(kubelet 创建 mirror pod) | 是 |
| 是否受控制器管理 | 否 | 是(Deployment、StatefulSet 等) |
| 是否可被 kubectl 删除 | 否(需删除文件) | 是 |
用途
静态 Pod 最重要的用途是部署控制平面组件:
# kubeadm 部署的集群中,控制平面组件都是静态 Pod
/etc/kubernetes/manifests/
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
├── kube-scheduler.yaml
└── etcd.yaml
# /etc/kubernetes/manifests/kube-apiserver.yaml(简化示例)
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
labels:
component: kube-apiserver
spec:
containers:
- name: kube-apiserver
image: registry.k8s.io/kube-apiserver:v1.30.0
command:
- kube-apiserver
- --advertise-address=192.168.1.10
- --etcd-servers=https://127.0.0.1:2379
- --service-cluster-ip-range=10.96.0.0/12
ports:
- containerPort: 6443
hostNetwork: true
[!info] 为什么控制平面用静态 Pod? 这是一个「鸡生蛋」的问题:kube-apiserver 是 Kubernetes 集群的核心,如果 kube-apiserver 本身也依赖 API Server 来创建,那集群就无法启动。静态 Pod 由 kubelet 独立管理,不依赖 API Server,因此可以用来部署 API Server 本身。
常见陷阱与最佳实践
镜像拉取策略(imagePullPolicy)
| 策略 | 行为 | 适用场景 |
|---|---|---|
Always |
每次创建 Pod 都拉取镜像 | 使用 :latest 标签时(默认策略) |
IfNotPresent |
本地不存在时才拉取 | 使用固定版本标签(如 :v1.2.3) |
Never |
从不拉取,仅使用本地镜像 | 离线环境或测试场景 |
containers:
- name: app
image: myapp:v1.2.3 # 使用固定标签
imagePullPolicy: IfNotPresent # 推荐:避免不必要的拉取
[!warning] :latest 标签陷阱 当镜像标签为
:latest或不指定标签时,imagePullPolicy默认为Always。这意味着每次 Pod 重建都会重新拉取镜像,带来不确定性。生产环境必须使用固定版本标签(如:v1.2.3或 SHA256 digest)。
优雅终止(Graceful Shutdown)
当 Kubernetes 删除一个 Pod 时,会经历以下流程:
sequenceDiagram
participant User as 用户/API
participant API as API Server
participant Kubelet as kubelet
participant App as 应用进程
User->>API: 删除 Pod
API->>Kubelet: 标记 Pod 为 Terminating
Note over API: 从 Service Endpoints 移除
Kubelet->>App: 执行 preStop hook
App-->>App: 执行清理逻辑
App-->>Kubelet: preStop 完成
Kubelet->>App: 发送 SIGTERM 信号
App-->>App: 开始优雅关闭
alt 在 terminationGracePeriodSeconds 内退出
App-->>Kubelet: 进程正常退出
else 超时未退出
Kubelet->>App: 发送 SIGKILL 强制杀死
end
关键配置项:
apiVersion: v1
kind: Pod
metadata:
name: graceful-demo
spec:
terminationGracePeriodSeconds: 60 # 默认 30s,给足够时间清理
containers:
- name: app
image: myapp:v1
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 1. 通知应用停止接收新请求
curl -s http://localhost:8080/admin/drain
# 2. 等待正在进行的请求处理完成
sleep 15
# 3. 关闭数据库连接池
echo "Shutting down gracefully..."
[!tip] preStop 的妙用 preStop hook 在 SIGTERM 之前执行,可以用来做「通知式关闭」——比如先从注册中心注销、停止接收新连接、等待进行中的请求完成。配合
terminationGracePeriodSeconds调大值,可以实现零请求丢失的优雅关闭。
PID 1 信号问题
在 Linux 中,PID 1 进程有特殊行为:默认不响应 SIGTERM 信号(为了兼容 init 系统)。如果容器的入口进程是 shell 脚本,shell 不会将 SIGTERM 转发给子进程,导致容器无法优雅退出。
问题示例:
# 有问题的 Dockerfile
FROM node:20
COPY server.js .
CMD node server.js # node 进程成为 PID 1,但不一定正确处理信号
解决方案:
# 方案 1:使用 exec 形式(推荐)
CMD ["node", "server.js"] # node 直接成为 PID 1
# 方案 2:使用 tini 作为 init 进程
RUN apt-get update && apt-get install -y tini
ENTRYPOINT ["tini", "--"]
CMD ["node", "server.js"]
# 方案 3:在 Pod Spec 中设置(Kubernetes 1.28+)
# 通过 feature gate: SidecarContainers
# 方案 3:Pod 级别的 init 进程设置
apiVersion: v1
kind: Pod
metadata:
name: signal-demo
spec:
# Kubernetes 1.28+ 支持设置 Pod 的 PID namespace
# 确保 PID 1 被正确处理
containers:
- name: app
image: myapp:v1
其他注意事项
- 不要在同一 Pod 内运行多个不相关的服务 — 这违反了 Pod 的设计初衷,应该拆分为多个 Pod
- 使用 Liveness Probe 要谨慎 — 错误的 liveness 探针配置会导致容器反复重启(restart loop),建议先用 readiness 验证探针逻辑
- 设置资源 requests — 不设 requests 的 Pod 会变成 BestEffort QoS,在节点压力下最先被驱逐
- 避免使用 hostNetwork — 除非必要(如 CNI 插件、Ingress Controller),否则不要使用 hostNetwork,它会占用宿主机端口并绕过 Kubernetes 网络策略
延伸阅读
- k8s-02-architecture — Kubernetes 整体架构,理解 Pod 在集群中的位置
- k8s-04-deployment-and-replicaset — Deployment 如何管理 Pod 的副本与滚动更新
- k8s-scheduler — 调度器完整机制,包括亲和性、污点容忍、拓扑分布等