第5章:Pod:Kubernetes的基本构建块(Pods: The Building Blocks of Kubernetes)
一、导读
1.1 本章学习目标
本章全面深入地讲解 Pod 的方方面面。从 Pod 的生命周期到标签和注解,从调度约束到 Init Container,你将掌握 Pod 的所有核心特性。这些知识帮助你将 Pod 调度到合适的节点、在启动前完成初始化、将 Pod 信息暴露给容器。
本章的核心学习目标包括:
- 理解 Pod 的完整生命周期:从 Pending 到终止的每个阶段。
- 掌握标签和注解的使用:Label 用于选择,Annotation 用于元数据。
- 配置调度约束:nodeSelector、nodeAffinity、taint/toleration。
- 使用 Init Container:在主容器启动前完成初始化任务。
- 使用 Downward API:将 Pod 信息(名称、IP、标签等)暴露给容器。
- 理解 Pod 的终止流程:优雅终止和强制终止的区别。
1.2 为什么本章如此重要
Pod 是 Kubernetes 中唯一可以直接创建的资源(其他资源都是控制器管理的)。深入理解 Pod 的特性,你才能:
- 将 Pod 调度到合适的节点(GPU 节点、SSD 节点、特定区域)
- 在应用启动前完成数据预热、等待依赖服务
- 让容器感知自己的运行环境(Pod 名称、Namespace、标签)
- 正确配置优雅终止,避免请求丢失
1.3 核心问题引导
Pod 从创建到终止经历了哪些阶段?每个阶段发生了什么?
nodeAffinity 和 nodeSelector 的区别是什么?什么时候用哪个?
Taint 和 Toleration 如何配合工作?有哪些内置的 Taint?
Init Container 和普通容器有什么区别?Init Container 之间如何传递数据?
当 Pod 被删除时,容器是如何被终止的?SIGTERM 和 SIGKILL 的区别?
二、核心概念详解
2.1 Pod 的生命周期
Pod 的完整生命周期:
Pending → Running → Succeeded/Failed
↓
Terminating(删除中)- Pending:API Server 已创建 Pod 对象,但容器尚未运行
- 调度决策
- 拉取镜像
- 创建容器
- Running:至少一个容器正在运行
- Succeeded:所有容器正常退出(退出码 0),不会重启
- Failed:至少一个容器异常退出
- Unknown:无法与 kubelet 通信,无法获取状态
2.1.1 Pod 的条件(Conditions)
kubectl get pod my-pod -o jsonpath='{.status.conditions}'Pod 的 Conditions:
- PodScheduled:Pod 已被调度到节点
- ContainersReady:所有容器就绪
- Initialized:所有 Init Container 完成
- Ready:Pod 可以接收流量(ContainersReady && Initialized)
2.2 标签和注解
2.2.1 标签(Labels)
标签是 K8s 资源的核心标识机制。Controller 通过 Label Selector 管理资源。
metadata:
labels:
app: web
env: production
version: "1.0"
team: backend
tier: frontend标签的命名规则:
- 前缀(可选):DNS 子域名格式,如
kubernetes.io/ - 名称:最长 63 字符,字母数字开头和结尾
- 值:最长 63 字符,可以为空
# 基于标签筛选
kubectl get pods -l 'app=web,env=production'
kubectl get pods -l 'app in (web,api)'
kubectl get pods -l 'version notin (v1.0)'
kubectl get pods -l '!canary' # 不包含 canary 标签
# 管理标签
kubectl label pod my-pod env=staging
kubectl label pod my-pod env=production --overwrite
kubectl label pod my-pod env- # 删除标签2.2.2 注解(Annotations)
注解用于存储非标识性的元数据,不影响调度和选择。
metadata:
annotations:
kubernetes.io/change-cause: "Update to v1.1"
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
description: "This is a web server pod"
owner: "team-backend"
build-url: "https://ci.example.com/builds/123"注解的常见用途:
- 构建信息(commit hash、构建 URL)
- 监控配置(Prometheus scrape 配置)
- 部署工具元数据(Helm、ArgoCD)
- 变更记录
2.3 调度约束
2.3.1 nodeSelector
最简单的节点选择方式:匹配节点标签。
spec:
nodeSelector:
disktype: ssd
zone: us-east-1a# 给节点添加标签
kubectl label nodes node-1 disktype=ssd2.3.2 nodeAffinity
更灵活的节点亲和性规则,支持多种操作符。
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
- nvme
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1a
- weight: 20
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1b操作符:In、NotIn、Exists、DoesNotExist、Gt、Lt
2.3.3 Taint 和 Toleration
Taint(污点)标记在节点上,Toleration(容忍)配置在 Pod 上。只有 Toleration 匹配 Taint 的 Pod 才能调度到该节点。
# 给节点添加 Taint
kubectl taint nodes node-1 gpu=true:NoSchedule
kubectl taint nodes node-1 dedicated=team-a:PreferNoSchedule
kubectl taint nodes node-1 maintenance=true:NoExecute
# 移除 Taint
kubectl taint nodes node-1 gpu=true:NoSchedule-spec:
tolerations:
- key: "gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
- key: "dedicated"
operator: "Equal"
value: "team-a"
effect: "PreferNoSchedule"
- key: "maintenance"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600Taint 效果:
- NoSchedule:新 Pod 不会被调度
- PreferNoSchedule:尽量避免调度,但不保证
- NoExecute:已运行的 Pod 会被驱逐(除非有 Toleration)
内置 Taint:
node.kubernetes.io/not-ready:节点未就绪node.kubernetes.io/unreachable:节点不可达node.kubernetes.io/out-of-disk:磁盘不足node.kubernetes.io/memory-pressure:内存压力node.kubernetes.io/disk-pressure:磁盘压力
2.4 Init Container
Init Container 在主容器启动之前运行,按顺序执行,每个必须成功完成。
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
- name: data-preload
image: data-loader:1.0
command: ['python', 'preload.py']
volumeMounts:
- name: data
mountPath: /data
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}Init Container 的常见用途:
- 等待依赖服务就绪
- 数据预热/迁移
- 生成配置文件
- 下载必要的数据
2.5 Downward API
Downward API 将 Pod 的元数据暴露给容器。
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: APP_LABEL
valueFrom:
fieldRef:
fieldPath: metadata.labels['app']
volumeMounts:
- name: pod-info
mountPath: /etc/pod-info
readOnly: true
volumes:
- name: pod-info
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations2.6 Pod 的终止
当 Pod 被删除时,K8s 执行优雅终止流程:
API Server 将 Pod 标记为 Terminating
kubelet 发送 SIGTERM 给容器
容器开始优雅关闭(处理完当前请求、关闭连接)
如果超过 terminationGracePeriodSeconds(默认30秒),发送 SIGKILL 强制终止
删除 Pod
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]preStop 钩子在 SIGTERM 之前执行,常用于:
- 从 Service 的 Endpoints 中注销(等待负载均衡器更新)
- 优雅关闭连接
- 清理资源
三、实战案例
3.1 使用 Init Container 完成初始化
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
- name: wait-for-dependencies
image: busybox:1.36
command:
- sh
- -c
- |
echo "Waiting for database..."
until nc -z db-service 5432; do
echo "Database not ready, waiting..."
sleep 2
done
echo "Database is ready!"
echo "Waiting for cache..."
until nc -z cache-service 6379; do
echo "Cache not ready, waiting..."
sleep 2
done
echo "Cache is ready!"
- name: setup-config
image: busybox:1.36
command:
- sh
- -c
- |
echo "Setting up configuration..."
cat > /config/app.conf <<EOF
database.host=db-service
database.port=5432
cache.host=cache-service
cache.port=6379
EOF
volumeMounts:
- name: config
mountPath: /config
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config
mountPath: /etc/app-config
volumes:
- name: config
emptyDir: {}3.2 使用 nodeAffinity 调度到 GPU 节点
apiVersion: v1
kind: Pod
metadata:
name: gpu-training
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: accelerator
operator: In
values:
- nvidia-tesla-v100
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: zone
operator: In
values:
- us-east-1a
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: training
image: tensorflow/tensorflow:latest-gpu
resources:
limits:
nvidia.com/gpu: 1四、本章小结
本章全面讲解了 Pod 的核心特性:
生命周期:Pending → Running → Succeeded/Failed,每个阶段有明确的条件检查。
标签和注解:Label 用于选择和标识,Annotation 用于存储元数据。
调度约束:nodeSelector 简单匹配、nodeAffinity 灵活规则、Taint/Toleration 控制调度。
Init Container:在主容器启动前按顺序执行初始化任务。
Downward API:将 Pod 信息暴露为环境变量或文件。
优雅终止:SIGTERM → preStop → 等待 → SIGKILL。
五、思考与练习
思考题
如果一个 Pod 同时配置了 nodeSelector 和 nodeAffinity,两者会如何交互?
Init Container 失败后会发生什么?Pod 会进入什么状态?
为什么 Downward API 不能暴露容器的资源限制?
优雅终止时,为什么需要 preStop 中的 sleep?直接发送 SIGTERM 有什么问题?
NoExecute Taint 和 Pod 的 tolerationSeconds 如何配合?
练习题
给集群节点添加自定义标签,使用 nodeAffinity 将 Pod 调度到特定节点。
配置 Taint 使某个节点只运行特定 Pod(如 GPU 工作负载)。
使用 Init Container 在应用启动前等待数据库就绪。
使用 Downward API 将 Pod 名称和标签暴露为环境变量。
配置优雅终止,观察 Pod 删除时的终止流程。