Kubernetes Service
学习参考:Service
环境准备
bash
[root@master30 ~ 16:09:49]# kubectl create namespace services
namespace/services created
[root@master30 ~ 16:10:02]# kubectl config set-context --current --namespace services
Context "kubernetes-admin@kubernetes" modified.
[root@master30 ~ 16:10:25]# mkdir services
[root@master30 ~ 16:10:46]# cd services/
先看两个例子
示例1:
bash
[root@master30 services 16:14:58]# kubectl run web --image=docker.io/library/httpd --image-pull-policy=IfNotPresent -o yaml --dry-run=client > pod-web.yml
[root@master30 services 16:15:07]# ls
pod-web.yml
[root@master30 services 16:15:11]# kubectl apply -f pod-web.yml
pod/web created
[root@master30 services 16:15:47]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web 1/1 Running 0 35s 10.224.45.33 worker32 <none> <none>
# 此时集群外节点无法通过pod-ip访问pod
root@client:~# curl http://10.224.193.65
# 删除pod
root@master30:~# kubectl delete pod web --force
# 重新创建一个可以访问的pod,修改如下
root@master30:~# vim pod-web.yml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web
name: web
spec:
containers:
- image: docker.io/library/httpd
imagePullPolicy: IfNotPresent
name: web
# 添加ports参数
ports:
- containerPort: 80
hostPort: 8080
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@master30:~# kubectl apply -f pod-web.yml
root@master30:~# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web 1/1 Running 0 5s 10.224.51.150 worker31.laoma.cloud <none> <none>
# 外部节点可以通过worker31的8080端口访问pod
[root@client ~]# curl http://worker31.laoma.cloud:8080
<html><body><h1>It works!</h1></body></html>
# 结论,pod只能通过所在主机的ip访问,集群外部需要知道pod运行在哪个主机。
# 如果pod通过控制器管理,在一个主机上创建多个相同的pod,则出现端口冲突
# 清理 pod
root@master30:~# kubectl delete pod web --force
示例2:
bash
root@master30:~# kubectl create deployment web --image=docker.io/library/httpd --replicas=4 --dry-run=client -o yaml > deploy-web.yml
root@master30:~# vim deploy-web.yml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: web
name: web
spec:
replicas: 4
selector:
matchLabels:
app: web
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: web
spec:
containers:
- image: docker.io/library/httpd
name: httpd
#添加imagePullPolicy
imagePullPolicy: IfNotPresent
#添加ports
ports:
- containerPort: 80
hostPort: 8080
resources: {}
status: {}
root@master30:~# kubectl apply -f deploy-web.yml
root@master30:~# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-79fc679949-2tr7x 0/1 Pending 0 7s
web-79fc679949-msbc5 1/1 Running 0 7s
web-79fc679949-t6twf 0/1 Pending 0 7s
web-79fc679949-vqfr8 1/1 Running 0 7s
# 有2个pod状态是挂起,原因是没有多余的端口
root@master30:~# kubectl describe pod web-79fc679949-2tr7x
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 49s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't have free ports for the requested pod ports.
Warning FailedScheduling 49s default-scheduler 0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't have free ports for the requested pod ports.
# 清理环境
root@master30:~# kubectl delete deployments.apps web
问题总结:
- 集群外部客户端需要知道pod运行在哪个主机才能访问pod。
- pod通过控制器控制时候,无法在同一个主机上创建多个类似pod。
Service 介绍
如果你使用 Deployment 来运行你的应用, Deployment 可以动态地创建和销毁 Pod。 在任何时刻,你都不知道有多少个这样的 Pod 正在工作以及它们健康与否; 你甚至不知道如何辨别 Pod是否健康。 Kubernetes Pod 的创建和销毁是为了匹配集群的预期状态。 Pod 是临时资源(你不应该期待单个 Pod 既可靠又耐用)。
每个 Pod 会获得属于自己的 IP 地址(Kubernetes 使用网络插件来保证这一点)。 对于集群中给定的某个 Deployment,这一刻运行的 Pod 集合可能不同于下一刻运行该应用的 Pod 集合。
**这就带来了一个问题:**如果某组 Pod(称为"后端")为集群内的其他 Pod(称为"前端") 集合提供功能,前端要如何发现并跟踪要连接的 IP 地址,以便其使用负载的后端组件呢?
答案是 Service。
- Kubernetes 中 Service ,可以将运行在一个或一组 Pod 上的网络应用程序公开为网络服务。Service有自己的IP和端口,而且这个IP是不变的。Service为Pod提供了负载均衡。客户端只需要访问Service的IP, Kubernetes则负责建立和维护Service与Pod的映射关系。 无论后端Pod如何变化, 对客户端不会有任何影响, 因为Service没有变。
- Kubernetes 中 Service 的一个关键目标:让你无需修改现有应用以使用某种不熟悉的服务发现机制。 你可以在 Pod 集合中运行代码,无论该代码是为云原生环境设计的, 还是被容器化的老应用。 你可以使用 Service 让一组 Pod 可在网络上访问,这样客户端就能与之交互。
Service 基本管理
环境准备:创建 deployment
bash
# 创建 Deployment
[root@master30 services 16:20:10]# kubectl create deployment web --image=docker.io/library/httpd --replicas=3
deployment.apps/web created
# 查看pod
[root@master30 services 16:21:33]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
web-6db76cb4fc-9vbkn 1/1 Running 0 52s app=web,pod-template-hash=6db76cb4fc
web-6db76cb4fc-drkpx 1/1 Running 0 52s app=web,pod-template-hash=6db76cb4fc
web-6db76cb4fc-sfx4v 1/1 Running 0 52s app=web,pod-template-hash=6db76cb4fc
创建 Service
bash
[root@master30 services 16:22:11]# kubectl create service clusterip web --tcp=8080:80
service/web created
[root@master30 services 16:27:33]# kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.103.108.10 <none> 8080/TCP 11s
## 选项--tcp=8080:80代表访问集群ip的8080/TCP,将转发给pod的80端口
## svc默认标签是:app=<svc-name>
# 查看Service详细信息
[root@master30 services 16:27:44]# kubectl describe services web
Name: web
Namespace: services
Labels: app=web
Annotations: <none>
Selector: app=web
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.103.108.10
IPs: 10.103.108.10
Port: 8080-80 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.224.137.35:80,10.224.45.35:80,10.224.45.36:80
Session Affinity: None
Events: <none>
# 访问测试,访问service-ip对应的8080端口
[root@master30 services 16:29:11]# curl 10.103.108.10:8080
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>It works! Apache httpd</title>
</head>
<body>
<p>It works!</p>
</body>
</html>
验证 Service
- 更改每个pod主页,验证负载均衡功能。
bash
# 更改每个pod主页
[root@master30 services 16:31:29]# 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 services 16:32:45]# for i in {1..60};do curl -s 10.103.108.10:8080;done|sort |uniq -c
23 web-6db76cb4fc-9vbkn
13 web-6db76cb4fc-drkpx
24 web-6db76cb4fc-sfx4v
- 此时如果创建一个具有相同标签的pod,service也会将请求转发到该pod
bash
[root@master30 services 16:33:00]# kubectl run web --image=docker.io/library/httpd --labels=app=web
pod/web created
[root@master30 services 16:34:55]# kubectl get pod
NAME READY STATUS RESTARTS AGE
web 1/1 Running 0 11s
web-6db76cb4fc-9vbkn 1/1 Running 0 14m
web-6db76cb4fc-drkpx 1/1 Running 0 14m
web-6db76cb4fc-sfx4v 1/1 Running 0 14m
[root@master30 services 16:35:06]# kubectl exec -it web -- bash -c "echo web > htdocs/index.html"
[root@master30 services 16:35:18]# for i in {1..60};do curl -s 10.103.108.10:8080;done|sort |uniq -c
14 web
18 web-6db76cb4fc-9vbkn
19 web-6db76cb4fc-drkpx
9 web-6db76cb4fc-sfx4v
- 此时重启deploy,service仍然能动态发现后端pod
bash
[root@master30 services 16:35:55]# kubectl rollout restart deployment web
deployment.apps/web restarted
[root@master30 services 16:40:05]# 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 services 16:40:27]# for i in {1..60};do curl -s 10.103.108.10:8080;done|sort |uniq -c
16 web
15 web-75cfd9c986-r8z8x
12 web-75cfd9c986-rjxtk
17 web-75cfd9c986-wf6lj
- 如果创建的pod具有标签app1=web1和app2=web2,而deploy控制器的selector匹配的标签为app1=web1,service匹配的标签为app2=web2也是可以的。
bash
# 清理环境,重建 Deployment和Service root@master30:~# kubectl delete deployments.apps web --force root@master30:~# kubectl delete service web 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: docker.io/library/httpd name: httpd imagePullPolicy: IfNotPresent resources: {} status: {}
bash
bash root@master30:~# kubectl apply -f deploy-web.yml
# 通过expose方式创建service
root@master30:~# kubectl expose deployment web --port=8080 --target-port=80 --selector=app2=web2
# 选项说明: # --port=8080,定义service监听的端口 # --target-port=80,定义后端pod鉴定的端口 # --selector=app2=web2,定义service选择器标签
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
It works!
- 无法ping通service ip,但可以ping通pod。service只允许http方式访问80,其他没有做iptables映射。
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
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
type: ClusterIP
status:
loadBalancer: {}
Service 发现
所谓发现 Service,是指集群内应用访问 Service。
我们介绍以下三种方式发现 Service:
- 通过 IP 访问 Service。
- 通过环境变量访问 Service。
- 通过 dns 解析的名称访问 Service。
通过 IP 访问 Service
实验准备:mysql+wordpress
准备mysql资源
bash
#生成yaml文件
[root@master30 services 10:04:26]# kubectl run mysql --image=hub.laoma.cloud/library/mysql:latest \
> --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 services 10:19:01]# ls
pod-mysql.yaml pod-web.yml
#应用文件
[root@master30 services 10:19:05]# kubectl apply -f pod-mysql.yaml
pod/mysql created
#设置端口
[root@master30 services 10:19:17]# kubectl expose pod mysql --port=3306 --target-port=3306
service/mysql exposed
[root@master30 services 10:19:47]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mysql 1/1 Running 0 38s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mysql ClusterIP 10.97.76.107 <none> 3306/TCP 8s
[root@master30 services 10:19:55]# apt install -y mysql-client
[root@master30 services 10:20:52]# mysql -utom -predhat -h10.97.76.107
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 9.6.0 MySQL Community Server - GPL
Copyright (c) 2000, 2026, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| blog |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.01 sec)
mysql>
准备WordPress资源
bash
#获取mysql ip
[root@master30 services 10:23:37]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/mysql 1/1 Running 0 4m28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mysql ClusterIP 10.97.76.107 <none> 3306/TCP 3m58s
[root@master30 services 10:23:55]# 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.97.76.107
pod/wordpress created
# 为了测试方便,我们这里创建NodePort类型Service
[root@master30 services 10:37:54]# kubectl expose pod wordpress --port=80 --target-port=80 --type NodePort
service/wordpress exposed
[root@master30 services 10:39:31]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql ClusterIP 10.97.76.107 <none> 3306/TCP 19m
wordpress NodePort 10.110.213.72 <none> 80:30743/TCP 10s
访问测试
通过环境变量访问 Service
获取环境变量信息
bash
[root@master30 services 10:41:29]# kubectl run test --rm -it --image=hub.laoma.cloud/library/busybox --image-pull-policy=IfNotPresent
If you don't see a command prompt, try pressing enter.
/ # env | grep MYSQL
MYSQL_PORT_3306_TCP_ADDR=10.97.76.107
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_SERVICE_HOST=10.97.76.107
MYSQL_PORT=tcp://10.97.76.107:3306
MYSQL_SERVICE_PORT=3306
MYSQL_PORT_3306_TCP=tcp://10.97.76.107:3306
/ # exit
Session ended, resume using 'kubectl attach test -c test -i -t' command when the pod is running
pod "test" deleted
说明:
- 可以通过环境变量MYSQL_SERVICE_HOST访问服务mysql。
- service创建后才能使用该环境变量。
- service属于namespace,pod只能访问同一个namespace中service。
准备WordPress资源
bash
# 删除 pod-WordPress 资源,重新创建
[root@master30 services 10:43:15]# kubectl delete pod wordpress --force
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "wordpress" force deleted
# 查看Service变量
[root@master30 services 10:45:37]# kubectl exec -it wordpress -- sh -c 'env | grep MYSQL'
MYSQL_PORT_3306_TCP_ADDR=10.97.76.107
MYSQL_PORT_3306_TCP_PORT=3306
MYSQL_SERVICE_HOST=10.97.76.107
MYSQL_PORT_3306_TCP_PROTO=tcp
MYSQL_PORT=tcp://10.97.76.107:3306
MYSQL_SERVICE_PORT=3306
MYSQL_PORT_3306_TCP=tcp://10.97.76.107:3306
访问测试
通过 DNS 名称访问 Service
Kubernetes 还提供了更为方便的DNS访问。kubeadm部署时会默认安装coredns组件。
bash
[root@master30 services 10:45:47]# kubectl get deployments.apps --namespace=kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
calico-kube-controllers 1/1 1 1 6d
coredns 2/2 2 2 6d18h
coredns是一个DNS服务器。 每当有新的Service被创建, coredns会添加该Service的DNS记录。 Cluster中的Pod可以通过.访问Service。
bash
root@master30:~# kubectl run busybox --rm -it --image=docker.io/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
/ # 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
由于这个Pod与web service同属于laoma namespace, 因此可以省略laoma直接用web访问Service.
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 services 10:47:20]# kubectl delete pod wordpress --force
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "wordpress" force deleted
[root@master30 services 10:48:05]# 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
pod/wordpress created
测试访问
环境清理
bash
[root@master30 services 10:51:07]# kubectl delete service mysql wordpress
service "mysql" deleted
service "wordpress" deleted
[root@master30 services 10:51:21]# kubectl delete pod mysql wordpress
pod "mysql" deleted
pod "wordpress" deleted
[root@master30 services 10:51:36]# kubectl get all
No resources found in services namespace.
Service 类型
Kubernetes Service 支持以下四种类型:
- ClusterIP:只能在集群内部访问。
- NodePort:通过物理节点的端口来访问,每个物理节点都提供相同的端口。
- LoadBalancer:负载均衡,来源于物理网段中一个独立的IP。
- ExternalName:配置集群内部的CName。
- Headless:只有服务名,不分配IP地址。
我们的应用可能希望将 Service 暴露在一个外部 IP 地址上。 Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
环境准备
创建 deployment
bash
[root@master30 ~ 11:55:41]# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/web created
ClusterIP
ClusterIP,是通过集群的内部 IP 公开 Service,选择该值时 Service 只能够在集群内部访问。 这也是服务类型的默认值。
- ClusterIP 从集群中预留的 IP 地址池中分配一个 IP 地址。其他几种 Service 类型在
ClusterIP类型的基础上进行构建。 - 在创建
Service的请求中,可以通过设置.spec.clusterIP字段来指定自己的集群 IP 地址。
如果将 Service 的 .spec.clusterIP 设置为 "None",则 Kubernetes 不会为其分配 IP 地址。
- 所选择的 IP 地址必须是合法的 IPv4 或者 IPv6 地址,并且这个 IP 地址在 API 服务器上所配置的
service-cluster-ip-rangeCIDR 范围内。 如果你尝试创建一个带有非法clusterIP地址值的 Service,API 服务器会返回 HTTP 状态码 422, 表示值不合法。
其他信息参考上面的 发现 Service章节。
NodePort
- 如果将Service 的
type字段设置为NodePort,则 Kubernetes 控制平面将在--service-node-port-range标志所指定的范围内分配端口(默认值:30000-32767 )。 Service 在其.spec.ports[*].nodePort字段中报告已分配的端口。 NodePort类型的 Service 通过每个节点上的 IP 和分配的端口(NodePort)公开 Service。 为了让 Service 可通过节点端口访问,Kubernetes 会为 NodePort 类型的Service 配置 clusterIP 地址。每个节点将该端口(每个节点上的相同端口号)上的流量代理到 Service。
示例:
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
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: {}
# 创建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
端口说明:
- nodePort是节点上监听的端口。
- port是ClusterIP上监听的端口。
- targetPort是Pod监听的端口。
LoadBalancer
kubernetes并没有真正实现 LoadBalancer,需要借助第三方工具实现,例如metallb。
每个LoadBalancer类型的service需要关联一个公网IP。创建LoadBalancer类型的service只需要将Service 的 Type 改成 LoadBalancer。
这里我们使用 metallb。
部署
部署 metallb
bash
[root@master30 ~ 11:39:45]# tar -xf metallb-0.14.8.tar.gz
# 查看镜像
[root@master30 ~ 11:39:54]# 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 ~ 11:40:19]# sed -i 's/quay.io/hub.laoma.cloud/g' metallb-0.14.8/config/manifests/metallb-native.yaml
[root@master30 ~ 11:40:30]# grep image metallb-0.14.8/config/manifests/metallb-native.yaml
image: hub.laoma.cloud/metallb/controller:v0.14.8
image: hub.laoma.cloud/metallb/speaker:v0.14.8
# 等待着所有pod正常运行再进行下一步
[root@master30 ~ 11:40:32]# kubectl apply -f metallb-0.14.8/config/manifests/metallb-native.yaml
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
。。。。。。。
[root@master30 ~ 11:42:12]# kubectl get all -n metallb-system
NAME READY STATUS RESTARTS AGE
pod/controller-d6499775f-pxfzm 1/1 Running 0 25s
pod/speaker-dtfkx 1/1 Running 0 25s
pod/speaker-zdc7m 0/1 Running 0 25s
pod/speaker-zl6xs 0/1 Running 0 25s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/metallb-webhook-service ClusterIP 10.104.204.83 <none> 443/TCP 25s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/speaker 3 3 1 3 1 kubernetes.io/os=linux 25s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/controller 1/1 1 1 25s
NAME DESIRED CURRENT READY AGE
replicaset.apps/controller-d6499775f 1 1 1 25s
配置地址池
bash
[root@master30 ~ 11:43:18]# 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 ~ 11:43:36]# kubectl apply -f ippool.yaml
ipaddresspool.metallb.io/first-pool created
配置 lay2
bash
[root@master30 ~ 11:43:47]# cat << 'EOF' > L2.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
EOF
[root@master30 ~ 11:44:23]# kubectl apply -f L2.yaml
l2advertisement.metallb.io/example created
测试
bash
[root@master30 ~ 12:18:09]# kubectl expose deployment web --type LoadBalancer --port=80 --target-port=80 -o yaml --dry-run=client > service-LoadBalancer.yaml
[root@master30 ~ 12:18:21]# cat service-LoadBalancer.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: {}
[root@master30 ~ 12:18:27]# kubectl apply -f service-LoadBalancer.yaml
service/web created
[root@master30 ~ 12:19:07]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web LoadBalancer 10.97.23.204 10.1.8.40 80:30530/TCP 7s
# 访问测试,端口号使用service的80,非32101端口
[root@master30 ~ 12:19:14]# curl 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-n168PrJoeydSE1J4{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-n168PrJoeydSE1J4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-n168PrJoeydSE1J4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-n168PrJoeydSE1J4 .error-icon{fill:#552222;}#mermaid-svg-n168PrJoeydSE1J4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-n168PrJoeydSE1J4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-n168PrJoeydSE1J4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-n168PrJoeydSE1J4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-n168PrJoeydSE1J4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-n168PrJoeydSE1J4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-n168PrJoeydSE1J4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-n168PrJoeydSE1J4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-n168PrJoeydSE1J4 .marker.cross{stroke:#333333;}#mermaid-svg-n168PrJoeydSE1J4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-n168PrJoeydSE1J4 p{margin:0;}#mermaid-svg-n168PrJoeydSE1J4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-n168PrJoeydSE1J4 .cluster-label text{fill:#333;}#mermaid-svg-n168PrJoeydSE1J4 .cluster-label span{color:#333;}#mermaid-svg-n168PrJoeydSE1J4 .cluster-label span p{background-color:transparent;}#mermaid-svg-n168PrJoeydSE1J4 .label text,#mermaid-svg-n168PrJoeydSE1J4 span{fill:#333;color:#333;}#mermaid-svg-n168PrJoeydSE1J4 .node rect,#mermaid-svg-n168PrJoeydSE1J4 .node circle,#mermaid-svg-n168PrJoeydSE1J4 .node ellipse,#mermaid-svg-n168PrJoeydSE1J4 .node polygon,#mermaid-svg-n168PrJoeydSE1J4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-n168PrJoeydSE1J4 .rough-node .label text,#mermaid-svg-n168PrJoeydSE1J4 .node .label text,#mermaid-svg-n168PrJoeydSE1J4 .image-shape .label,#mermaid-svg-n168PrJoeydSE1J4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-n168PrJoeydSE1J4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-n168PrJoeydSE1J4 .rough-node .label,#mermaid-svg-n168PrJoeydSE1J4 .node .label,#mermaid-svg-n168PrJoeydSE1J4 .image-shape .label,#mermaid-svg-n168PrJoeydSE1J4 .icon-shape .label{text-align:center;}#mermaid-svg-n168PrJoeydSE1J4 .node.clickable{cursor:pointer;}#mermaid-svg-n168PrJoeydSE1J4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-n168PrJoeydSE1J4 .arrowheadPath{fill:#333333;}#mermaid-svg-n168PrJoeydSE1J4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-n168PrJoeydSE1J4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-n168PrJoeydSE1J4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n168PrJoeydSE1J4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-n168PrJoeydSE1J4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n168PrJoeydSE1J4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-n168PrJoeydSE1J4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-n168PrJoeydSE1J4 .cluster text{fill:#333;}#mermaid-svg-n168PrJoeydSE1J4 .cluster span{color:#333;}#mermaid-svg-n168PrJoeydSE1J4 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-n168PrJoeydSE1J4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-n168PrJoeydSE1J4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-n168PrJoeydSE1J4 .icon-shape,#mermaid-svg-n168PrJoeydSE1J4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-n168PrJoeydSE1J4 .icon-shape p,#mermaid-svg-n168PrJoeydSE1J4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-n168PrJoeydSE1J4 .icon-shape .label rect,#mermaid-svg-n168PrJoeydSE1J4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-n168PrJoeydSE1J4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-n168PrJoeydSE1J4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-n168PrJoeydSE1J4 :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 响应请求:**业务容器接收请求并返回数据,原路响应给客户端。
ExternalName
ExternalName,将服务映射到 externalName 字段的内容(例如,api.foo.bar.example)。 该映射将集群的 DNS 服务器配置为返回具有该外部主机名值的 CNAME 记录。 集群不会为之创建任何类型代理。
例如,将 prod 命名空间中的 my-service 服务映射到 database.example.com。
yaml
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: database.example.com
当查找主机 my-service.prod.svc.cluster.local 时,集群 DNS 服务返回 CNAME 记录, 其值为 database.example.com。访问 my-service 的方式与访问其他 Service 的方式相同, 主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发来完成。
Headless Services
有时并不需要负载均衡,也不需要单独的 Service IP。遇到这种情况,可以通过显式设置ClusterIP的值为 None 来创建无头服务(Headless Service)。
无头 Service 不会获得集群 IP,kube-proxy 不会处理这类 Service, 而且平台也不会为它们提供负载均衡或路由支持。
取决于 Service 是否定义了选择算符,DNS 会以不同的方式被自动配置。
-
带选择算符的服务,对定义了选择算符的无头 Service,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象,并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址), 这些记录直接指向 Service 的后端 Pod 集合。
-
无选择算符的服务,对没有定义选择算符的无头 Service,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会执行以下操作之一:
-
对于
type: ExternalNameService,查找和配置其 CNAME 记录; -
对所有其他类型的 Service,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A/AAAA 记录:对于 IPv4 端点,DNS 系统创建 A 记录;对于 IPv6 端点,DNS 系统创建 AAAA 记录。
当你定义无选择算符的无头 Service 时,
port必须与targetPort匹配。
Service 会话保持
会话保持介绍
如果要确保来自特定客户端的连接每次都传递给同一个 Pod, 你可以通过设置 Service 的 .spec.sessionAffinity 为 ClientIP 来设置基于客户端 IP 地址的会话亲和性(默认为 None)。
你还可以通过设置 Service 的 .spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话粘性时间(默认值为 10800,即 3 小时)。
工作原理:
- 客户端首次访问服务,Service将请求转发给某个Pod,Service记录客户端和Pod的对应关系。
- 客户端的后续请求继续转发给同一个Pod。
适合有状态服务(如:Java Web、WebSocket、游戏服务):
- 登录状态不丢失
- 购物车不丢失
- 长连接稳定
准备测试环境
bash
[root@master30 ~ 14:21:14]# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
deployment.apps/web created
[root@master30 ~ 14:21:46]# kubectl expose deployment web --port 80
service/web exposed
[root@master30 ~ 14:22:00]# 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 ~ 14:22:12]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/web-5885dfff8c-gk7jw 1/1 Running 0 36s
pod/web-5885dfff8c-tffbm 1/1 Running 0 36s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/web ClusterIP 10.106.43.135 <none> 80/TCP 22s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web 2/2 2 2 36s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-5885dfff8c 2 2 2 36s
[root@master30 ~ 14:23:10]# for i in {1..20};do curl -s 10.106.43.135:80;done|sort |uniq -c
10 web-5885dfff8c-gk7jw
10 web-5885dfff8c-tffbm
设置会话保持
bash
[root@master30 ~ 14:23:12]# kubectl patch service web -p '{"spec":{"sessionAffinity":"ClientIP"}}'
service/web patched
# 再次访问:结果保持一致
[root@master30 ~ 14:24:56]# for i in {1..20};do curl -s 10.106.43.135:80;done|sort |uniq -c
20 web-5885dfff8c-gk7jw
会话保持与 kube-proxy IPVS 的关系
1. service 里的 sessionAffinity 优先级最高
service 配置 ClientIP → kube-proxy 自动使用 IPVS 的 SH 算法
- SH = Source Hashing 源地址哈希
- 保证同一 IP → 同一 Pod
2. 如果 service 不配置 sessionAffinity
kube-proxy 就用ipvs scheduler 里设置的算法:
- rr(轮询)
- lc(最少连接)
- wrr(加权轮询)
金丝雀发布
环境准备:
bash[root@master30 ~ 14:26:49]# mkdir web [root@master30 ~ 14:27:56]# echo hello nginx 1.28 > web/index28.html [root@master30 ~ 14:28:19]# echo hello nginx 1.29 > web/index29.html [root@master30 ~ 14:28:27]# kubectl create configmap web --from-file=./web configmap/web created [root@master30 ~ 14:28:47]# kubectl get configmaps web -o yaml |grep ^data -A4 data: index28.html: | hello nginx 1.28 index29.html: | hello nginx 1.29
使用金丝雀发布部署应用新版本 ,同时保留用旧版本。 这样,新版本在完全发布之前也可以接收实时的生产流量。
例如,你可以使用 track 标签来区分不同的版本。
- 主要稳定的发行版将有一个
track标签,其值为stable:
bash
[root@master30 ~ 14:28:58]# vim webapp-1.28.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
[root@master30 ~ 14:30:13]# kubectl apply -f webapp-1.28.yaml
deployment.apps/web-28 created
- 创建 service
bash
[root@master30 ~ 14:30:38]# vim webapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: web
name: web
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: web
tier: frontend
[root@master30 ~ 14:31:34]# kubectl apply -f webapp-svc.yaml
service/web created
[root@master30 ~ 14:31:56]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.100.67.165 <none> 80/TCP 4s
- 部署应用新版本。新的发行版将有一个
track标签,其值为canary:
bash
[root@master30 ~ 14:32:00]# vim webapp-1.29.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
[root@master30 ~ 14:32:49]# kubectl apply -f webapp-1.29.yaml
deployment.apps/web-29 created
验证访问比例:
bash
[root@master30 ~ 14:33:44]# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/web-28-7c6ffdbbd7-2h95j 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-2wblj 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-5qtbx 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-7vsp2 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-g9zcr 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-q5z9f 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-sb7fs 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-t28tn 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-vxqxz 1/1 Running 0 3m16s
pod/web-28-7c6ffdbbd7-zmdz2 1/1 Running 0 3m16s
pod/web-29-5546565fbd-8ws6c 1/1 Running 0 46s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/web ClusterIP 10.100.67.165 <none> 80/TCP 118s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web-28 10/10 10 10 3m16s
deployment.apps/web-29 1/1 1 1 46s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-28-7c6ffdbbd7 10 10 10 3m16s
replicaset.apps/web-29-5546565fbd 1 1 1 46s
##修改pod数量
[root@master30 ~ 14:33:54]# kubectl scale deployment web-28 --replicas 8
deployment.apps/web-28 scaled
[root@master30 ~ 14:34:35]# kubectl scale deployment web-29 --replicas 2
deployment.apps/web-29 scaled
[root@master30 ~ 14:35:15]# for i in {1..50}; do curl -s 10.100.67.165; done | sort -n|uniq -c
40 hello nginx 1.28
10 hello nginx 1.29
- 总pod数量不变的情况下,逐步减少旧版本和增加新版本副本数量,。
bash
[root@master30 ~ 14:35:17]# kubectl scale deployment web-29 --replicas 4
deployment.apps/web-29 scaled
[root@master30 ~ 14:36:03]# kubectl scale deployment web-28 --replicas 6
deployment.apps/web-28 scaled
[root@master30 ~ 14:36:11]# for i in {1..50}; do curl -s 10.100.67.165; done | sort -n|uniq -c
32 hello nginx 1.28
18 hello nginx 1.29
kube-proxy
kube-proxy 是 K8s 服务于 Pod 网络的核心组件,负责实现 Service(服务) 到 Endpoint(后端 Pod) 的流量转发与负载均衡。
工作模式类型
kube-proxy 工作模式有以下几种:
| 模式 | 地位 | 性能 | 适用场景 | 特点 |
|---|---|---|---|---|
| iptables | 默认模式 | 中(服务数 < 1000) | 小规模集群、环境稳定 | 依赖内核 netfilter,规则多时有性能损耗 |
| IPVS | 推荐模式 | 极高(服务数 10w+) | 中大规模生产环境 | 基于内核 IPVS,哈希表查找,性能碾压 iptables |
| Userspace | 老旧/废弃 | 低 | 仅测试、兼容旧版 | 全用户态转发,性能最差,K8s 1.25+ 已移除 |
✅ 生产环境必选 IPVS 模式,配合 Calico 网络插件,性能与稳定性最佳。
工作模式切换
查看工作模式
bash
[root@master30 ~ 14:40:36]# kubectl get configmaps -n kube-system kube-proxy -o yaml | grep mode
mode: ""
# mode值为空。
# 查看pod/kube-proxy日志
[root@master30 ~ 14:55:52]# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-7z8tj
pod/kube-proxy-gqlxm
pod/kube-proxy-vmgpt
[root@master30 ~ 14:57:40]# kubectl logs -n kube-system kube-proxy-7z8tj | grep Using
I0629 03:54:06.220184 1 server_linux.go:69] "Using iptables proxy"
I0629 03:54:06.313313 1 server_linux.go:165] "Using iptables Proxier"
# 输出内容"Using iptables proxy",表明默认使用iptables
修改工作模式
bash
# 步骤 1:编辑 kube-proxy 配置 ConfigMap
[root@master30 ~ 14:57:50]# kubectl edit configmaps -n kube-system kube-proxy
configmap/kube-proxy edited
# 找到并修改 `mode` 字段为相应的值例如ipvs。
....
metricsBindAddress: ""
mode: "ipvs"
....
# 步骤 2:重启 kube-proxy DaemonSet
[root@master30 ~ 14:59:08]# kubectl rollout restart daemonset -n kube-system kube-proxy
daemonset.apps/kube-proxy restarted
# 步骤 3:验证模式切换
[root@master30 ~ 14:59:41]# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
pod/kube-proxy-jzjl5
pod/kube-proxy-m6g6m
pod/kube-proxy-q96th
[root@master30 ~ 15:00:12]# kubectl logs -n kube-system pod/kube-proxy-jzjl5 | grep Using
I0629 06:59:42.047993 1 server_linux.go:233] "Using ipvs Proxier"
# 输出内容"Using ipvs Proxier",表明使用ipvs
IPVS 模式
IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但它使用哈希表作为底层数据结构,在内核空间中生效。 这意味着 IPVS 模式下的 kube-proxy 比 iptables 模式下的 kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 为将流量均衡到后端 Pod 提供了更多选择:
rr:轮询lc:最少连接(打开连接数最少)dh:目标地址哈希sh:源地址哈希sed:最短预期延迟nq:最少队列
工作原理
kube-proxy 在宿主机内核中创建 IPVS 虚拟服务器 ,并将后端 Pod 作为 Real Server 注册。IPVS 基于 哈希表 存储转发规则,查找效率为 O(1)。流量到达后,IPVS 根据配置的调度算法直接将流量转发到后端 Pod,绕过了复杂的 iptables 规则链。
通信流程图:
#mermaid-svg-bd7Dc2zIgmuS6Mdj{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-bd7Dc2zIgmuS6Mdj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .error-icon{fill:#552222;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .marker.cross{stroke:#333333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj p{margin:0;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster-label text{fill:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster-label span{color:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster-label span p{background-color:transparent;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .label text,#mermaid-svg-bd7Dc2zIgmuS6Mdj span{fill:#333;color:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .node rect,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node circle,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node ellipse,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node polygon,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .rough-node .label text,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node .label text,#mermaid-svg-bd7Dc2zIgmuS6Mdj .image-shape .label,#mermaid-svg-bd7Dc2zIgmuS6Mdj .icon-shape .label{text-anchor:middle;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .rough-node .label,#mermaid-svg-bd7Dc2zIgmuS6Mdj .node .label,#mermaid-svg-bd7Dc2zIgmuS6Mdj .image-shape .label,#mermaid-svg-bd7Dc2zIgmuS6Mdj .icon-shape .label{text-align:center;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .node.clickable{cursor:pointer;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .arrowheadPath{fill:#333333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bd7Dc2zIgmuS6Mdj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bd7Dc2zIgmuS6Mdj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster text{fill:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .cluster span{color:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj 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-bd7Dc2zIgmuS6Mdj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bd7Dc2zIgmuS6Mdj rect.text{fill:none;stroke-width:0;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .icon-shape,#mermaid-svg-bd7Dc2zIgmuS6Mdj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .icon-shape p,#mermaid-svg-bd7Dc2zIgmuS6Mdj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .icon-shape .label rect,#mermaid-svg-bd7Dc2zIgmuS6Mdj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bd7Dc2zIgmuS6Mdj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bd7Dc2zIgmuS6Mdj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bd7Dc2zIgmuS6Mdj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 Service IP:Port
哈希查找+调度算法
直接路由/隧道
客户端 Pod
宿主机网卡
内核 IPVS 虚拟服务器
选择最优后端 Pod
后端 Pod IP:Port
响应流量直接返回
核心优势:
- 支持多种高级调度算法(轮询 rr、加权轮询 wrr、最少连接 lc 等)。
- 性能与服务数量无关,支持十万级服务规模。
- 内置健康检查(与 K8s Endpoint 联动)。
验证原理
前置准备
- 已安装 IPVS 依赖(
ipvsadm、ipset)并加载内核模块。 - kube-proxy 已切换为 IPVS 模式。
验证步骤
1. 创建测试资源
bash
# 创建一个 nginx Deployment
[root@master30 ~ 15:01:13]# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas=3
deployment.apps/web created
[root@master30 ~ 15:02:22]# kubectl get pods -o wide | awk '{print $1,$6}'
NAME IP
web-759dfd9847-64xg2 10.224.137.11
web-759dfd9847-jxcdb 10.224.45.10
web-759dfd9847-m44zq 10.224.45.11
# 设置 pod 主页内容为为自己的pod名称
[root@master30 ~ 15:02:36]# 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 ~ 15:03:02]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-759dfd9847-64xg2 1/1 Running 0 110s 10.224.137.11 worker31 <none> <none>
web-759dfd9847-jxcdb 1/1 Running 0 110s 10.224.45.10 worker32 <none> <none>
web-759dfd9847-m44zq 1/1 Running 0 110s 10.224.45.11 worker32 <none> <none>
[root@master30 ~ 15:03:34]# curl 10.224.137.11
web-759dfd9847-64xg2
[root@master30 ~ 15:03:40]# curl 10.224.45.10
web-759dfd9847-jxcdb
[root@master30 ~ 15:03:50]# curl 10.224.45.11
web-759dfd9847-m44zq
# 创建Service
[root@master30 ~ 15:03:57]# kubectl expose deployment web --port=80
service/web exposed
[root@master30 ~ 15:04:24]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.109.204.228 <none> 80/TCP 5s
2. 验证负载均衡效果
在集群内或外部访问 Service,观察流量是否分发到不同 Pod:
bash
# 连续访问 60 次
[root@master30 ~ 15:05:20]# for i in {1..60}; do curl -s 10.109.204.228; done| sort | uniq -c
20 web-759dfd9847-64xg2
20 web-759dfd9847-jxcdb
20 web-759dfd9847-m44zq
[root@master30 ~ 15:05:29]# for i in {1..60}; do curl -s 10.109.204.228; done| sort | uniq -c
20 web-759dfd9847-64xg2
20 web-759dfd9847-jxcdb
20 web-759dfd9847-m44zq
预期输出:会看到不同的 Pod 名称各自出现20次,证明 IPVS 轮询(rr)算法生效。
3. 查看 IPVS 规则
在任意集群节点执行,这里在master30节点执行,查看 kube-proxy 创建的 IPVS 虚拟服务:
bash
# 列出虚拟服务器
[root@master30 ~ 15:05:31]# ipvsadm -Lnt 10.109.204.228:80
看到类似如下的记录,其中10.109.204.228 是 Service IP,80 是 Service Port:
ini
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.109.204.228:80 rr
-> 10.224.45.10:80 Masq 1 0 40
-> 10.224.45.11:80 Masq 1 0 40
-> 10.224.137.11:80 Masq 1 0 40
结论:IPVS 已成功为 Service 创建了虚拟服务,并绑定了所有后端 Pod。
调度算法选择
- 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
iptables 模式
工作原理
kube-proxy 默认工作模式是 iptables 模式。
kube-proxy 监听 Service 和 Endpoint 变化,在宿主机内核中动态生成 iptables 规则链(KUBE-SERVICES、KUBE-SEP-XXX 等)。当流量进入宿主机时,通过 netfilter 框架逐条匹配 iptables 规则,实现 DNAT(目标地址转换)和负载均衡。
通信流程图:
#mermaid-svg-NGQetJEC4iIfLq8B{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-NGQetJEC4iIfLq8B .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NGQetJEC4iIfLq8B .error-icon{fill:#552222;}#mermaid-svg-NGQetJEC4iIfLq8B .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NGQetJEC4iIfLq8B .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NGQetJEC4iIfLq8B .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NGQetJEC4iIfLq8B .marker.cross{stroke:#333333;}#mermaid-svg-NGQetJEC4iIfLq8B svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NGQetJEC4iIfLq8B p{margin:0;}#mermaid-svg-NGQetJEC4iIfLq8B .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NGQetJEC4iIfLq8B .cluster-label text{fill:#333;}#mermaid-svg-NGQetJEC4iIfLq8B .cluster-label span{color:#333;}#mermaid-svg-NGQetJEC4iIfLq8B .cluster-label span p{background-color:transparent;}#mermaid-svg-NGQetJEC4iIfLq8B .label text,#mermaid-svg-NGQetJEC4iIfLq8B span{fill:#333;color:#333;}#mermaid-svg-NGQetJEC4iIfLq8B .node rect,#mermaid-svg-NGQetJEC4iIfLq8B .node circle,#mermaid-svg-NGQetJEC4iIfLq8B .node ellipse,#mermaid-svg-NGQetJEC4iIfLq8B .node polygon,#mermaid-svg-NGQetJEC4iIfLq8B .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NGQetJEC4iIfLq8B .rough-node .label text,#mermaid-svg-NGQetJEC4iIfLq8B .node .label text,#mermaid-svg-NGQetJEC4iIfLq8B .image-shape .label,#mermaid-svg-NGQetJEC4iIfLq8B .icon-shape .label{text-anchor:middle;}#mermaid-svg-NGQetJEC4iIfLq8B .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NGQetJEC4iIfLq8B .rough-node .label,#mermaid-svg-NGQetJEC4iIfLq8B .node .label,#mermaid-svg-NGQetJEC4iIfLq8B .image-shape .label,#mermaid-svg-NGQetJEC4iIfLq8B .icon-shape .label{text-align:center;}#mermaid-svg-NGQetJEC4iIfLq8B .node.clickable{cursor:pointer;}#mermaid-svg-NGQetJEC4iIfLq8B .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NGQetJEC4iIfLq8B .arrowheadPath{fill:#333333;}#mermaid-svg-NGQetJEC4iIfLq8B .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NGQetJEC4iIfLq8B .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NGQetJEC4iIfLq8B .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NGQetJEC4iIfLq8B .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NGQetJEC4iIfLq8B .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NGQetJEC4iIfLq8B .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NGQetJEC4iIfLq8B .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NGQetJEC4iIfLq8B .cluster text{fill:#333;}#mermaid-svg-NGQetJEC4iIfLq8B .cluster span{color:#333;}#mermaid-svg-NGQetJEC4iIfLq8B 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-NGQetJEC4iIfLq8B .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NGQetJEC4iIfLq8B rect.text{fill:none;stroke-width:0;}#mermaid-svg-NGQetJEC4iIfLq8B .icon-shape,#mermaid-svg-NGQetJEC4iIfLq8B .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NGQetJEC4iIfLq8B .icon-shape p,#mermaid-svg-NGQetJEC4iIfLq8B .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NGQetJEC4iIfLq8B .icon-shape .label rect,#mermaid-svg-NGQetJEC4iIfLq8B .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NGQetJEC4iIfLq8B .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NGQetJEC4iIfLq8B .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NGQetJEC4iIfLq8B :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 占用飙升,延迟显著增加。
全链路流程图:
- 入口:访问 ClusterIP
- 分流 :进入
KUBE-SERVICES - 服务调度 :进入
KUBE-SVC-XXX(SNAT + 负载均衡) - 端点转发 :进入
KUBE-SEP-XXX - 最终到达:后端 Pod
#mermaid-svg-aBxvWqfJYbdNXiQU{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-aBxvWqfJYbdNXiQU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aBxvWqfJYbdNXiQU .error-icon{fill:#552222;}#mermaid-svg-aBxvWqfJYbdNXiQU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aBxvWqfJYbdNXiQU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aBxvWqfJYbdNXiQU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aBxvWqfJYbdNXiQU .marker.cross{stroke:#333333;}#mermaid-svg-aBxvWqfJYbdNXiQU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aBxvWqfJYbdNXiQU p{margin:0;}#mermaid-svg-aBxvWqfJYbdNXiQU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster-label text{fill:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster-label span{color:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster-label span p{background-color:transparent;}#mermaid-svg-aBxvWqfJYbdNXiQU .label text,#mermaid-svg-aBxvWqfJYbdNXiQU span{fill:#333;color:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU .node rect,#mermaid-svg-aBxvWqfJYbdNXiQU .node circle,#mermaid-svg-aBxvWqfJYbdNXiQU .node ellipse,#mermaid-svg-aBxvWqfJYbdNXiQU .node polygon,#mermaid-svg-aBxvWqfJYbdNXiQU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aBxvWqfJYbdNXiQU .rough-node .label text,#mermaid-svg-aBxvWqfJYbdNXiQU .node .label text,#mermaid-svg-aBxvWqfJYbdNXiQU .image-shape .label,#mermaid-svg-aBxvWqfJYbdNXiQU .icon-shape .label{text-anchor:middle;}#mermaid-svg-aBxvWqfJYbdNXiQU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aBxvWqfJYbdNXiQU .rough-node .label,#mermaid-svg-aBxvWqfJYbdNXiQU .node .label,#mermaid-svg-aBxvWqfJYbdNXiQU .image-shape .label,#mermaid-svg-aBxvWqfJYbdNXiQU .icon-shape .label{text-align:center;}#mermaid-svg-aBxvWqfJYbdNXiQU .node.clickable{cursor:pointer;}#mermaid-svg-aBxvWqfJYbdNXiQU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aBxvWqfJYbdNXiQU .arrowheadPath{fill:#333333;}#mermaid-svg-aBxvWqfJYbdNXiQU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aBxvWqfJYbdNXiQU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aBxvWqfJYbdNXiQU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aBxvWqfJYbdNXiQU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aBxvWqfJYbdNXiQU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aBxvWqfJYbdNXiQU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster text{fill:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU .cluster span{color:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU 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-aBxvWqfJYbdNXiQU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aBxvWqfJYbdNXiQU rect.text{fill:none;stroke-width:0;}#mermaid-svg-aBxvWqfJYbdNXiQU .icon-shape,#mermaid-svg-aBxvWqfJYbdNXiQU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aBxvWqfJYbdNXiQU .icon-shape p,#mermaid-svg-aBxvWqfJYbdNXiQU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aBxvWqfJYbdNXiQU .icon-shape .label rect,#mermaid-svg-aBxvWqfJYbdNXiQU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aBxvWqfJYbdNXiQU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aBxvWqfJYbdNXiQU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aBxvWqfJYbdNXiQU :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
Userspace 模式
kube-proxy 在用户态监听 Service 端口,接收流量后,通过本地代理转发到后端 Pod。全用户态处理,性能极低。
通信流程图:
#mermaid-svg-FejsbL24odRdX9lJ{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-FejsbL24odRdX9lJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FejsbL24odRdX9lJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FejsbL24odRdX9lJ .error-icon{fill:#552222;}#mermaid-svg-FejsbL24odRdX9lJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FejsbL24odRdX9lJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FejsbL24odRdX9lJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FejsbL24odRdX9lJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FejsbL24odRdX9lJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FejsbL24odRdX9lJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FejsbL24odRdX9lJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FejsbL24odRdX9lJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FejsbL24odRdX9lJ .marker.cross{stroke:#333333;}#mermaid-svg-FejsbL24odRdX9lJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FejsbL24odRdX9lJ p{margin:0;}#mermaid-svg-FejsbL24odRdX9lJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FejsbL24odRdX9lJ .cluster-label text{fill:#333;}#mermaid-svg-FejsbL24odRdX9lJ .cluster-label span{color:#333;}#mermaid-svg-FejsbL24odRdX9lJ .cluster-label span p{background-color:transparent;}#mermaid-svg-FejsbL24odRdX9lJ .label text,#mermaid-svg-FejsbL24odRdX9lJ span{fill:#333;color:#333;}#mermaid-svg-FejsbL24odRdX9lJ .node rect,#mermaid-svg-FejsbL24odRdX9lJ .node circle,#mermaid-svg-FejsbL24odRdX9lJ .node ellipse,#mermaid-svg-FejsbL24odRdX9lJ .node polygon,#mermaid-svg-FejsbL24odRdX9lJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FejsbL24odRdX9lJ .rough-node .label text,#mermaid-svg-FejsbL24odRdX9lJ .node .label text,#mermaid-svg-FejsbL24odRdX9lJ .image-shape .label,#mermaid-svg-FejsbL24odRdX9lJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-FejsbL24odRdX9lJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FejsbL24odRdX9lJ .rough-node .label,#mermaid-svg-FejsbL24odRdX9lJ .node .label,#mermaid-svg-FejsbL24odRdX9lJ .image-shape .label,#mermaid-svg-FejsbL24odRdX9lJ .icon-shape .label{text-align:center;}#mermaid-svg-FejsbL24odRdX9lJ .node.clickable{cursor:pointer;}#mermaid-svg-FejsbL24odRdX9lJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FejsbL24odRdX9lJ .arrowheadPath{fill:#333333;}#mermaid-svg-FejsbL24odRdX9lJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FejsbL24odRdX9lJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FejsbL24odRdX9lJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FejsbL24odRdX9lJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FejsbL24odRdX9lJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FejsbL24odRdX9lJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FejsbL24odRdX9lJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FejsbL24odRdX9lJ .cluster text{fill:#333;}#mermaid-svg-FejsbL24odRdX9lJ .cluster span{color:#333;}#mermaid-svg-FejsbL24odRdX9lJ 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-FejsbL24odRdX9lJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FejsbL24odRdX9lJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-FejsbL24odRdX9lJ .icon-shape,#mermaid-svg-FejsbL24odRdX9lJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FejsbL24odRdX9lJ .icon-shape p,#mermaid-svg-FejsbL24odRdX9lJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FejsbL24odRdX9lJ .icon-shape .label rect,#mermaid-svg-FejsbL24odRdX9lJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FejsbL24odRdX9lJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FejsbL24odRdX9lJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FejsbL24odRdX9lJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 访问 Service IP:Port
客户端 Pod
宿主机 kube-proxy 用户态进程
负载均衡选择后端 Pod
建立连接转发流量
后端 Pod
现状:K8s 1.25+ 版本已彻底移除该模式,不再支持。
Kubernetes Ingress
环境准备
bash
[root@master30 ~ 15:11:39]# kubectl create namespace ingress
namespace/ingress created
[root@master30 ~ 16:23:34]# kubectl config set-context --current --namespace ingress
Context "kubernetes-admin@kubernetes" modified.
Ingress 介绍
Ingress 可为 Service 提供外部可访问的 URL、对其流量作负载均衡、 终止 SSL/TLS,以及基于名称的虚拟托管等能力。 Ingress 控制器 负责完成 Ingress 的工作,通常会使用某个负载均衡器、边缘路由器或其他前端来帮助处理流量。
下面是 Ingress 的一个简单示例,可将所有流量都发送到同一 Service:

