Kubernetes 控制器与 Service 完全指南:从 ReplicaSet 到负载均衡

Kubernetes 控制器与 Service 完全指南:从 ReplicaSet 到负载均衡

深入理解 K8s 工作负载管理(ReplicaSet/Deployment/DaemonSet/Job/CronJob)与 Service 服务发现,附金丝雀发布实战


写在前面

在 Kubernetes 中,控制器(Controller)Service 是两个最核心的概念。控制器负责维持应用的期望状态 (如副本数、版本、任务执行),而 Service 则解决了Pod 动态变化带来的服务发现和负载均衡问题

本文基于 Kubernetes 1.30,系统讲解:

  • ReplicaSet、Deployment、DaemonSet、Job、CronJob 的原理与实战
  • Service 的四种类型(ClusterIP/NodePort/LoadBalancer/ExternalName)及 Headless Service
  • 服务发现(环境变量、DNS)
  • 会话保持与金丝雀发布

无论你是刚入门还是准备 CKA 考试,这篇文章都是不可多得的学习资料。


一、控制器概述

1.1 什么是控制器?

控制器是 Kubernetes 的"大脑",它持续监控集群状态,确保实际状态期望状态一致。当 Pod 意外退出、节点故障或副本数变化时,控制器会自动执行修复或扩缩容操作。

控制器按用途可分为两类:

  • 服务类控制器:长期运行,如 ReplicaSet、Deployment、DaemonSet
  • 任务类控制器:一次性或周期性任务,如 Job、CronJob

二、ReplicaSet(RS)

2.1 基本概念

ReplicaSet 保证指定数量的 Pod 副本始终运行。它通过标签选择器识别管理的 Pod,如果 Pod 数量不足则创建,过多则删除。

注意:ReplicaSet 通常不直接使用,而是由 Deployment 自动管理。我们一般只操作 Deployment,不会手动创建 RS。

2.2 实战:创建一个 ReplicaSet

yaml 复制代码
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
bash 复制代码
kubectl apply -f rs.yaml
kubectl get rs
kubectl get pods

2.3 健壮性测试

删除一个 Pod,RS 会立即重建,保持总数恒定:

bash 复制代码
kubectl delete pod nginx-95pth
kubectl get pods   # 看到新 Pod 快速启动

2.4 删除 ReplicaSet

默认级联删除所有 Pod:

bash 复制代码
kubectl delete rs nginx

若只删除 RS 而保留 Pod,使用 --cascade=orphan

bash 复制代码
kubectl delete rs nginx --cascade=orphan

三、Deployment(生产首选)

3.1 为什么用 Deployment?

Deployment 在 ReplicaSet 之上提供了声明式更新能力,支持:

  • 滚动更新(Rolling Update)
  • 回滚(Rollback)
  • 版本历史记录
  • 暂停/恢复发布

可以说,Deployment 是你在生产环境管理无状态应用的唯一选择。

3.2 创建 Deployment

命令行方式
bash 复制代码
kubectl create deployment web --image=nginx:1.27 --replicas=2
YAML 方式(推荐)
bash 复制代码
kubectl create deployment web --image=nginx:1.27 --replicas=2 --dry-run=client -o yaml > deploy-web.yaml

编辑 YAML,完善后应用:

bash 复制代码
kubectl apply -f deploy-web.yaml

3.3 扩缩容

bash 复制代码
kubectl scale deployment web --replicas=4
# 或编辑 YAML
kubectl edit deployment web

3.4 滚动更新与回滚

更新镜像

bash 复制代码
kubectl set image deployment/web nginx=nginx:1.28 --record

查看历史

bash 复制代码
kubectl rollout history deployment web

回滚到指定版本

bash 复制代码
kubectl rollout undo deployment web --to-revision=1

3.5 滚动更新参数

Deployment 通过 maxSurgemaxUnavailable 控制更新速度:

  • maxSurge:更新过程中允许超出期望副本数的最大百分比(默认 25%)
  • maxUnavailable:更新过程中允许不可用副本的最大百分比(默认 25%)

示例:设置 maxSurge=30%,maxUnavailable=20%,可实现平滑更新。

3.6 节点故障处理

