Kubernetes-04 工作负载详解
目录
Kubernetes 工作负载详解
系列第四篇。工作负载(Workload)是在 K8s 上运行的应用程序。K8s 提供多种内置工作负载资源,适用于不同的应用场景。
目录
- 工作负载概览
- Deployment
- ReplicaSet
- StatefulSet
- DaemonSet
- Job
- CronJob
- HorizontalPodAutoscaler(HPA)
- 工作负载选型指南
- 小结
1. 工作负载概览
工作负载类型
├── Deployment → 无状态应用(Web 服务、API 服务)★ 最常用
├── StatefulSet → 有状态应用(数据库、消息队列)
├── DaemonSet → 每个节点都要跑一个(日志收集、监控 Agent)
├── Job → 一次性任务(数据处理、批量导入)
└── CronJob → 定时任务(定时报表、定时清理)
2. Deployment
Deployment 是最常用的工作负载类型,管理无状态应用。
2.1 核心特性
- 声明式更新:修改 spec 后自动滚动升级
- 副本管理:保持指定数量的 Pod 副本
- 滚动升级:零停机更新
- 回滚:一键回滚到之前版本
- 扩缩容:手动或自动调整副本数
2.2 完整 Deployment 示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-api
namespace: production
labels:
app: go-api
annotations:
kubernetes.io/change-cause: "升级到 v1.2.0,修复内存泄漏"
spec:
replicas: 3 # 副本数
selector:
matchLabels:
app: go-api # 必须与 template.metadata.labels 匹配
# 升级策略
strategy:
type: RollingUpdate # RollingUpdate(默认)| Recreate
rollingUpdate:
maxSurge: 1 # 升级过程中最多多出几个 Pod(可以是数字或百分比)
maxUnavailable: 0 # 升级过程中最多有几个 Pod 不可用(0 表示始终保持 replicas 个可用)
# Pod 稳定性(等待这么久才开始对下一个 Pod 升级)
minReadySeconds: 10
# 保留历史版本数(用于回滚)
revisionHistoryLimit: 5
template: # Pod 模板
metadata:
labels:
app: go-api
version: "1.2.0"
spec:
# 优雅终止超时(SIGTERM 后等待这么久再强制 SIGKILL)
terminationGracePeriodSeconds: 30
containers:
- name: go-api
image: myregistry.io/go-api:v1.2.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
# Pod 拓扑分散约束(打散到不同 Zone/Node)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: go-api
2.3 Deployment 操作
# 创建
kubectl apply -f deployment.yaml
# 查看
kubectl get deployment go-api
kubectl get deploy go-api -o wide
# 查看滚动更新状态
kubectl rollout status deployment/go-api
# 查看历史版本
kubectl rollout history deployment/go-api
kubectl rollout history deployment/go-api --revision=2
# 手动触发更新(修改镜像)
kubectl set image deployment/go-api go-api=myregistry.io/go-api:v1.3.0
# 回滚到上一版本
kubectl rollout undo deployment/go-api
# 回滚到指定版本
kubectl rollout undo deployment/go-api --to-revision=2
# 暂停升级(用于多次修改后统一升级)
kubectl rollout pause deployment/go-api
# 恢复
kubectl rollout resume deployment/go-api
# 手动扩缩容
kubectl scale deployment go-api --replicas=5
# 删除
kubectl delete deployment go-api
2.4 滚动升级详解
初始状态:3 个 v1 Pod
[v1] [v1] [v1]
maxSurge=1, maxUnavailable=0 的升级过程:
Step 1: 创建 1 个新 v2 Pod(超出 replicas,但不超过 maxSurge)
[v1] [v1] [v1] [v2(pending)]
Step 2: v2 Pod 就绪后,终止 1 个 v1 Pod
[v1] [v1] [v2]
Step 3: 再创建 1 个 v2
[v1] [v1] [v2] [v2(pending)]
Step 4: v2 就绪,终止 v1
[v1] [v2] [v2]
Step 5: 同上...
[v2] [v2] [v2] ← 升级完成
2.5 Go 应用的优雅终止
K8s 删除 Pod 时的流程:
1. Pod 进入 Terminating 状态
2. 从 Service Endpoints 中移除(新请求不再路由到这个 Pod)
3. 发送 SIGTERM 信号给容器的 PID 1
4. 等待 terminationGracePeriodSeconds(默认 30s)
5. 如果还没退出,发送 SIGKILL 强制杀死
Go 应用处理优雅关闭:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
mux.HandleFunc("/healthz", healthz)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// 启动服务器
go func() {
log.Println("Server starting on :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP server error: %v", err)
}
}()
// 等待信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
sig := <-quit
log.Printf("Received signal: %v, shutting down...", sig)
// 优雅关闭(等待进行中的请求完成)
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("HTTP server shutdown error: %v", err)
}
log.Println("Server stopped gracefully")
}
3. ReplicaSet
ReplicaSet 确保指定数量的 Pod 副本始终运行。
你通常不需要直接使用 ReplicaSet,Deployment 会自动管理它。
# 查看 Deployment 创建的 ReplicaSet
kubectl get replicaset
# NAME DESIRED CURRENT READY AGE
# go-api-7d9b4c9b8d 3 3 3 10m ← 当前版本
# go-api-6c8b9d7f6a 0 0 0 20m ← 上一版本(用于回滚)
4. StatefulSet
StatefulSet 管理有状态应用,与 Deployment 的核心区别:
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机哈希(go-api-7d9b4c-xk2lp) | 稳定有序(mysql-0, mysql-1) |
| 网络标识 | Pod IP 变化 | 稳定 DNS(mysql-0.mysql) |
| 存储 | 多 Pod 共享或独立(需手动处理) | 每个 Pod 独立 PVC(自动创建) |
| 启动顺序 | 并行(无序) | 顺序(0→1→2) |
| 删除顺序 | 无序 | 逆序(2→1→0) |
| 升级 | 并行滚动 | 逆序顺序升级 |
4.1 StatefulSet 示例(Redis 集群)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: default
spec:
serviceName: "redis" # 必须指定,用于创建稳定 DNS
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.2-alpine
ports:
- containerPort: 6379
name: redis
command:
- redis-server
- /etc/redis/redis.conf
volumeMounts:
- name: data
mountPath: /data
- name: config
mountPath: /etc/redis
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "1Gi"
volumes:
- name: config
configMap:
name: redis-config
# 每个 Pod 自动创建独立的 PVC
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "fast-ssd"
resources:
requests:
storage: 10Gi
对应的 Headless Service(无 ClusterIP):
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
clusterIP: None # 关键:Headless Service
selector:
app: redis
ports:
- port: 6379
name: redis
稳定 DNS 记录:
redis-0.redis.default.svc.cluster.local → redis-0 的 IP
redis-1.redis.default.svc.cluster.local → redis-1 的 IP
redis-2.redis.default.svc.cluster.local → redis-2 的 IP
4.2 StatefulSet 操作
# 查看
kubectl get statefulset
kubectl get pods -l app=redis # redis-0, redis-1, redis-2
# 扩容(按顺序创建新 Pod)
kubectl scale statefulset redis --replicas=5
# 升级(逆序:redis-2 先升级)
kubectl set image statefulset/redis redis=redis:7.4-alpine
# 查看升级状态
kubectl rollout status statefulset/redis
4.3 什么时候用 StatefulSet
- 数据库:MySQL、PostgreSQL(主从复制需要稳定网络标识)
- 分布式存储:Elasticsearch、Cassandra
- 消息队列:Kafka(每个 Broker 有固定 ID)
- 缓存集群:Redis Cluster
- ZooKeeper、Consul 等需要固定节点身份的服务
注意: 真实生产环境中,数据库通常不建议跑在 K8s 中(或使用专门的 Operator,如 MySQL Operator、PG Operator),直接用 StatefulSet 运维复杂度较高。
5. DaemonSet
DaemonSet 确保每个(或指定)节点上都运行一个 Pod 副本。当节点加入集群时自动在其上创建 Pod,节点移除时对应 Pod 也被删除。
5.1 使用场景
- 日志收集:Fluentd、Fluent Bit(每个节点的日志都需要采集)
- 监控 Agent:Prometheus Node Exporter、Datadog Agent
- 网络插件:Calico、Flannel(CNI 插件)
- 存储插件:CSI Node Driver
- 安全扫描:Falco
5.2 DaemonSet 示例(Node Exporter)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # 每次更新最多有 1 个节点的 Pod 不可用
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true # 使用宿主机网络
hostPID: true # 共享宿主机 PID namespace
# 允许调度到 control-plane 节点
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
hostPort: 9100 # 使用宿主机端口
args:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
- name: sys
mountPath: /host/sys
readOnly: true
- name: root
mountPath: /host/root
readOnly: true
securityContext:
privileged: true # 需要读取宿主机信息
volumes:
- name: proc
hostPath:
path: /proc
- name: sys
hostPath:
path: /sys
- name: root
hostPath:
path: /
5.3 只在指定节点运行
spec:
template:
spec:
# 只在有 gpu=true 标签的节点上运行
nodeSelector:
gpu: "true"
# 或使用 nodeAffinity
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values: [gpu-node]
6. Job
Job 管理一次性任务,确保指定数量的 Pod 成功完成。
6.1 使用场景
- 数据处理/ETL
- 数据库迁移
- 批量导入
- 生成报告
- 发送批量邮件
6.2 Job 类型
6.2.1 单次 Job(默认)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
spec:
# 成功完成次数(默认 1)
completions: 1
# 并行运行数(默认 1)
parallelism: 1
# 失败重试次数
backoffLimit: 3
# Job 完成后多久自动删除(秒)
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: OnFailure # Job 必须是 OnFailure 或 Never
containers:
- name: migration
image: myapp:v1.2.0
command: ["/app/migrate", "--up", "--database", "$(DATABASE_URL)"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
cpu: "200m"
memory: "256Mi"
6.2.2 并行 Job(固定完成次数)
spec:
completions: 10 # 需要成功完成 10 次
parallelism: 3 # 同时运行 3 个 Pod
执行过程:
Round 1: [Pod1] [Pod2] [Pod3] → 3 个完成
Round 2: [Pod4] [Pod5] [Pod6] → 3 个完成
Round 3: [Pod7] [Pod8] [Pod9] → 3 个完成
Round 4: [Pod10] → 1 个完成
结果:10 次完成
6.2.3 Work Queue 模式(不固定完成次数)
spec:
parallelism: 5 # 并行 5 个
# 不设置 completions,靠 Pod 自己判断队列是否为空
6.3 Job 操作
kubectl get jobs
kubectl describe job db-migration
kubectl logs job/db-migration
# 查看 Job 创建的 Pod
kubectl get pods -l job-name=db-migration
7. CronJob
CronJob 按照 cron 表达式定时创建 Job。
7.1 Cron 表达式
┌─────── 分钟 (0-59)
│ ┌───── 小时 (0-23)
│ │ ┌─── 日 (1-31)
│ │ │ ┌─ 月 (1-12)
│ │ │ │ ┌ 星期 (0-7, 0和7都是周日)
│ │ │ │ │
* * * * *
示例:
0 2 * * * → 每天凌晨 2:00
0 */6 * * * → 每 6 小时
0 9 * * 1-5 → 工作日上午 9:00
*/5 * * * * → 每 5 分钟
0 0 1 * * → 每月 1 日 0:00
7.2 CronJob 示例
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
namespace: production
spec:
# 每天凌晨 2:00 生成报告
schedule: "0 2 * * *"
# 时区(K8s 1.27+)
timeZone: "Asia/Shanghai"
# 并发策略
concurrencyPolicy: Forbid # Allow(默认)| Forbid(跳过)| Replace(替换)
# 任务开始时间超过多少秒算 missed
startingDeadlineSeconds: 100
# 保留成功/失败的 Job 历史数量
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
ttlSecondsAfterFinished: 86400 # Job 完成后 24h 删除
template:
spec:
restartPolicy: OnFailure
containers:
- name: report
image: myapp:v1.2.0
command: ["/app/report", "--date", "yesterday", "--output", "s3://my-bucket/reports/"]
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-secret
key: access-key-id
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-secret
key: secret-access-key
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
7.3 CronJob 操作
# 查看 CronJob
kubectl get cronjob
# 手动触发一次(测试用)
kubectl create job --from=cronjob/daily-report manual-test-001
# 暂停 CronJob(不再创建新 Job)
kubectl patch cronjob daily-report -p '{"spec":{"suspend":true}}'
# 恢复
kubectl patch cronjob daily-report -p '{"spec":{"suspend":false}}'
# 查看历史 Job
kubectl get jobs -l app=daily-report
7.4 concurrencyPolicy 详解
Allow(默认):允许并发执行,上一次没跑完时开始新的
适用:任务之间互不影响,且幂等
Forbid:禁止并发,上一次没跑完时跳过这次
适用:任务不可并发(如独占数据库操作)
Replace:上一次没跑完时,终止它并开始新的
适用:只需要最新一次结果
8. HorizontalPodAutoscaler(HPA)
HPA 根据指标自动调整 Deployment/StatefulSet 的副本数。
8.1 基于 CPU 的 HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: go-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: go-api
minReplicas: 2 # 最少副本数
maxReplicas: 20 # 最多副本数
metrics:
# CPU 利用率超过 70% 时扩容
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# 内存使用超过 80% 时扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 扩容稳定窗口(60s 内连续超阈值才扩容)
policies:
- type: Pods
value: 4 # 每次最多增加 4 个 Pod
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # 缩容稳定窗口(5min 内持续低于阈值才缩容)
policies:
- type: Percent
value: 25 # 每次最多缩容 25%
periodSeconds: 60
8.2 基于自定义指标的 HPA
需要配合 Prometheus Adapter 或 KEDA:
metrics:
# 基于 Prometheus 自定义指标
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000" # 每个 Pod 平均处理 1000 RPS
# 基于外部指标(如消息队列长度)
- type: External
external:
metric:
name: kafka_consumer_lag
selector:
matchLabels:
topic: order-events
target:
type: AverageValue
averageValue: "100" # 消费延迟超过 100 才扩容
8.3 HPA 操作
# 查看 HPA 状态
kubectl get hpa
kubectl describe hpa go-api-hpa
# 输出:
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# go-api-hpa Deployment/go-api 45%/70% 2 20 3
前提: HPA 需要 Metrics Server 提供 CPU/内存指标
# 安装 Metrics Server(minikube)
minikube addons enable metrics-server
# 验证
kubectl top pods
kubectl top nodes
9. 工作负载选型指南
你的应用是什么类型?
│
├─ Web 服务/API/微服务(无状态)
│ └─ Deployment ✓
│
├─ 需要稳定网络标识和存储(数据库/MQ/缓存集群)
│ └─ StatefulSet ✓
│
├─ 需要在每个节点上运行(监控/日志/网络)
│ └─ DaemonSet ✓
│
├─ 一次性批处理任务
│ └─ Job ✓
│
├─ 定时执行的任务
│ └─ CronJob ✓
│
└─ 需要自动扩缩容?
└─ 在 Deployment/StatefulSet 基础上加 HPA ✓
10. 小结
各工作负载对比
| 特性 | Deployment | StatefulSet | DaemonSet | Job | CronJob |
|---|---|---|---|---|---|
| Pod 名称 | 随机 | 有序固定 | 随机 | 随机 | 随机 |
| 存储 | 共享 | 独立 PVC | 独立 | 临时 | 临时 |
| 有序操作 | 否 | 是 | 否 | 否 | 否 |
| 自动恢复 | 是 | 是 | 是 | 是(失败重试) | 是 |
| 适用场景 | Web/API | DB/MQ | 基础设施 | 批处理 | 定时任务 |
关键命令速查
# 查看所有工作负载
kubectl get deploy,sts,ds,job,cronjob -A
# 查看 Deployment 事件
kubectl describe deploy <name>
# 滚动升级状态
kubectl rollout status deploy/<name>
# 回滚
kubectl rollout undo deploy/<name>
# 扩缩容
kubectl scale deploy <name> --replicas=5
# 查看 HPA
kubectl get hpa
kubectl top pods
xingliuhua