kubernetes的service,proxy,ingress

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

问题总结:

  1. 集群外部客户端需要知道pod运行在哪个主机才能访问pod。
  2. 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

  1. 更改每个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
  1. 此时如果创建一个具有相同标签的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
  1. 此时重启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
  1. 如果创建的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!
  1. 无法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:

  1. 通过 IP 访问 Service。
  2. 通过环境变量访问 Service。
  3. 通过 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

说明:

  1. 可以通过环境变量MYSQL_SERVICE_HOST访问服务mysql。
  2. service创建后才能使用该环境变量。
  3. 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-range CIDR 范围内。 如果你尝试创建一个带有非法 clusterIP 地址值的 Service,API 服务器会返回 HTTP 状态码 422, 表示值不合法。

其他信息参考上面的 发现 Service章节。

NodePort

  1. 如果将Service 的 type 字段设置为 NodePort,则 Kubernetes 控制平面将在 --service-node-port-range 标志所指定的范围内分配端口(默认值:30000-32767 )。 Service 在其 .spec.ports[*].nodePort 字段中报告已分配的端口。
  2. 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 接收并响应请求

  1. 客户端发起请求:用户通过浏览器 / 应用访问 MetalLB 分配的 LB VIP(如 10.1.8.200)。
  2. ARP 寻址,流量进入集群节点:MetalLB 使用 Layer2 模式,通过 ARP 广播声明 VIP 归属,流量进入集群任意一个节点
  3. **节点内核拦截流量:**节点识别目标地址为 Service LB IP,将流量交给内核网络框架处理。
  4. **kube-proxy 执行转发规则:**kube-proxy 匹配 iptables/IPVS 规则,确定流量所属 Service。
  5. **Service 负载均衡选择 Pod:**从 Service 后端 endpoints 中,按策略选择一个健康 Pod。
  6. **CNI 网络跨节点转发:**若 Pod 不在当前节点,流量通过 Calico/Flannel 等 CNI 网络转发到目标节点。
  7. **流量进入 Pod:**目标节点通过 veth-pair 设备,将流量送入 Pod 网络命名空间。
  8. **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: ExternalName Service,查找和配置其 CNAME 记录;

  • 对所有其他类型的 Service,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A/AAAA 记录:对于 IPv4 端点,DNS 系统创建 A 记录;对于 IPv6 端点,DNS 系统创建 AAAA 记录。

    当你定义无选择算符的无头 Service 时,port 必须与 targetPort 匹配。

Service 会话保持

会话保持介绍

如果要确保来自特定客户端的连接每次都传递给同一个 Pod, 你可以通过设置 Service 的 .spec.sessionAffinityClientIP 来设置基于客户端 IP 地址的会话亲和性(默认为 None)。

你还可以通过设置 Service 的 .spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话粘性时间(默认值为 10800,即 3 小时)。

工作原理:

  1. 客户端首次访问服务,Service将请求转发给某个Pod,Service记录客户端和Pod的对应关系。
  2. 客户端的后续请求继续转发给同一个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 配置 ClientIPkube-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 标签来区分不同的版本。

  1. 主要稳定的发行版将有一个 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
  1. 创建 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
  1. 部署应用新版本。新的发行版将有一个 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
  1. 总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 联动)。

验证原理

前置准备
  1. 已安装 IPVS 依赖(ipvsadmipset)并加载内核模块。
  2. 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 占用飙升,延迟显著增加。

全链路流程图:

  1. 入口:访问 ClusterIP
  2. 分流 :进入 KUBE-SERVICES
  3. 服务调度 :进入 KUBE-SVC-XXX(SNAT + 负载均衡)
  4. 端点转发 :进入 KUBE-SEP-XXX
  5. 最终到达:后端 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 作为一个项目,目前支持和维护 AWSGCENginx Ingress 控制器。

ingress-nginx 工作流程

本次实验使用 ingress-nginx 控制器。

ingress-nginx 工作流程:

  1. 先部署 Ingress Controller 实体(相当于前端--Nginx)
  2. 然后再创建 Ingress (k8s 资源--相当于 Nginx 配置)
  3. Ingress Controller 与 kubernetes apiserver交互,动态的获取集群中的ingress规则。解析ingress规则,生成proxy服务的配置,比如nginx配置。
  4. 再写到 ingress proxy 的pod中,如果pod运行的是nginx服务,就生成nginx配置,并放到/etc/nginx/nginx.conf中。
  5. 然后reload服务。

Ingress 本质是7层http/https代理。

ingress-nginx 部署前提

  1. 本次环境使用负载均衡器处理流量,提前部署好 LoadBalancer,例如 metallb。

  2. 你必须部署一个 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个部分:

  1. 创建一个独立的命名空间 ingress-nginx

  2. 创建 ConfigMap

    ConfigMap是存储通用的配置变量的,类似于配置文件,使用户可以将分布式系统中用于不同模块的环境变量统一到一个对象中管理;而它与配置文件的区别在于它是存在集群的"环境"中的,并且支持K8S集群中所有通用的操作调用方式。

    创建pod时,对configmap进行绑定,pod内的应用可以直接引用ConfigMap的配置。相当于configmap为应用/运行环境封装配置。

    pod使用ConfigMap,通常用于:设置环境变量的值、设置命令行参数、创建配置文件。

  3. Ingress的RBAC授权的控制,其创建了Ingress用到的ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding

  4. 创建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上。

    bash 复制代码
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: test-ingress
    spec:
      defaultBackend:
        service: 
          name: testsvc
          port: 
            number: 80
  • 示例2: 虚拟主机,一个域名对应一个path

    yaml 复制代码
    apiVersion: 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

    yaml 复制代码
    apiVersion: 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。

    yaml 复制代码
    apiVersion: 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的配置也需要apiVersionkindmetadata字段。

  • Ingress spec 中包含配置一个loadbalancer或proxy server的所有信息。最重要的是,它包含了一个匹配所有入站请求的规则列表。目前ingress只支持http规则。

  • 每条http规则包含以下信息:

    • 一个host配置项(比如www.laoma.cloud,默认是*)
    • paths列表(比如:/laoma),每个path都关联一个backendservice: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 处理或者作与 PrefixExact 类型相同的处理。

示例

类型 路径 请求路径 匹配与否?
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 标签