目录

Kubernetes-04 工作负载详解

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