Kubernetes Service 与 kube-proxy 详解:从服务暴露到流量转发
学习参考:
前言: 这两个技术是一对"前台 + 幕后"的组合。
- Service 是 K8s 的"前台":Pod 是临时的,会销毁重建、IP 会变;==Service 提供固定 IP,实现后端 Pod 的负载均衡与服务发现,==屏蔽后端 Pod 的变化。
- kube-proxy 是"幕后":Pod 网络核心组件,负责实现 Service → Endpoint(后端 Pod) 的流量转发、负载均衡。Service 的虚拟 IP 之所以能通,全靠 kube-proxy 在每个节点上写下的 iptables / IPVS 规则。
本文分两部分:第一部分(1~7 章)讲 Service 的原理、类型与实践;第二部分(8~12 章)深入 kube-proxy 的三种工作模式与流量转发机制。
环境准备(两部分实验共用):
bash
root@master30:~# kubectl create ns services
root@master30:~# kubectl config set-context --current --namespace services
第一部分:Kubernetes Service
1、为什么需要 Service?
两种情况:
-
单个 Pod :拥有独立 Pod‑IP,集群外部节点无法直接使用 Pod‑IP 访问 Pod,Pod 销毁重建 IP 就会变化。
-
多副本: 用 hostPort 会绑定宿主机端口 ,一台节点同一个端口只能启动一个 Pod,多副本会出现部分 Pod Pending 调度失败==。
报错关键词:
didn't have free ports for the requested pod ports。
问题总结:
- 集群外部客户端需要知道 pod 运行在哪个主机才能访问 pod。
- pod 通过控制器控制时候,无法在同一个主机上创建多个类似 pod。
2、Service 介绍
Deployment 会动态创建、销毁 Pod,Pod 是临时资源,Pod IP 会不断变化。
问题 :前端 Pod 如何持续发现后端一组动态变化 Pod 的地址?
答案:Service
- Service 把一组 Pod 的网络应用对外暴露成网络服务 ;Service 拥有固定不变的 IP 与端口 ,提供负载均衡。
- ==客户端只访问 Service IP ,K8s 维护 Service 与后端 Pod 映射;==后端 Pod 发生伸缩 / 重建,对客户端无感知。
- 不需要修改业务代码,即可实现 Pod 集合网络访问。
✅ ==Service 解决:固定访问入口 + 负载均衡 + 自动发现后端 Pod,==Service 自身 IP 恒定,后端 Pod 变化对客户端透明。
小知识点:Service IP 不能 ping 通!它是 iptables/ipvs 虚拟 IP,只处理对应端口的 TCP/UDP 流量。(原理见第二部分 kube-proxy。)
3、Service 基本管理
3.1 环境准备
创建 deployment ------ 后端 pod
bash
# 创建 Deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/httpd:2.4.58 --replicas=3
# 查看pod
root@master30:~# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
web-5646dd6f6c-5hs6f 1/1 Running 0 8m43s app=web,pod-template-hash=5646dd6f6c
web-5646dd6f6c-6fjqs 1/1 Running 0 8m43s app=web,pod-template-hash=5646dd6f6c
web-5646dd6f6c-tvw78 1/1 Running 0 8m43s app=web,pod-template-hash=5646dd6f6c
deployment 会给 pod 打上标签 app=web,Service 依靠标签选择器 selector 匹配后端 Pod。
3.2 创建 Service
方式 1:命令快速创建
bash
# --tcp=service端口:pod端口
root@master30:~# kubectl create service clusterip web --tcp=8080:80
选项 --tcp=8080:80 代表访问集群 ip 的 8080/TCP,将转发给 pod 的 80 端口。
svc 默认标签是:app=svc-name
方式 2:yaml 文件创建
bash
root@master30:~# kubectl delete svc web
# 获取Service资源yaml文件模版
root@master30:~# kubectl create service clusterip web --tcp=8080:80 -o yaml --dry-run=client > svc-web.yml
root@master30:~# cat svc-web.yml
yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
ports:
- name: 8080-80
port: 8080
protocol: TCP
targetPort: 80
selector:
app: web # 根据标签匹配pod
type: ClusterIP
status:
loadBalancer: {}
3.3 查看 Service 信息
yaml
root@master30:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.103.19.150 <none> 8080/TCP 6m58s
# 查看Service详细信息
root@master30:~# kubectl describe service web
Name: web
Namespace: laoma
Labels: app=web
Annotations: <none>
Selector: app=web # 根据标签匹配pod
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.103.19.150
IPs: 10.103.19.150
Port: 8080-80 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.224.193.67:80,10.224.193.68:80,10.224.41.131:80
Session Affinity: None
Events: <none>
root@master30:~# kubectl describe service web |grep -e Endpoints -e IP:
IP: 10.103.19.150
Endpoints: 10.224.193.67:80,10.224.193.68:80,10.224.41.131:80
# 访问测试,访问service-ip对应的8080端口,成功。
root@master30:~# curl 10.103.19.150:8080
<html><body><h1>It works!</h1></body></html>
Endpoints:Service 匹配到的真实后端 Pod 地址集合,selector 标签变了,endpoints 自动更新。
3.4 验证 Service
- 更改每个 pod 主页,验证负载均衡功能。
bash
# 获取pod名称
root@master30:~# kubectl get pods -o name | awk -F/ '{print $2}'
web-5646dd6f6c-5hs6f
web-5646dd6f6c-6fjqs
web-5646dd6f6c-tvw78
# 更改每个pod主页
root@master30:~# \
for pod in $(kubectl get pods -o name | awk -F/ '{print $2}')
do
kubectl exec -it $pod -- bash -c "echo $pod > htdocs/index.html"
done
# 验证效果:多次访问,观察流量分发到不同Pod
root@master30:~# for i in {1..60};do curl -s 10.103.19.150:8080;done|sort |uniq -c
19 web-5646dd6f6c-5hs6f
19 web-5646dd6f6c-6fjqs
22 web-5646dd6f6c-tvw78
- 新增相同标签 Pod,自动加入 Service 后端 Endpoints
bash
root@master30:~# kubectl run web --image=hub.laoma.cloud/library/httpd --labels=app=web
root@master30:~# for i in {1..60};do curl -s 10.103.19.150:8080;done|sort |uniq -c
14 <html><body><h1>It works!</h1></body></html>
16 web-5646dd6f6c-5hs6f
17 web-5646dd6f6c-6fjqs
13 web-5646dd6f6c-tvw78
- Deployment 滚动重启重建 Pod ,Service 自动感知更新 Endpoints。
bash
root@master30:~# kubectl rollout restart deployment web
root@master30:~# for i in {1..60};do curl -s 10.103.19.150:8080;done|sort |uniq -c
-
Deployment selector 和 Service selector 可以使用完全不同标签
Deployment 用一组标签管理 Pod;Service 用另一组标签选择 Pod,两者互相独立。
bash
# 清理环境,重建 Deployment和Service
root@master30:~# kubectl delete deployments.apps web --force
root@master30:~# kubectl delete service web
# 修改deployment,pod打上 app1:web1、app2:web2
root@master30:~# vim deploy-web.yml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
replicas: 2
selector:
matchLabels:
app1: web1
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app1: web1
app2: web2
spec:
containers:
- image: hub.laoma.cloud/library/httpd
name: httpd
imagePullPolicy: IfNotPresent
resources: {}
status: {}
root@master30:~# kubectl apply -f deploy-web.yml
通过 expose 方式创建 service
选项说明:
- --port =8080,定义**service 监听的端口**
- --target-port =80,定义**后端 pod 服务的端口**
- --selector =app2=web2,定义**service 选择器标签**
bash
root@master30:~# kubectl expose deployment web --port=8080 --target-port=80 --selector=app2=web2
root@master30:~# kubectl describe svc web |grep -e IP: -e Endpoints
IP: 10.101.131.56
Endpoints: 10.224.193.73:80,10.224.41.136:80
root@master30:~# curl 10.101.131.56:8080
<html><body><h1>It works!</h1></body></html>
- 无法 ping 通 service ip,但可以 ping 通 pod 。service 只允许 http 方式访问 80,其他没有做 iptables 映射。
Service Cluster‑IP 不支持 ping,只转发对应端口 TCP 流量,可以 curl 访问端口。
4、Service 发现(集群内部 Pod 如何找到 Service)
所谓发现 Service,是指**集群内应用访问 Service。**
三种方式:
- 通过 IP 访问 Service。
- 通过环境变量访问 Service。
- 通过 dns 解析的名称访问 Service。
4.1 通过 IP 访问 Service
直接填写 Service 的 ClusterIP;
缺点:IP 硬编码,IP 变化就失效。
实验准备:mysql + wordpress
准备 mysql 资源
bash
root@master30:~# kubectl run mysql --image=hub.laoma.cloud/library/mysql \
--image-pull-policy=IfNotPresent \
--env=MYSQL_ROOT_PASSWORD=redhat \
--env=MYSQL_USER=tom \
--env=MYSQL_PASSWORD=redhat \
--env=MYSQL_DATABASE=blog \
--dry-run=client -o yaml > pod-mysql.yaml
root@master30:~# kubectl apply -f pod-mysql.yaml
# 创建mysql pod,暴露clusterip service
root@master30:~# kubectl expose pod mysql --port=3306 --target-port=3306
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql ClusterIP 10.111.69.45 <none> 3306/TCP 25m
# 安装mysql客户端
root@master30:~# apt install -y mysql-client
# mysql客户端测试连接service‑ip
root@master30:~# mysql -u tom -predhat -h 10.111.69.45 --ssl-mode=DISABLED -e 'show databases;'
mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database |
+--------------------+
| information_schema |
| blog |
+--------------------+
准备 WordPress 资源
bash
# 创建wordpress,数据库地址写mysql的Service IP
root@master30:~# kubectl run wordpress \
--image=hub.laoma.cloud/library/wordpress \
--image-pull-policy=IfNotPresent \
--env=WORDPRESS_DB_USER=tom \
--env=WORDPRESS_DB_PASSWORD=redhat \
--env=WORDPRESS_DB_NAME=blog \
--env=WORDPRESS_DB_HOST=10.111.69.45
# wordpress对外暴露NodePort,外部浏览器访问
root@master30:~# kubectl expose pod wordpress --port=80 --target-port=80 --type NodePort
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql ClusterIP 10.111.69.45 <none> 3306/TCP 41m
wordpress NodePort 10.106.106.246 <none> 80:31102/TCP 3s
# 访问测试
root@client:~# firefox 10.1.8.30:31102
4.2 通过环境变量访问 Service
在 Pod 启动时,注入同命名空间下已经存在的 Service的环境变量。
说明:可以==通过环境变量 MYSQL_SERVICE_HOST访问服务 mysql。==
⚠️ 限制:
- ==Service 必须先创建,==再创建 Pod,否则不会生成环境变量;
- service 属于 namespace ,pod只能访问同一个 namespace中 service,跨 namespace 看不到。
获取环境变量信息
bash
root@master30:~# kubectl run test --rm -it --image=hub.laoma.cloud/library/busybox --image-pull-policy=IfNotPresent sh
If you don't see a command prompt, try pressing enter.
/ # env|grep MYSQL
MYSQL_PORT_3306_TCP_ADDR=10.111.69.45
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_SERVICE_HOST=10.111.69.45
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_PORT=tcp://10.111.69.45:3306
MYSQL_SERVICE_PORT=3306
MYSQL_PORT_3306_TCP=tcp://10.111.69.45:3306
/ # exit
准备 WordPress 资源
数据库地址使用环境变量 $(MYSQL_SERVICE_HOST)
bash
# 删除 pod-WordPress 资源,重新创建
root@master30:~# kubectl delete pod wordpress --force
root@master30:~# kubectl run wordpress \
--image=hub.laoma.cloud/library/wordpress \
--image-pull-policy=IfNotPresent \
--env=WORDPRESS_DB_USER=tom \
--env=WORDPRESS_DB_PASSWORD=redhat \
--env=WORDPRESS_DB_NAME=blog \
--env=WORDPRESS_DB_HOST='$(MYSQL_SERVICE_HOST)'
# 查看Service变量
root@master30:~# kubectl exec -it wordpress -- sh -c 'env|grep MYSQL'
MYSQL_PORT_3306_TCP_ADDR=10.111.69.45
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_SERVICE_HOST=10.111.69.45
MYSQL_PORT=tcp://10.111.69.45:3306
MYSQL_SERVICE_PORT=3306
MYSQL_PORT_3306_TCP=tcp://10.111.69.45:3306
# 访问测试
root@client:~# firefox 10.1.8.30:31102
4.3 通过 DNS 名称访问 Service
kubeadm 部署时会**默认安装 coredns 组件**。
集群由 coredns 组件**提供域名解析**,coredns 部署在 kube‑system 命名空间。
coredns 是一个 DNS 服务器。每当有新的 Service 被创建,coredns 会添加该 Service 的 DNS 记录。
域名规则:
- 同命名空间:直接写服务名 ,如
mysql; - 完整访问域名:SERVICE_NAME.NAMESPACE_NAME.svc.cluster.local