当 worker 节点宕机,Kubernetes 的 node-controller 会在以下时间线后驱逐 Pod:

  1. kubelet 每 10s 发送一次心跳
  2. controller-manager 每 5s 检查,40s 未收到心跳标记 NotReady
  3. 持续 NotReady 5 分钟后,开始将 Pod 调度到其他节点(pod-eviction-timeout=300s

四、DaemonSet(每个节点一个 Pod)

4.1 应用场景

DaemonSet 确保每个节点(或部分节点)运行一个 Pod 副本。典型用途:

  • 日志收集(Fluentd)
  • 监控代理(Node Exporter)
  • 网络插件(Calico、kube-proxy)

4.2 实战:部署一个 DaemonSet

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: busybox
spec:
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["sleep", "36000"]
bash 复制代码
kubectl apply -f daemonset.yaml
kubectl get ds
kubectl get pods -o wide   # 每个节点一个

4.3 调度控制

Master 节点默认有 NoSchedule 污点,DaemonSet Pod 不会调度到 master。若想强制调度,可移除污点(不推荐):

bash 复制代码
kubectl taint node master node-role.kubernetes.io/control-plane-

恢复:

bash 复制代码
kubectl taint node master node-role.kubernetes.io/control-plane:NoSchedule

4.4 生产级示例:Node Exporter

yaml 复制代码
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: node-exporter
  template:
    metadata:
      labels:
        app: node-exporter
    spec:
      hostNetwork: true
      containers:
      - name: node-exporter
        image: prom/node-exporter:latest
        ports:
        - containerPort: 9100
        volumeMounts:
        - name: proc
          mountPath: /host/proc
          readOnly: true
        - name: sys
          mountPath: /host/sys
          readOnly: true
      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: sys
        hostPath:
          path: /sys

五、Job(一次性任务)

5.1 基本用法

Job 用于运行一次性任务(如数据库迁移、备份),Pod 正常退出(返回 0)即视为完成。

yaml 复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(200)"]
      restartPolicy: Never
bash 复制代码
kubectl apply -f job.yaml
kubectl logs pi-xxxxx   # 查看输出

5.2 restartPolicy 与 backoffLimit

  • restartPolicy:只能是 Never(失败重建 Pod)或 OnFailure(失败重启容器)
  • backoffLimit:失败重试次数(默认 6)

5.3 并行 Job

通过 completionsparallelism 控制并行执行:

yaml 复制代码
spec:
  completions: 6          # 总共需成功 6 次
  parallelism: 2          # 同时运行 2 个 Pod

5.4 超时与自动清理

  • activeDeadlineSeconds:任务最大运行时间,超时则标记失败
  • ttlSecondsAfterFinished:完成后自动清理 Job 及其 Pod(需启用特性门控)

六、CronJob(定时任务)

6.1 创建 CronJob

bash 复制代码
kubectl create cronjob mycronjob --image=busybox --schedule="*/2 * * * *" -- echo hello

等几分钟,会看到 Job 按周期创建。

6.2 关键参数

参数 说明
schedule Cron 表达式,必填
startingDeadlineSeconds 错过调度后允许延迟启动的秒数
concurrencyPolicy Allow(并发)、Forbid(跳过)、Replace(替换旧任务)
successfulJobsHistoryLimit 保留成功 Job 数量(默认 3)
failedJobsHistoryLimit 保留失败 Job 数量(默认 1)

6.3 实战:定期清理目录

yaml 复制代码
apiVersion: batch/v1
kind: CronJob
metadata:
  name: clean
spec:
  schedule: "0 2 * * *"   # 每天凌晨 2 点
  jobTemplate:
    spec:
      template:
        spec:
          nodeName: worker31   # 指定运行节点
          containers:
          - name: clean
            image: busybox
            command: ["sh", "-c", "rm -rf /var/data/*"]
            volumeMounts:
            - mountPath: /var/data
              name: data
          volumes:
          - name: data
            hostPath:
              path: /var/data
          restartPolicy: OnFailure

七、Service 核心概念

7.1 为什么需要 Service?

Pod 是动态的(创建、销毁、重启),IP 不固定。Service 为 Pod 提供稳定的 IP 和 DNS 名称 ,并实现负载均衡

7.2 创建 Service

命令行方式
bash 复制代码
kubectl expose deployment web --port=8080 --target-port=80
YAML 方式
yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  ports:
  - port: 8080
    targetPort: 80
  selector:
    app: web
  type: ClusterIP

7.3 服务发现方式

  1. 通过 ClusterIP 直接访问
  2. 环境变量 :在 Pod 中可通过 WEB_SERVICE_HOSTWEB_SERVICE_PORT 等环境变量访问
  3. DNS :集群内 Pod 可通过 <service>.<namespace>.svc.cluster.local 访问(CoreDNS 自动解析)

示例:在 Pod 内部访问 Service

bash 复制代码
kubectl run test --rm -it --image=busybox -- sh
/ # wget -qO- http://web.services:8080

八、Service 类型详解

8.1 ClusterIP(默认)

仅在集群内部可访问,适用于内部服务间通信。

8.2 NodePort

在每个节点上开放一个固定端口(30000-32767),外部可通过 <节点IP>:<端口> 访问。

bash 复制代码
kubectl expose deployment web --type=NodePort --port=8080 --target-port=80

查看分配的端口:

bash 复制代码
kubectl get svc web   # 显示 PORT(S): 8080:31917/TCP

外部访问:curl http://10.1.8.30:31917

8.3 LoadBalancer

需要配套的负载均衡器(如云厂商 LB 或 MetalLB)。本文使用 MetalLB 在裸机环境模拟。

部署 MetalLB(仅需一次):

bash 复制代码
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml

配置 IP 地址池:

yaml 复制代码
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.1.8.40-10.1.8.80

启用 L2 模式:

yaml 复制代码
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system

创建 LoadBalancer Service:

bash 复制代码
kubectl expose deployment web --type=LoadBalancer --port=80 --target-port=80

MetalLB 会从地址池分配一个外部 IP(如 10.1.8.40),外部即可通过该 IP 访问。

8.4 ExternalName

将 Service 映射到外部域名(CNAME):

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-external
spec:
  type: ExternalName
  externalName: database.example.com

内部访问 my-external 时,DNS 返回 database.example.com

8.5 Headless Service

clusterIP: None,不分配 IP,直接返回 Pod IP 列表,适用于自定义负载均衡(如 StatefulSet)。


九、会话保持(Session Affinity)

对于有状态服务(如购物车、登录状态),需要确保同一客户端始终访问同一个 Pod。

bash 复制代码
kubectl patch svc web -p '{"spec":{"sessionAffinity":"ClientIP"}}'

默认超时 10800 秒(3 小时),可调整:

bash 复制代码
kubectl patch svc web -p '{"spec":{"sessionAffinityConfig":{"clientIP":{"timeoutSeconds":3600}}}}'

十、金丝雀发布实战

10.1 场景

现有稳定版 v1.28(10 个副本),想发布 v1.29,先引入少量流量测试。

10.2 部署稳定版

yaml 复制代码
# web-28.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-28
spec:
  replicas: 10
  selector:
    matchLabels:
      app: web
      track: stable
  template:
    metadata:
      labels:
        app: web
        track: stable
    spec:
      containers:
      - name: nginx
        image: nginx:1.28
        volumeMounts:
        - name: webcontent
          mountPath: /usr/share/nginx/html
      volumes:
      - name: webcontent
        configMap:
          name: web
          items:
          - key: index28.html
            path: index.html

假设 ConfigMap web 已创建,包含 index28.htmlindex29.html

创建 Service(选择所有 app=web 的 Pod):

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80

10.3 部署金丝雀版本

yaml 复制代码
# web-29.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-29
spec:
  replicas: 1              # 只放 1 个新版本
  selector:
    matchLabels:
      app: web
      track: canary
  template:
    metadata:
      labels:
        app: web
        track: canary
    spec:
      containers:
      - name: nginx
        image: nginx:1.29
        volumeMounts:
        - name: webcontent
          mountPath: /usr/share/nginx/html
      volumes:
      - name: webcontent
        configMap:
          name: web
          items:
          - key: index29.html
            path: index.html

10.4 观察流量比例

bash 复制代码
for i in {1..50}; do curl -s http://<service-ip>; done | sort | uniq -c

你会看到大约 1/11 的请求返回 1.29 的内容,其余为 1.28。

10.5 逐步扩大金丝雀

bash 复制代码
kubectl scale deployment web-28 --replicas=3
kubectl scale deployment web-29 --replicas=2

继续调整直到全部切换为新版本,旧版本缩容为 0。


十一、总结与避坑指南

控制器 用途 特点
ReplicaSet 保证副本数 一般不直接使用
Deployment 无状态应用管理 滚动更新、回滚、版本控制
DaemonSet 每个节点一个 Pod 日志、监控、网络组件
Job 一次性任务 支持并行和重试
CronJob 定时任务 支持并发策略和保留历史

Service 关键点

  • ClusterIP:集群内访问
  • NodePort:节点端口暴露
  • LoadBalancer:外部 LB 入口
  • Headless:无 ClusterIP,直接解析 Pod IP

常见问题

  1. Deployment 滚动更新卡住 → 检查 maxUnavailable 和 Pod 健康探针
  2. Service 无法访问 → 确认 selector 与 Pod label 匹配,检查 targetPort 是否正确
  3. NodePort 端口冲突 → 手动指定或调整 --service-node-port-range
  4. 金丝雀流量比例不准确 → 确保 Service 的 selector 覆盖所有版本 Pod

后续学习

至此,你已经掌握了 Kubernetes 核心控制器和 Service 的完整知识。下一步可以深入:

  • Ingress(七层路由)
  • PersistentVolume 与 StatefulSet
  • Helm 包管理
  • Service Mesh(Istio/Linkerd)

如果你觉得本文有帮助,欢迎点赞、收藏、留言,我会持续输出云原生干货!


本文所有 YAML 和命令均基于 K8s v1.30 + containerd 实测,建议在实验环境中跟随操作,效果更佳。