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 通过 maxSurge 和 maxUnavailable 控制更新速度:
- maxSurge:更新过程中允许超出期望副本数的最大百分比(默认 25%)
- maxUnavailable:更新过程中允许不可用副本的最大百分比(默认 25%)
示例:设置 maxSurge=30%,maxUnavailable=20%,可实现平滑更新。
3.6 节点故障处理
当 worker 节点宕机,Kubernetes 的 node-controller 会在以下时间线后驱逐 Pod:
- kubelet 每 10s 发送一次心跳
- controller-manager 每 5s 检查,40s 未收到心跳标记
NotReady - 持续 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
通过 completions 和 parallelism 控制并行执行:
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 服务发现方式
- 通过 ClusterIP 直接访问
- 环境变量 :在 Pod 中可通过
WEB_SERVICE_HOST、WEB_SERVICE_PORT等环境变量访问 - 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.html和index29.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
常见问题:
- Deployment 滚动更新卡住 → 检查
maxUnavailable和 Pod 健康探针 - Service 无法访问 → 确认 selector 与 Pod label 匹配,检查 targetPort 是否正确
- NodePort 端口冲突 → 手动指定或调整
--service-node-port-range - 金丝雀流量比例不准确 → 确保 Service 的 selector 覆盖所有版本 Pod
后续学习
至此,你已经掌握了 Kubernetes 核心控制器和 Service 的完整知识。下一步可以深入:
- Ingress(七层路由)
- PersistentVolume 与 StatefulSet
- Helm 包管理
- Service Mesh(Istio/Linkerd)
如果你觉得本文有帮助,欢迎点赞、收藏、留言,我会持续输出云原生干货!
本文所有 YAML 和命令均基于 K8s v1.30 + containerd 实测,建议在实验环境中跟随操作,效果更佳。