bash
root@master30:~# kubectl get deployments.apps --namespace=kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
calico-kube-controllers 1/1 1 1 2d23h
coredns 2/2 2 2 3d
busybox 测试 DNS 解析
bash
# kubelet创建Pod时自动把DNS指向 coredns
root@master30:~# kubectl run busybox --rm -it --image=hub.laoma.cloud/library/busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # cat /etc/resolv.conf
nameserver 10.96.0.10
search service.svc.cluster.local svc.cluster.local cluster.local laoma.cloud
options ndots:5
# 这个Pod与web service同属于laoma namespace,可以省略laoma直接用web访问Service.
/ # wget wordpress.service:80
Connecting to wordpress.service:80 (10.106.106.246:80)
Connecting to wordpress.service:80 (10.106.106.246:80)
saving to 'index.html'
index.html 100% |***************************************| 11607 0:00:00 ETA
'index.html' saved
bash
/ # rm index.html
/ # wget wordpress:80
Connecting to wordpress:80 (10.106.106.246:80)
Connecting to wordpress:80 (10.106.106.246:80)
saving to 'index.html'
index.html 100% |***************************************| 11571 0:00:00 ETA
'index.html' saved
10.96.0.10 是哪个 DNS 服务器呢?
bash
root@master30:~# kubectl get service --namespace kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 3d2h
接上面的 WordPress 示例
bash
# 删除 pod-WordPress 资源,重新创建
root@master30:~# kubectl delete pod wordpress --force
root@master30:~# kubectl run wordpress \
--image=hub.laoma.cloud/library/wordpress \
--image-pull-policy=IfNotPresent \
--env=WORDPRESS_DB_USER=tom \
--env=WORDPRESS_DB_PASSWORD=redhat \
--env=WORDPRESS_DB_NAME=blog \
--env=WORDPRESS_DB_HOST=mysql
# 访问测试
root@client:~# firefox 10.1.8.30:31102
# 清理环境
root@master30:~# kubectl delete svc mysql wordpress
root@master30:~# kubectl delete pod mysql wordpress
5、Service 类型
Kubernetes Service 支持以下四种类型:
- ClusterIP :只能在集群内部访问。
- NodePort:通过物理节点的端口来访问,每个物理节点都提供相同的端口。
- LoadBalancer :负载均衡,来源于物理网段中一个独立的 IP。
- ExternalName:配置集群内部的 CName。
- Headless:只有服务名,不分配 IP 地址。
Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
环境准备
创建 deployment
bash
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
5.1 ClusterIP
- **默认**类型,分配集群内部虚拟 IP ;仅集群内部访问。
- NodePort、LoadBalancer 底层都基于 ClusterIP。
- 可以==手动指定
.spec.clusterIP;==设置clusterIP: None即为 Headless 无头服务。
5.2 NodePort
- 控制平面 自动从端口池30000‑32767分配 nodePort,也可以 yaml 手动指定 nodePort。
- 集群每一个节点都会监听这个 nodePort 端口 ,访问任意节点 IP:nodePort 都可以访问服务。
三个端口区分:
port:Service ClusterIP 端口(集群内部访问)targetPort:Pod 容器端口nodePort:宿主机节点对外开放端口
示例:
bash
root@master30:~# kubectl expose deployment web --type NodePort --port=8080 --target-port=80 -o yaml --dry-run=client > service-NodePort.yaml
root@master30:~# vim service-NodePort.yaml
yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 80
selector:
app: web
type: NodePort
status:
loadBalancer: {}
bash
# 创建NodePort类型Service
root@master30:~# kubectl apply -f service-NodePort.yaml
# 查看node节点对应端口为31917
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web NodePort 10.110.40.0 <none> 8080:31917/TCP 48s
# 说明:
# 1-EXTERNAL-IP为nodes, 表示可通过Cluster每个节点自身的IP访问Service。
# 2-PORT(S)为8080:31917。 8080是ClusterIP监听的端口,31917则是节点上监听的端口。
# Kubernetes会从30000~32767中分配一个可用的端口,每个节点都会监听此端口并将请求转发给Service
# 访问集群中任一节点测试
root@client:~# curl http://10.1.8.30:31917
root@client:~# curl http://10.1.8.31:31917
root@client:~# curl http://10.1.8.32:31917
分析防火墙规则
bash
# 与ClusterIP对比,每个节点的iptables中额外增加了下面两条规则:
root@master30:~# iptables-save | grep 31917
-A KUBE-NODEPORTS -p tcp -m comment --comment "laoma/web:http" -m tcp --dport 31917 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "laoma/web:http" -m tcp --dport 31917 -j KUBE-SVC-WE5D4GWSX3MMPCHH
# 规则的含义是:访问当前节点31917端口的请求会应用规则KUBE-SVC-WE5D4GWSX3MMPCHH
# 进一步分析,其作用就是负载均衡到每一个Pod。
root@master30:~# iptables-save |grep KUBE-SVC-WE5D4GWSX3MMPCHH
-A KUBE-SERVICES -d 10.110.40.0/32 -p tcp -m comment --comment "laoma/web:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-WE5D4GWSX3MMPCHH
-A KUBE-SVC-WE5D4GWSX3MMPCHH -m comment --comment "laoma/web:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-LUIIA2GDKT4VDBA2
-A KUBE-SVC-WE5D4GWSX3MMPCHH -m comment --comment "laoma/web:http" -j KUBE-SEP-Y6B6PNSXFIBR4D2K
NodePort 默认的是随机选择, 我们可以**使用 nodePort 指定为特定端口。**
yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
ports:
- port: 8080
protocol: TCP
# 指定节点固定端口
nodePort: 30080
targetPort: 80
selector:
app: web
type: NodePort
status:
loadBalancer: {}
端口说明:
- nodePort 是节点上监听的端口。
- port 是 ClusterIP 上监听的端口。
- targetPort 是 Pod 监听的端口。
清理环境
bashroot@master30:~# kubectl delete svc web
5.3 LoadBalancer
原生 K8s 没有实现 LB,裸机集群依靠 MetalLB 组件提供外部 IP。
每个 LoadBalancer 类型的 service 需要关联一个外部 IP。
部署
部署 metallb
bash
root@master30:~# wget https://laoma.cloud/class/course-materials/softwares/stage03/metallb-0.14.8.tar.gz
root@master30:~# tar -xf metallb-0.14.8.tar.gz
# 查看镜像
root@master30:~# grep image metallb-0.14.8/config/manifests/metallb-native.yaml
image: quay.io/metallb/controller:v0.14.8
image: quay.io/metallb/speaker:v0.14.8
# 按需修改镜像
root@master30:~# sed -i 's/quay.io/hub.laoma.cloud/g' metallb-0.14.8/config/manifests/metallb-native.yaml
root@master30:~# kubectl apply -f metallb-0.14.8/config/manifests/metallb-native.yaml
# 等待着所有pod正常运行再进行下一步
root@master30:~# kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/controller-786f9df989-98bjh 1/1 Running 0 85s
pod/speaker-gthhx 1/1 Running 0 85s
pod/speaker-jwj25 1/1 Running 0 85s
pod/speaker-s5zvq 1/1 Running 0 85s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/webhook-service ClusterIP 10.106.37.32 <none> 443/TCP 85s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/speaker 3 3 3 3 3 kubernetes.io/os=linux 85s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/controller 1/1 1 1 85s
NAME DESIRED CURRENT READY AGE
replicaset.apps/controller-786f9df989 1 1 1 85s
配置地址池
bash
root@master30:~# cat << 'EOF' > ippool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 10.1.8.40-10.1.8.80
EOF
root@master30:~# kubectl apply -f ippool.yaml
配置 lay2 二层宣告
bash
root@master30:~# cat << 'EOF' > L2.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
EOF
root@master30:~# kubectl apply -f L2.yaml
测试
bash
root@master30:~# kubectl expose deployment web --type LoadBalancer --port=80 --target-port=80 -o yaml --dry-run=client > service-LoadBalancer.yaml
root@master30:~# cat service-LoadBalancer.yaml
yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: web
type: LoadBalancer
status:
loadBalancer: {}
bash
root@master30:~# kubectl apply -f service-LoadBalancer.yaml
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web LoadBalancer 10.109.101.67 10.1.8.40 80:32101/TCP 5s
# 使用分配出来的EXTERNAL‑IP访问,端口写service的port,不是nodePort
[root@client ~]# curl -s http://10.1.8.40:80
<html><body><h1>It works!</h1></body></html>
总结
- 客户端流量到达 pod 路径:客户端请求 → MetalLB 虚拟 IP(LB IP) → 节点 kube-proxy → Service 转发 → 后端 Pod。
- ✅ MetalLB 只做 ARP 宣告 + IP 占坑,转发全靠 kube-proxy + Service。
- MetalLB + Service 是标准 K8s 负载均衡流程。
详细流程图
#mermaid-svg-YDCJvdrnTzUSbWI9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YDCJvdrnTzUSbWI9 .error-icon{fill:#552222;}#mermaid-svg-YDCJvdrnTzUSbWI9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YDCJvdrnTzUSbWI9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .marker.cross{stroke:#333333;}#mermaid-svg-YDCJvdrnTzUSbWI9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YDCJvdrnTzUSbWI9 p{margin:0;}#mermaid-svg-YDCJvdrnTzUSbWI9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster-label text{fill:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster-label span{color:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster-label span p{background-color:transparent;}#mermaid-svg-YDCJvdrnTzUSbWI9 .label text,#mermaid-svg-YDCJvdrnTzUSbWI9 span{fill:#333;color:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .node rect,#mermaid-svg-YDCJvdrnTzUSbWI9 .node circle,#mermaid-svg-YDCJvdrnTzUSbWI9 .node ellipse,#mermaid-svg-YDCJvdrnTzUSbWI9 .node polygon,#mermaid-svg-YDCJvdrnTzUSbWI9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .rough-node .label text,#mermaid-svg-YDCJvdrnTzUSbWI9 .node .label text,#mermaid-svg-YDCJvdrnTzUSbWI9 .image-shape .label,#mermaid-svg-YDCJvdrnTzUSbWI9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-YDCJvdrnTzUSbWI9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .rough-node .label,#mermaid-svg-YDCJvdrnTzUSbWI9 .node .label,#mermaid-svg-YDCJvdrnTzUSbWI9 .image-shape .label,#mermaid-svg-YDCJvdrnTzUSbWI9 .icon-shape .label{text-align:center;}#mermaid-svg-YDCJvdrnTzUSbWI9 .node.clickable{cursor:pointer;}#mermaid-svg-YDCJvdrnTzUSbWI9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .arrowheadPath{fill:#333333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YDCJvdrnTzUSbWI9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YDCJvdrnTzUSbWI9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YDCJvdrnTzUSbWI9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster text{fill:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 .cluster span{color:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YDCJvdrnTzUSbWI9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YDCJvdrnTzUSbWI9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-YDCJvdrnTzUSbWI9 .icon-shape,#mermaid-svg-YDCJvdrnTzUSbWI9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YDCJvdrnTzUSbWI9 .icon-shape p,#mermaid-svg-YDCJvdrnTzUSbWI9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YDCJvdrnTzUSbWI9 .icon-shape .label rect,#mermaid-svg-YDCJvdrnTzUSbWI9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YDCJvdrnTzUSbWI9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YDCJvdrnTzUSbWI9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YDCJvdrnTzUSbWI9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 LB VIP:10.1.8.40
客户端用户
ARP 寻址 → 流量进入集群任意节点
节点内核拦截 Service IP 流量
kube-proxy 处理 iptables/IPVS 规则
Service 负载均衡:选择一个健康 Pod
通过 CNI 网络转发到 Pod 所在节点
流量进入 Pod 网络命名空间
目标 Pod 接收并响应请求
- 客户端发起请求:用户通过浏览器 / 应用访问 MetalLB 分配的 LB VIP(如 10.1.8.200)。
- ARP 寻址,流量进入集群节点: MetalLB 使用 Layer2 模式,通过 ARP 广播声明 VIP 归属,流量进入集群任意一个节点。
- 节点内核拦截流量: 节点识别目标地址为 Service LB IP,将流量交给内核网络框架处理。
- kube-proxy 执行转发规则: kube-proxy 匹配 iptables/IPVS 规则,确定流量所属 Service。
- Service 负载均衡选择 Pod: 从 Service 后端 endpoints 中,按策略选择一个健康 Pod。
- CNI 网络跨节点转发: 若 Pod 不在当前节点,流量通过 Calico/Flannel 等 CNI 网络转发到目标节点。
- 流量进入 Pod: 目标节点通过 veth-pair 设备,将流量送入 Pod 网络命名空间。
- Pod 响应请求: 业务容器接收请求并返回数据,原路响应给客户端。
5.4 ExternalName
会代理流量,只做 DNS CNAME 解析 ,把集群内 service 域名 映射为外部域名。
该映射==将集群的 DNS 服务器 配置为返回具有该外部主机名值的 CNAME 记录==。集群不会为之创建任何类型代理。
例如,将当前命名空间中的 www 服务映射到 www.laoma.cloud。
yaml
apiVersion: v1
kind: Service
metadata:
name: www
spec:
type: ExternalName
externalName: www.laoma.cloud
集群内访问 www.svc.cluster.local,DNS 返回 CNAME 记录指向外部域名。主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发来完成。
5.5 Headless Services
取决于 Service 是否定义了选择算符,DNS 会以不同的方式被自动配置。
设置 clusterIP: None,不给 Service 分配集群虚拟 IP,没有负载均衡。
- 带 selector :DNS 直接返回所有后端 Pod 的 IP 地址列表 ,常用于有状态应用(StatefulSet);
- 无 selector :自己手动维护 endpoint。
6、Service 会话保持
6.1 会话保持介绍
- 默认 sessionAffinity: None :请求**随机分发到各个 Pod**。
- 设置 sessionAffinity: ClientIP :同一个客户端 IP 的请求固定转发同一个 Pod;
- 默认超时 10800 秒(3 小时)。
- 适用场景:登录会话、WebSocket 长连接、有状态业务。
工作原理:
- 客户端首次访问服务,Service 将请求转发给某个 Pod,Service 记录客户端和 Pod 的对应关系。
- 客户端的后续请求继续转发给同一个 Pod。
6.2 准备测试环境
bash
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
root@master30:~# kubectl expose deployment web --port 80
# 修改每个pod首页
root@master30:~# for pod in $(kubectl get pods -o name | awk -F/ '{print $2}'); do kubectl exec -it $pod -- bash -c "echo $pod > htdocs/index.html"; done
root@master30:~# kubectl get svc web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.111.90.183 <none> 80/TCP 9m25s
# 默认负载均衡,流量分散
root@master30:~# for i in {1..20};do curl -s 10.111.90.183;done|sort |uniq -c
10 web-6db76cb4fc-5gbx2
10 web-6db76cb4fc-b8f7l
6.3 设置会话保持
bash
# 开启会话保持
root@master30:~# kubectl patch svc web -p '{"spec":{"sessionAffinity":"ClientIP"}}'
# 再次访问,全部打到同一个pod
root@master30:~# for i in {1..20};do curl -s 10.111.90.183;done|sort |uniq -c
20 web-6db76cb4fc-5gbx2
7、金丝雀发布(灰度发布)
学习参考:金丝雀部署
思想:
新版本少量副本接收流量做验证,验证无误再逐步切流,不直接全部升级。
实现:
两套 Deployment,共用同一个 Service selector;
通过调节副本数控制新旧版本流量占比。
- stable:旧版本,track: stable,多副本
- canary:新版本,track: canary,少量副本
Service selector 匹配公共标签,不识别 track 标签。
环境准备:
- 准备 configmap 存放网页
bash
root@master30:~# mkdir web
root@master30:~# echo hello nginx 1.28 > web/index28.html
root@master30:~# echo hello nginx 1.29 > web/index29.html
root@master30:~# kubectl create configmap web --from-file=./web
root@master30:~# kubectl get configmaps web -o yaml |grep ^data -A4
data:
index28.html: |
hello nginx 1.28
index29.html: |
hello nginx 1.29
- 主要稳定的发行版将有一个
track标签,其值为stable:
bash
root@master30:~# vim webapp-1.28.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web-28
spec:
replicas: 10
selector:
matchLabels:
app: web
tier: frontend
track: stable
template:
metadata:
labels:
app: web
tier: frontend
track: stable
spec:
containers:
- image: hub.laoma.cloud/library/nginx:1.28
name: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: webcontent
mountPath: "/usr/share/nginx/html"
volumes:
- name: webcontent
configMap:
name: web
items:
- key: index28.html
path: index.html
bash
root@master30:~# kubectl apply -f webapp-1.28.yaml
- 创建 service,匹配公共标签
app:web,tier:frontend
bash
root@master30:~# vim webapp-svc.yaml
yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: web
tier: frontend
bash
root@master30:~# kubectl apply -f webapp-svc.yaml
root@master30:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.98.235.36 <none> 80/TCP 5m31s
- 部署应用新版本。新的发行版将有一个
track标签,其值为canary:
bash
root@master30:~# vim webapp-1.29.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web-29
spec:
replicas: 1
selector:
matchLabels:
app: web
tier: frontend
track: canary
template:
metadata:
labels:
app: web
tier: frontend
track: canary
spec:
containers:
- image: hub.laoma.cloud/library/nginx:1.29
name: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: webcontent
mountPath: "/usr/share/nginx/html"
volumes:
- name: webcontent
configMap:
name: web
items:
- key: index29.html
path: index.html
bash
root@master30:~# kubectl apply -f webapp-1.29.yaml
调整副本数观察流量占比
bash
root@master30:~# kubectl scale deployment web-28 --replicas 8
root@master30:~# kubectl scale deployment web-29 --replicas 2
root@master30:~# for i in {1..50}; do curl -s 10.98.235.36; done | sort -n|uniq -c
39 hello nginx 1.28
11 hello nginx 1.29
- 总 pod 数量不变的情况下,**逐步减少旧版本和增加新版本副本**数量。
bash
# 逐步切流:减少旧副本,增加新副本
root@master30:~# kubectl scale deployment web-28 --replicas 6
root@master30:~# kubectl scale deployment web-29 --replicas 4
root@master30:~# for i in {1..50}; do curl -s 10.98.235.36; done | sort -n|uniq -c
31 hello nginx 1.28
19 hello nginx 1.29
第二部分:kube-proxy
前面讲了 Service 如何"对外承诺"一个固定 IP 和负载均衡,但 Service 本身只是一个虚拟概念------真正把流量从 Service IP 转发到后端 Pod 的,是每个节点上运行的 kube-proxy。这一部分我们深入 kube-proxy 的内部实现。
8、kube-proxy 工作模式类型
kube-proxy 工作模式有以下几种:
| 模式 | 地位 | 性能 | 适用场景 | 特点 |
|---|---|---|---|---|
| iptables | 默认模式 | 中(服务数 < 1000) | 小规模集群、环境稳定 | 依赖内核 netfilter,规则多时有性能损耗 |
| IPVS | 推荐模式 | 极高(服务数 10w+) | 中大规模生产环境 | 基于内核 IPVS,哈希表查找,性能碾压 iptables |
| Userspace | 老旧/废弃 | 低 | 仅测试、兼容旧版 | 全用户态转发,性能最差,K8s 1.25+ 已移除 |
==✅ 生产环境必选 IPVS 模式,==配合 Calico 网络插件,性能与稳定性最佳。
9、工作模式查看与切换
9.1 查看工作模式
方式 1:看 configmap
bash
root@master30:~# kubectl get cm -n kube-system kube-proxy -o yaml|grep mode
mode: ""
# mode值为空。
mode 为空不等于没模式,要看 pod 日志。
方式 2:查看 kube‑proxy pod 日志(最准确)
bash
# 查看pod/kube-proxy日志
root@master30:~# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-9xfwh
pod/kube-proxy-sdflw
pod/kube-proxy-vkfmm
root@master30:~# kubectl logs -n kube-system kube-proxy-9xfwh |grep Using
I0415 13:03:28.380643 1 server.go:511] "Using lenient decoding as strict decoding failed" err=<
I0415 13:03:28.380796 1 server_linux.go:69] "Using iptables proxy"
I0415 13:03:28.473406 1 server_linux.go:165] "Using iptables Proxier"
# 输出内容"Using iptables proxy",表明默认使用iptables
- 输出
Using iptables proxy→ iptables 模式 - 输出
Using ipvs Proxier→ IPVS 模式
9.2 修改工作模式
- 编辑 kube‑proxy 配置 ConfigMap
bash
root@master30:~# kubectl edit configmap -n kube-system kube-proxy
找到并修改 mode 字段为相应的值,例如 ipvs:
bash
....
metricsBindAddress: ""
mode: "ipvs"
....
- 重启 kube-proxy DaemonSet,使配置生效
bash
root@master30:~# kubectl rollout restart daemonset -n kube-system kube-proxy
- 验证模式切换
bash
root@master30:~# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-72b7j
pod/kube-proxy-csxlh
pod/kube-proxy-x2stv
root@master30:~# kubectl logs -n kube-system kube-proxy-72b7j |grep Using
I0415 13:22:14.052593 1 server_linux.go:233] "Using ipvs Proxier"
# 输出内容"Using ipvs Proxier",表明使用ipvs
10、IPVS 模式
10.1 工作原理
- 在内核 创建IPVS 虚拟服务器,后端 Pod 注册为 Real Server。
- 使用哈希表存储转发规则,查找效率 O(1)。
- 根据调度算法 把流量直接分发后端 Pod,规避 iptables 大量规则链。
通信流程图:
#mermaid-svg-igqBeEQvwxKeYbfp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-igqBeEQvwxKeYbfp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-igqBeEQvwxKeYbfp .error-icon{fill:#552222;}#mermaid-svg-igqBeEQvwxKeYbfp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-igqBeEQvwxKeYbfp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-igqBeEQvwxKeYbfp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-igqBeEQvwxKeYbfp .marker.cross{stroke:#333333;}#mermaid-svg-igqBeEQvwxKeYbfp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-igqBeEQvwxKeYbfp p{margin:0;}#mermaid-svg-igqBeEQvwxKeYbfp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-igqBeEQvwxKeYbfp .cluster-label text{fill:#333;}#mermaid-svg-igqBeEQvwxKeYbfp .cluster-label span{color:#333;}#mermaid-svg-igqBeEQvwxKeYbfp .cluster-label span p{background-color:transparent;}#mermaid-svg-igqBeEQvwxKeYbfp .label text,#mermaid-svg-igqBeEQvwxKeYbfp span{fill:#333;color:#333;}#mermaid-svg-igqBeEQvwxKeYbfp .node rect,#mermaid-svg-igqBeEQvwxKeYbfp .node circle,#mermaid-svg-igqBeEQvwxKeYbfp .node ellipse,#mermaid-svg-igqBeEQvwxKeYbfp .node polygon,#mermaid-svg-igqBeEQvwxKeYbfp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-igqBeEQvwxKeYbfp .rough-node .label text,#mermaid-svg-igqBeEQvwxKeYbfp .node .label text,#mermaid-svg-igqBeEQvwxKeYbfp .image-shape .label,#mermaid-svg-igqBeEQvwxKeYbfp .icon-shape .label{text-anchor:middle;}#mermaid-svg-igqBeEQvwxKeYbfp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-igqBeEQvwxKeYbfp .rough-node .label,#mermaid-svg-igqBeEQvwxKeYbfp .node .label,#mermaid-svg-igqBeEQvwxKeYbfp .image-shape .label,#mermaid-svg-igqBeEQvwxKeYbfp .icon-shape .label{text-align:center;}#mermaid-svg-igqBeEQvwxKeYbfp .node.clickable{cursor:pointer;}#mermaid-svg-igqBeEQvwxKeYbfp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-igqBeEQvwxKeYbfp .arrowheadPath{fill:#333333;}#mermaid-svg-igqBeEQvwxKeYbfp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-igqBeEQvwxKeYbfp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-igqBeEQvwxKeYbfp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-igqBeEQvwxKeYbfp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-igqBeEQvwxKeYbfp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-igqBeEQvwxKeYbfp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-igqBeEQvwxKeYbfp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-igqBeEQvwxKeYbfp .cluster text{fill:#333;}#mermaid-svg-igqBeEQvwxKeYbfp .cluster span{color:#333;}#mermaid-svg-igqBeEQvwxKeYbfp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-igqBeEQvwxKeYbfp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-igqBeEQvwxKeYbfp rect.text{fill:none;stroke-width:0;}#mermaid-svg-igqBeEQvwxKeYbfp .icon-shape,#mermaid-svg-igqBeEQvwxKeYbfp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-igqBeEQvwxKeYbfp .icon-shape p,#mermaid-svg-igqBeEQvwxKeYbfp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-igqBeEQvwxKeYbfp .icon-shape .label rect,#mermaid-svg-igqBeEQvwxKeYbfp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-igqBeEQvwxKeYbfp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-igqBeEQvwxKeYbfp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-igqBeEQvwxKeYbfp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 Service IP:Port
哈希查找+调度算法
直接路由/隧道
客户端 Pod
宿主机网卡
内核 IPVS 虚拟服务器
选择最优后端 Pod
后端 Pod IP:Port
响应流量直接返回
核心优势:
- 支持多种高级调度算法(轮询 rr、加权轮询 wrr、最少连接 lc 等)。
- 性能与服务数量无关,支持十万级服务规模。
- 内置健康检查(与 K8s Endpoint 联动)。
10.2 IPVS 支持调度算法
rr:轮询,平均分发流量wrr:加权轮询,生产常用lc:最少连接,长连接 / websocket 适用sh:源地址哈希(对应 Service 会话保持 ClientIP)sed:最短预期延迟nq:最少队列
wrr 权重来源:①Pod 的 resources.requests;②ipvsadm 命令临时设置(仅测试)
10.3 验证原理
前置准备
- 已安装 IPVS 依赖(
ipvsadm、ipset)并加载内核模块。 - kube-proxy 已切换为 IPVS 模式。
验证步骤
1. 创建测试资源 deployment + service
bash
# 创建一个 nginx Deployment
root@master30:~# kubectl create deployment web --image=nginx --replicas=3
root@master30:~# kubectl get pods -o wide | awk '{print $1,$6}'
NAME IP
web-7c56dcdb9b-5r9jm 10.224.113.165
web-7c56dcdb9b-84bhr 10.224.113.164
web-7c56dcdb9b-nswcm 10.224.19.38
# 设置 pod 主页内容为为自己的pod名称
root@master30:~# \
for pod in $( kubectl get pods -o custom-columns=NAME:.metadata.name --no-headers)
do
kubectl exec $pod -- bash -c "echo $pod > /usr/share/nginx/html/index.html"
done
# 验证主页内容
root@master30:~# curl http://10.224.113.165
web-7c56dcdb9b-5r9jm
root@master30:~# curl http://10.224.113.164
web-7c56dcdb9b-84bhr
root@master30:~# curl http://10.224.19.38
web-7c56dcdb9b-nswcm
# 创建Service
root@master30:~# kubectl expose deployment web --port=80
root@master30:~# kubectl get svc web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.103.143.120 <none> 80/TCP 2m38s
2. 查看 IPVS 规则
节点上用 ipvsadm -Lnt ServiceIP:端口 查看内核 IPVS 虚拟服务、RealServer 后端 Pod 列表
bash
# 列出虚拟服务器
root@master30:~# ipvsadm -Lnt 10.103.143.120:80
看到类似如下的记录,其中 10.103.143.120 是 Service IP,80 是 Service Port:
ini
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.103.143.120:80 rr
-> 10.224.19.38:80 Masq 1 0 0
-> 10.224.113.164:80 Masq 1 0 0
-> 10.224.113.165:80 Masq 1 0 0
结论:IPVS 已成功为 Service 创建了虚拟服务,并绑定了所有后端 Pod。
3. 验证负载均衡效果
在集群内或外部访问 Service,观察流量是否分发到不同 Pod:
bash
# 连续访问 60 次
root@master30:~# for i in {1..60}; do curl -s 10.103.143.120; done| sort | uniq -c
20 web-7c56dcdb9b-5r9jm
20 web-7c56dcdb9b-84bhr
20 web-7c56dcdb9b-nswcm
预期输出:会看到不同的 Pod 名称各自出现 20 次,证明 IPVS 轮询(rr)算法生效。
10.4 调度算法选择
-
rr(轮询):简单均衡,节点配置一致时使用
-
wrr(加权轮询) :最推荐,适合通用业务、Web、API、网关。
wrr 权重来源:1. 与 Pod 的 resources.requests.cpu/memory 成正比;2. ipvsadm 命令临时设置,只适合临时测试。
-
lc(最少连接) :适合长连接、WebSocket、游戏服务
-
sh(源地址哈希):配合 Service sessionAffinity: ClientIP 使用
更改调度算法:
bash
root@master30:~# kubectl edit configmap kube-proxy -n kube-system
......
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: "wrr"
.......
mode: "ipvs"
.......
# 重启 kube-proxy
root@master30:~# kubectl rollout restart ds kube-proxy -n kube-system
10.5 最佳配置
yaml
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
# ========== IPVS 核心调优 ==========
ipvs:
# 调度算法(生产推荐:rr / wrr / lc 三选一)
scheduler: "rr"
# 会话保持时间(秒),0 表示关闭
tcpTimeout: 900
tcpFinTimeout: 120
udpTimeout: 300
# 排除 127.0.0.1 负载均衡(必须开启)
excludeCIDRs:
- "127.0.0.1/32"
# 严格arp,避免集群访问异常(必须开启)
strictARP: true
# ========== 性能优化 ==========
# 最大打开文件数(高并发必调)
oomScoreAdj: -999
# Iptables 规则优化
iptables:
minSyncPeriod: 5s
syncPeriod: 30s
# 日志级别
logging:
format: text
verbosity: 2
10.6 会话保持
1. sessionAffinity 优先级最高
service 配置 ClientIP → kube-proxy 自动使用 IPVS 的 SH 算法
- SH = Source Hashing 源地址哈希
- 保证同一 IP → 同一 Pod
2. 如果 service 不配置 sessionAffinity
kube-proxy 就用 ipvs scheduler 里设置的算法:
- rr(轮询)
- lc(最少连接)
- wrr(加权轮询)
11、iptables 模式
11.1 工作原理
kube-proxy 默认工作模式是 iptables 模式。
kube-proxy 监听 Service 和 Endpoint 变化,在宿主机内核中动态生成 iptables 规则链(KUBE-SERVICES、KUBE-SEP-XXX 等)。当流量进入宿主机时,通过 netfilter 框架逐条匹配 iptables 规则,实现 DNAT(目标地址转换)和负载均衡。
通信流程图:
#mermaid-svg-a2PiXNmayGUph6Hu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a2PiXNmayGUph6Hu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a2PiXNmayGUph6Hu .error-icon{fill:#552222;}#mermaid-svg-a2PiXNmayGUph6Hu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a2PiXNmayGUph6Hu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a2PiXNmayGUph6Hu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a2PiXNmayGUph6Hu .marker.cross{stroke:#333333;}#mermaid-svg-a2PiXNmayGUph6Hu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a2PiXNmayGUph6Hu p{margin:0;}#mermaid-svg-a2PiXNmayGUph6Hu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a2PiXNmayGUph6Hu .cluster-label text{fill:#333;}#mermaid-svg-a2PiXNmayGUph6Hu .cluster-label span{color:#333;}#mermaid-svg-a2PiXNmayGUph6Hu .cluster-label span p{background-color:transparent;}#mermaid-svg-a2PiXNmayGUph6Hu .label text,#mermaid-svg-a2PiXNmayGUph6Hu span{fill:#333;color:#333;}#mermaid-svg-a2PiXNmayGUph6Hu .node rect,#mermaid-svg-a2PiXNmayGUph6Hu .node circle,#mermaid-svg-a2PiXNmayGUph6Hu .node ellipse,#mermaid-svg-a2PiXNmayGUph6Hu .node polygon,#mermaid-svg-a2PiXNmayGUph6Hu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a2PiXNmayGUph6Hu .rough-node .label text,#mermaid-svg-a2PiXNmayGUph6Hu .node .label text,#mermaid-svg-a2PiXNmayGUph6Hu .image-shape .label,#mermaid-svg-a2PiXNmayGUph6Hu .icon-shape .label{text-anchor:middle;}#mermaid-svg-a2PiXNmayGUph6Hu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a2PiXNmayGUph6Hu .rough-node .label,#mermaid-svg-a2PiXNmayGUph6Hu .node .label,#mermaid-svg-a2PiXNmayGUph6Hu .image-shape .label,#mermaid-svg-a2PiXNmayGUph6Hu .icon-shape .label{text-align:center;}#mermaid-svg-a2PiXNmayGUph6Hu .node.clickable{cursor:pointer;}#mermaid-svg-a2PiXNmayGUph6Hu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a2PiXNmayGUph6Hu .arrowheadPath{fill:#333333;}#mermaid-svg-a2PiXNmayGUph6Hu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a2PiXNmayGUph6Hu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a2PiXNmayGUph6Hu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a2PiXNmayGUph6Hu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a2PiXNmayGUph6Hu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a2PiXNmayGUph6Hu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a2PiXNmayGUph6Hu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a2PiXNmayGUph6Hu .cluster text{fill:#333;}#mermaid-svg-a2PiXNmayGUph6Hu .cluster span{color:#333;}#mermaid-svg-a2PiXNmayGUph6Hu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a2PiXNmayGUph6Hu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a2PiXNmayGUph6Hu rect.text{fill:none;stroke-width:0;}#mermaid-svg-a2PiXNmayGUph6Hu .icon-shape,#mermaid-svg-a2PiXNmayGUph6Hu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a2PiXNmayGUph6Hu .icon-shape p,#mermaid-svg-a2PiXNmayGUph6Hu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a2PiXNmayGUph6Hu .icon-shape .label rect,#mermaid-svg-a2PiXNmayGUph6Hu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a2PiXNmayGUph6Hu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a2PiXNmayGUph6Hu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a2PiXNmayGUph6Hu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 Service IP:Port
匹配 Service 规则
DNAT 转换
客户端 Pod
宿主机网卡
内核 iptables 规则链
随机选择后端 Endpoint IP
后端 Pod IP:Port
响应流量原路返回
核心缺点:
- 每增加一个 Service 或 Endpoint,都会新增/修改 iptables 规则。
- 当服务数量超过 1000 时,规则链膨胀,CPU 占用飙升,延迟显著增加。
11.2 验证原理
前置准备
- kube-proxy 已切换为 iptables 模式。
验证步骤
1. 创建测试资源
使用 IPVS 实验环境准备的资源。
2. 查看防火墙规则
步骤 1:获取防火墙规则。
在任意集群节点执行,这里在 master30 节点执行,保存防火墙规则:
bash
root@master30:~# iptables-save > iptables.list
步骤 2:分析 svc-nginx 规则。
bash
root@master30:~# cat iptables.list |grep 10.103.143.120
-A KUBE-SERVICES -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-SVC-7D76YWGERGEPC4GC
-A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
分析防火墙规则:
- 第一条防火墙规则 :在
nat表KUBE-SERVICES服务总入口链中,匹配目的 IP 为 10.103.143.120/32、协议 TCP、目的端口 80 的报文,通过注释标识为 services/web 的 ClusterIP 服务,并跳转至该服务专属调度链 KUBE-SVC-7D76YWGERGEPC4GC。 - 第二条防火墙规则 :则在服务专属链
KUBE-SVC-7D76YWGERGEPC4GC中,匹配源地址非 Pod 网段 10.224.0.0/16、目的 IP 10.103.143.120/32、协议 TCP、目的端口 80 的报文,注释标识为 services/web cluster IP,并跳转至 KUBE-MARK-MASQ 进行 SNAT 标记。
步骤 3 :进一步追踪链路 KUBE-SVC-7D76YWGERGEPC4GC。
bash
root@master30:~# cat iptables.list | grep KUBE-SVC-7D76YWGERGEPC4GC
:KUBE-SVC-7D76YWGERGEPC4GC - [0:0]
-A KUBE-SERVICES -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-SVC-7D76YWGERGEPC4GC
-A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.113.164:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-HYZM2VM7RCC7M2HX
-A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.113.165:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-CHPZVFCOFL4YLCYM
-A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.19.38:80" -j KUBE-SEP-HM7RLUR2RXSQ3D6F
分析防火墙规则:
- 第一条防火墙规则: 已分析过。
- 第二条防火墙规则: 已分析过。
- 第三条防火墙规则: 在服务调度链
KUBE-SVC-7D76YWGERGEPC4GC中,对所有协议与地址的访问报文,以 1/3 随机概率 将其跳转至后端端点链KUBE-SEP-HYZM2VM7RCC7M2HX,注释标识该端点对应 services/web 服务后端 10.224.113.164:80。 - 第四条防火墙规则: 在服务调度链
KUBE-SVC-7D76YWGERGEPC4GC中,对未被前序规则匹配的报文,以 剩余流量 1/2 随机概率 (总概率 1/3)跳转至后端端点链KUBE-SEP-CHPZVFCOFL4YLCYM,注释标识对应 services/web 后端 10.224.113.165:80。 - 第五条防火墙规则: 在服务调度链
KUBE-SVC-7D76YWGERGEPC4GC中,对未被前序概率规则匹配的报文,无条件跳转 (总概率 1/3)至后端端点链KUBE-SEP-HM7RLUR2RXSQ3D6F,注释标识对应 services/web 后端 10.224.19.38:80。
步骤 4:进一步追踪上面最后三条规则目标链路。
bash
root@master30:~# cat iptables.list | grep -e KUBE-SEP-HYZM2VM7RCC7M2HX -e KUBE-SEP-CHPZVFCOFL4YLCYM -e KUBE-SEP-HM7RLUR2RXSQ3D6F | grep DNAT
-A KUBE-SEP-CHPZVFCOFL4YLCYM -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.113.165:80
-A KUBE-SEP-HM7RLUR2RXSQ3D6F -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.19.38:80
-A KUBE-SEP-HYZM2VM7RCC7M2HX -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.113.164:80
分析防火墙规则:
- 第一条防火墙规则: 在后端端点链
KUBE-SEP-HM7RLUR2RXSQ3D6F中,匹配TCP 协议 报文,注释标识为 services/web 服务,执行DNAT 目标地址转换 ,将报文目的地址修改为10.224.19.38:80。 - 第二条防火墙规则: 在后端端点链
KUBE-SEP-HYZM2VM7RCC7M2HX中,匹配TCP 协议 报文,注释标识为 services/web 服务,执行DNAT 目标地址转换 ,将报文目的地址修改为10.224.113.164:80。 - 第三条防火墙规则: 在后端端点链
KUBE-SEP-CHPZVFCOFL4YLCYM中,匹配TCP 协议 报文,注释标识为 services/web 服务,执行DNAT 目标地址转换 ,将报文目的地址修改为10.224.113.165:80。
3. 总结
全链路流程图:
- 入口:访问 ClusterIP
- 分流 :进入
KUBE-SERVICES - 服务调度 :进入
KUBE-SVC-XXX(SNAT + 负载均衡) - 端点转发 :进入
KUBE-SEP-XXX - 最终到达:后端 Pod
#mermaid-svg-P2ltF6CrToMGZMjL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P2ltF6CrToMGZMjL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P2ltF6CrToMGZMjL .error-icon{fill:#552222;}#mermaid-svg-P2ltF6CrToMGZMjL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P2ltF6CrToMGZMjL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P2ltF6CrToMGZMjL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P2ltF6CrToMGZMjL .marker.cross{stroke:#333333;}#mermaid-svg-P2ltF6CrToMGZMjL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P2ltF6CrToMGZMjL p{margin:0;}#mermaid-svg-P2ltF6CrToMGZMjL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-P2ltF6CrToMGZMjL .cluster-label text{fill:#333;}#mermaid-svg-P2ltF6CrToMGZMjL .cluster-label span{color:#333;}#mermaid-svg-P2ltF6CrToMGZMjL .cluster-label span p{background-color:transparent;}#mermaid-svg-P2ltF6CrToMGZMjL .label text,#mermaid-svg-P2ltF6CrToMGZMjL span{fill:#333;color:#333;}#mermaid-svg-P2ltF6CrToMGZMjL .node rect,#mermaid-svg-P2ltF6CrToMGZMjL .node circle,#mermaid-svg-P2ltF6CrToMGZMjL .node ellipse,#mermaid-svg-P2ltF6CrToMGZMjL .node polygon,#mermaid-svg-P2ltF6CrToMGZMjL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P2ltF6CrToMGZMjL .rough-node .label text,#mermaid-svg-P2ltF6CrToMGZMjL .node .label text,#mermaid-svg-P2ltF6CrToMGZMjL .image-shape .label,#mermaid-svg-P2ltF6CrToMGZMjL .icon-shape .label{text-anchor:middle;}#mermaid-svg-P2ltF6CrToMGZMjL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P2ltF6CrToMGZMjL .rough-node .label,#mermaid-svg-P2ltF6CrToMGZMjL .node .label,#mermaid-svg-P2ltF6CrToMGZMjL .image-shape .label,#mermaid-svg-P2ltF6CrToMGZMjL .icon-shape .label{text-align:center;}#mermaid-svg-P2ltF6CrToMGZMjL .node.clickable{cursor:pointer;}#mermaid-svg-P2ltF6CrToMGZMjL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P2ltF6CrToMGZMjL .arrowheadPath{fill:#333333;}#mermaid-svg-P2ltF6CrToMGZMjL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P2ltF6CrToMGZMjL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P2ltF6CrToMGZMjL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P2ltF6CrToMGZMjL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P2ltF6CrToMGZMjL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P2ltF6CrToMGZMjL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P2ltF6CrToMGZMjL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P2ltF6CrToMGZMjL .cluster text{fill:#333;}#mermaid-svg-P2ltF6CrToMGZMjL .cluster span{color:#333;}#mermaid-svg-P2ltF6CrToMGZMjL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P2ltF6CrToMGZMjL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P2ltF6CrToMGZMjL rect.text{fill:none;stroke-width:0;}#mermaid-svg-P2ltF6CrToMGZMjL .icon-shape,#mermaid-svg-P2ltF6CrToMGZMjL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P2ltF6CrToMGZMjL .icon-shape p,#mermaid-svg-P2ltF6CrToMGZMjL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P2ltF6CrToMGZMjL .icon-shape .label rect,#mermaid-svg-P2ltF6CrToMGZMjL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P2ltF6CrToMGZMjL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P2ltF6CrToMGZMjL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P2ltF6CrToMGZMjL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端访问
ClusterIP: 10.103.143.120:80
PREROUTING
nat 表
KUBE-SERVICES
服务总入口
KUBE-SVC-7D76YWGERGEPC4GC
services/web 专属调度链
SNAT 标记:非 Pod 网段流量
→ KUBE-MARK-MASQ
概率 1/3 负载均衡
概率 1/3 负载均衡
默认 1/3 负载均衡
KUBE-SEP-HYZM2VM7RCC7M2HX
KUBE-SEP-CHPZVFCOFL4YLCYM
KUBE-SEP-HM7RLUR2RXSQ3D6F
DNAT 转发
→ Pod: 10.224.113.164:80
DNAT 转发
→ Pod: 10.224.113.165:80
DNAT 转发
→ Pod: 10.224.19.38:80
12、Userspace 模式
kube-proxy 在用户态监听 Service 端口,接收流量后,通过本地代理转发到后端 Pod。全用户态处理,性能极低。
通信流程图:
#mermaid-svg-axBhMc0uVryM5TG9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-axBhMc0uVryM5TG9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-axBhMc0uVryM5TG9 .error-icon{fill:#552222;}#mermaid-svg-axBhMc0uVryM5TG9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-axBhMc0uVryM5TG9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-axBhMc0uVryM5TG9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-axBhMc0uVryM5TG9 .marker.cross{stroke:#333333;}#mermaid-svg-axBhMc0uVryM5TG9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-axBhMc0uVryM5TG9 p{margin:0;}#mermaid-svg-axBhMc0uVryM5TG9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-axBhMc0uVryM5TG9 .cluster-label text{fill:#333;}#mermaid-svg-axBhMc0uVryM5TG9 .cluster-label span{color:#333;}#mermaid-svg-axBhMc0uVryM5TG9 .cluster-label span p{background-color:transparent;}#mermaid-svg-axBhMc0uVryM5TG9 .label text,#mermaid-svg-axBhMc0uVryM5TG9 span{fill:#333;color:#333;}#mermaid-svg-axBhMc0uVryM5TG9 .node rect,#mermaid-svg-axBhMc0uVryM5TG9 .node circle,#mermaid-svg-axBhMc0uVryM5TG9 .node ellipse,#mermaid-svg-axBhMc0uVryM5TG9 .node polygon,#mermaid-svg-axBhMc0uVryM5TG9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-axBhMc0uVryM5TG9 .rough-node .label text,#mermaid-svg-axBhMc0uVryM5TG9 .node .label text,#mermaid-svg-axBhMc0uVryM5TG9 .image-shape .label,#mermaid-svg-axBhMc0uVryM5TG9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-axBhMc0uVryM5TG9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-axBhMc0uVryM5TG9 .rough-node .label,#mermaid-svg-axBhMc0uVryM5TG9 .node .label,#mermaid-svg-axBhMc0uVryM5TG9 .image-shape .label,#mermaid-svg-axBhMc0uVryM5TG9 .icon-shape .label{text-align:center;}#mermaid-svg-axBhMc0uVryM5TG9 .node.clickable{cursor:pointer;}#mermaid-svg-axBhMc0uVryM5TG9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-axBhMc0uVryM5TG9 .arrowheadPath{fill:#333333;}#mermaid-svg-axBhMc0uVryM5TG9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-axBhMc0uVryM5TG9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-axBhMc0uVryM5TG9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-axBhMc0uVryM5TG9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-axBhMc0uVryM5TG9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-axBhMc0uVryM5TG9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-axBhMc0uVryM5TG9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-axBhMc0uVryM5TG9 .cluster text{fill:#333;}#mermaid-svg-axBhMc0uVryM5TG9 .cluster span{color:#333;}#mermaid-svg-axBhMc0uVryM5TG9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-axBhMc0uVryM5TG9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-axBhMc0uVryM5TG9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-axBhMc0uVryM5TG9 .icon-shape,#mermaid-svg-axBhMc0uVryM5TG9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-axBhMc0uVryM5TG9 .icon-shape p,#mermaid-svg-axBhMc0uVryM5TG9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-axBhMc0uVryM5TG9 .icon-shape .label rect,#mermaid-svg-axBhMc0uVryM5TG9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-axBhMc0uVryM5TG9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-axBhMc0uVryM5TG9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-axBhMc0uVryM5TG9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 Service IP:Port
客户端 Pod
宿主机 kube-proxy 用户态进程
负载均衡选择后端 Pod
建立连接转发流量
后端 Pod
现状:K8s 1.25+ 版本已彻底移除该模式,不再支持。
总结
- Service 是抽象层:提供固定 ClusterIP、负载均衡与服务发现,类型有 ClusterIP / NodePort / LoadBalancer / ExternalName / Headless。
- kube-proxy 是实现层:Service IP 只是一个虚拟 IP,能否 ping 通、流量如何转发,完全取决于 kube-proxy 写入内核的规则。
- iptables 模式:逐条规则匹配 + DNAT,默认模式,适合小规模。
- IPVS 模式:内核哈希表 + 调度算法,生产推荐。
- Userspace 模式:已废弃。
- 学习建议:先通过第一部分理解 Service 的"是什么、怎么用",再通过第二部分用
iptables-save和ipvsadm命令亲眼验证"流量到底怎么转"。
环境清理
bash
root@master30:~# kubectl delete ns services