Ingress 控制器
为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。
与作为 kube-controller-manager 可执行文件的一部分运行的其他类型的控制器不同, Ingress 控制器不是随集群自动启动的。 基于此页面,你可选择最适合你的集群的 ingress 控制器实现。
Kubernetes 作为一个项目,目前支持和维护 AWS、 GCE 和 Nginx Ingress 控制器。
ingress-nginx 工作流程
本次实验使用 ingress-nginx 控制器。
ingress-nginx 工作流程:
- 先部署 Ingress Controller 实体(相当于前端--Nginx)
- 然后再创建 Ingress (k8s 资源--相当于 Nginx 配置)
- Ingress Controller 与 kubernetes apiserver交互,动态的获取集群中的ingress规则。解析ingress规则,生成proxy服务的配置,比如nginx配置。
- 再写到 ingress proxy 的pod中,如果pod运行的是nginx服务,就生成nginx配置,并放到/etc/nginx/nginx.conf中。
- 然后reload服务。
Ingress 本质是7层http/https代理。
ingress-nginx 部署前提
-
本次环境使用负载均衡器处理流量,提前部署好 LoadBalancer,例如 metallb。
-
你必须部署一个 Ingress 控制器 才能满足 Ingress 的要求,例如 ingress-nginx。
ingress-nginx 部署
bash
root@master30:~# wget http://192.168.42.200/course-materials/softwares/stage03/ingress-nginx-controller-v1.11.2.tar.gz
root@master30:~# tar -xf ingress-nginx-controller-v1.11.2.tar.gz
ingress-nginx 部署文件 deploy.yaml文件包涵4个部分:
-
创建一个独立的命名空间 ingress-nginx
-
创建 ConfigMap
ConfigMap是存储通用的配置变量的,类似于配置文件,使用户可以将分布式系统中用于不同模块的环境变量统一到一个对象中管理;而它与配置文件的区别在于它是存在集群的"环境"中的,并且支持K8S集群中所有通用的操作调用方式。
创建pod时,对configmap进行绑定,pod内的应用可以直接引用ConfigMap的配置。相当于configmap为应用/运行环境封装配置。
pod使用ConfigMap,通常用于:设置环境变量的值、设置命令行参数、创建配置文件。
-
Ingress的RBAC授权的控制,其创建了Ingress用到的ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding
-
创建ingress-controller。前面提到过,ingress-controller的作用是将新加入的Ingress进行转化为httpd的配置
bash
# 查看资源使用的镜像
[root@master30 ~ 16:26:15]# grep image: ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml | uniq
image: registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3@sha256:a320a50cc91bd15fd2d6fa6de58bd98c1bd64b9a6f926ce23a600d87043455a3
# 按需修改镜像
[root@master30 ~ 16:26:59]# sed -i 's/registry.k8s.io/hub.laoma.cloud/g' ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
[root@master30 ~ 16:27:42]# sed -i 's/@sha256.*//' ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
# 创建 Ingress
[root@master30 ~ 16:28:38]# kubectl apply -f ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
注意 :registry.k8s.io 镜像仓库中镜像无法直接下载,需要配置加速。我们也可以从 这里 获取。
查看部署的资源
bash
[root@master30 ~ 16:29:12]# kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-9h6wp 0/1 Completed 0 43s
pod/ingress-nginx-admission-patch-bc9l9 0/1 Completed 0 43s
pod/ingress-nginx-controller-8659885ffd-h2g8m 1/1 Running 0 43s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.102.78.68 10.1.8.40 80:30420/TCP,443:32094/TCP 43s
service/ingress-nginx-controller-admission ClusterIP 10.105.36.143 <none> 443/TCP 43s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ingress-nginx-controller 1/1 1 1 43s
NAME DESIRED CURRENT READY AGE
replicaset.apps/ingress-nginx-controller-8659885ffd 1 1 1 43s
NAME STATUS COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create Complete 1/1 5s 43s
job.batch/ingress-nginx-admission-patch Complete 1/1 7s 43s
Ingress 规则实践
Ingress 规则说明
规则示例
-
示例1: 没有rule的Ingress规则
指定一个没有rule的defaultBackend的方式,所有发送给该IP的流量都被转发到了
defaultBackend所列的Kubernetes service上。bashapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress spec: defaultBackend: service: name: testsvc port: number: 80 -
示例2: 虚拟主机,一个域名对应一个path
yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myingress spec: ingressClassName: nginx rules: - host: www.laoma.cloud http: paths: - path: / pathType: Prefix backend: service: name: www port: number: 80 - host: web.laoma.cloud http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 80 -
示例3: 一个域名对应对多个path
yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myingress spec: ingressClassName: nginx rules: - host: www.laoma.cloud http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 80 - path: /laoma pathType: Prefix backend: service: name: laoma port: number: 80 -
示例4: https 透传
如果后端的svc是https流量,系统ingress直接转发https流量给后端service,则需要配置透传 TLS。
yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myingress annotations: # 关键:透传 TLS,Ingress 不解密 nginx.ingress.kubernetes.io/ssl-passthrough: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: ingressClassName: nginx rules: - host: www.laoma.cloud http: paths: - path: / pathType: Prefix backend: service: name: web port: number: 443
配置说明
-
跟Kubernetes的其他配置一样,ingress的配置也需要
apiVersion,kind和metadata字段。 -
Ingress spec 中包含配置一个loadbalancer或proxy server的所有信息。最重要的是,它包含了一个匹配所有入站请求的规则列表。目前ingress只支持http规则。
-
每条http规则包含以下信息:
- 一个
host配置项(比如www.laoma.cloud,默认是*) paths列表(比如:/laoma),每个path都关联一个backend,service:port的组合,比如web:80。在loadbalancer将流量转发到backend之前,所有的入站请求都要先匹配host和path。
- 一个
IngressClass
Ingress 可以由不同的控制器实现,通常使用不同的配置。 每个 Ingress 应当指定一个类,也就是一个对 IngressClass 资源的引用。 IngressClass 资源包含额外的配置,其中包括应当实现该类的控制器名称。
你可以将一个特定的 IngressClass 标记为集群默认 Ingress 类。 将某个 IngressClass 资源的 ingressclass.kubernetes.io/is-default-class 注解设置为 true 将确保新的未指定 ingressClassName 字段的 Ingress 能够被赋予这一默认 IngressClass.
路径匹配
Ingress 中的每个路径都需要有对应的路径类型(Path Type)。未明确设置 pathType 的路径无法通过合法性检查。
当前支持的路径类型有三种:
Prefix:基于以/分隔的 URL 路径前缀匹配。匹配区分大小写, 并且对路径中各个元素逐个执行匹配操作。 路径元素指的是由/分隔符分隔的路径中的标签列表。 如果每个 p 都是请求路径 p 的元素前缀,则请求与路径 p 匹配。Exact:精确匹配 URL 路径,且区分大小写。ImplementationSpecific:对于这种路径类型,匹配方法取决于 IngressClass。 具体实现可以将其作为单独的pathType处理或者作与Prefix或Exact类型相同的处理。
示例
| 类型 | 路径 | 请求路径 | 匹配与否? |
|---|---|---|---|
| Prefix | / |
(所有路径) | 是 |
| Exact | /foo |
/foo |
是 |
| Exact | /foo |
/bar |
否 |
| Exact | /foo |
/foo/ |
否 |
| Exact | /foo/ |
/foo |
否 |
| Prefix | /foo |
/foo, /foo/ |
是 |
| Prefix | /foo/ |
/foo, /foo/ |
是 |
| Prefix | /aaa/bb |
/aaa/bbb |
否 |
| Prefix | /aaa/bbb |
/aaa/bbb |
是 |
| Prefix | /aaa/bbb/ |
/aaa/bbb |
是,忽略尾部斜线 |
| Prefix | /aaa/bbb |
/aaa/bbb/ |
是,匹配尾部斜线 |
| Prefix | /aaa/bbb |
/aaa/bbb/ccc |
是,匹配子路径 |
| Prefix | /aaa/bbb |
/aaa/bbbxyz |
否,字符串前缀不匹配 |
| Prefix | /, /aaa |
/aaa/ccc |
是,匹配 /aaa 前缀 |
| Prefix | /, /aaa, /aaa/bbb |
/aaa/bbb |
是,匹配 /aaa/bbb 前缀 |
| Prefix | /, /aaa, /aaa/bbb |
/ccc |
是,匹配 / 前缀 |
| Prefix | /aaa |
/ccc |
否,使用默认后端 |
| 混合 | /foo (Prefix), /foo (Exact) |
/foo |
是,优选 Exact 类型 |
多重匹配
在某些情况下,Ingress 中会有多条路径与同一个请求匹配。这时匹配路径最长者优先。 如果仍然有两条同等的匹配路径,则精确路径类型优先于前缀路径类型。
主机名匹配
主机名可以是精确匹配 (例如 "foo.bar.com")或者使用通配符匹配 (例如 "*.foo.com")。 精确匹配要求 HTTP host 头部字段与 host 字段值完全匹配。 通配符匹配则要求 HTTP host 头部字段与通配符规则中的后缀部分相同。
| 主机 | host 头部 | 匹配与否? |
|---|---|---|
*.foo.com |
bar.foo.com |
基于相同的后缀匹配 |
*.foo.com |
baz.bar.foo.com |
不匹配,通配符仅覆盖了一个 DNS 标签 |
*.foo.com |
foo.com |
不匹配,通配符仅覆盖了一个 DNS 标签 |