Service 发现

所谓发现 Service,是指集群内应用访问 Service。

我们介绍以下三种方式发现 Service:

  1. 通过 IP 访问 Service。

  2. 通过环境变量访问 Service。

  3. 通过 dns 解析的名称访问 Service。

通过 IP 访问 Service

实验准备:mysql+wordpress

准备mysql资源

 root@master30:~# kubectl run mysql --image=hub.laoma.cloud/library/mysql \
 --image-pull-policy=IfNotPresent \
 --env=MYSQL_ROOT_PASSWORD=redhat \
 --env=MYSQL_USER=tom \
 --env=MYSQL_PASSWORD=redhat \
 --env=MYSQL_DATABASE=blog \
 --dry-run=client -o yaml > pod-mysql.yaml
 root@master30:~# kubectl apply -f pod-mysql.yaml
 root@master30:~# kubectl expose pod mysql --port=3306 --target-port=3306
 root@master30:~# kubectl get service
 NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
 mysql   ClusterIP   10.111.69.45   <none>        3306/TCP   25m
 ​
 root@master30:~# apt install -y mysql-client
 root@master30:~# mysql -u tom -predhat -h 10.111.69.45 --ssl-mode=DISABLED  -e 'show databases;'
 mysql: [Warning] Using a password on the command line interface can be insecure.
 +--------------------+
 | Database           |
 +--------------------+
 | information_schema |
 | blog               |
 +--------------------+

准备WordPress资源

 root@master30:~# kubectl run wordpress \
 --image=hub.laoma.cloud/library/wordpress \
 --image-pull-policy=IfNotPresent \
 --env=WORDPRESS_DB_USER=tom \
 --env=WORDPRESS_DB_PASSWORD=redhat \
 --env=WORDPRESS_DB_NAME=blog \
 --env=WORDPRESS_DB_HOST=10.111.69.45
 ​
 # 为了测试方便,我们这里创建NodePort类型Service
 root@master30:~# kubectl expose pod wordpress --port=80 --target-port=80 --type NodePort
 root@master30:~# kubectl get service
 NAME        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
 mysql       ClusterIP   10.111.69.45     <none>        3306/TCP   41m
 wordpress   NodePort    10.106.106.246   <none>        80:31102/TCP   3s
 ​
 # 访问测试
 root@client:~# firefox 10.1.8.30:31102

通过环境变量访问 Service

获取环境变量信息

 root@master30:~# kubectl run test --rm -it --image=hub.laoma.cloud/library/busybox --image-pull-policy=IfNotPresent sh
 If you don't see a command prompt, try pressing enter.
 / # env|grep MYSQL
 MYSQL_PORT_3306_TCP_ADDR=10.111.69.45
 MYSQL_PORT_3306_TCP_PORT=3306
 MYSQL_SERVICE_HOST=10.111.69.45
 MYSQL_PORT_3306_TCP_PROTO=tcp
 MYSQL_PORT=tcp://10.111.69.45:3306
 MYSQL_SERVICE_PORT=3306
 MYSQL_PORT_3306_TCP=tcp://10.111.69.45:3306
 / # exit
 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资源

 # 删除 pod-WordPress 资源,重新创建
 root@master30:~# kubectl delete pod wordpress --force
 root@master30:~# kubectl run wordpress \
 --image=hub.laoma.cloud/library/wordpress \
 --image-pull-policy=IfNotPresent \
 --env=WORDPRESS_DB_USER=tom \
 --env=WORDPRESS_DB_PASSWORD=redhat \
 --env=WORDPRESS_DB_NAME=blog \
 --env=WORDPRESS_DB_HOST='$(MYSQL_SERVICE_HOST)'
 ​
 # 查看Service变量
 root@master30:~# kubectl exec -it wordpress -- sh -c 'env|grep MYSQL'
 MYSQL_PORT_3306_TCP_ADDR=10.111.69.45
 MYSQL_PORT_3306_TCP_PORT=3306
 MYSQL_PORT_3306_TCP_PROTO=tcp
 MYSQL_SERVICE_HOST=10.111.69.45
 MYSQL_PORT=tcp://10.111.69.45:3306
 MYSQL_SERVICE_PORT=3306
 MYSQL_PORT_3306_TCP=tcp://10.111.69.45:3306
 ​
 # 访问测试
 root@client:~# firefox 10.1.8.30:31102

通过 DNS 名称访问 Service

Kubernetes 还提供了更为方便的DNS访问。kubeadm部署时会默认安装coredns组件。

 root@master30:~# kubectl get deployments.apps --namespace=kube-system 
 NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
 calico-kube-controllers   1/1     1            1           2d23h
 coredns                   2/2     2            2           3d

coredns是一个DNS服务器。 每当有新的Service被创建, coredns会添加该Service的DNS记录。 Cluster中的Pod可以通过<SERVICE_NAME>.<NAMESPACE_NAME>访问Service。

 root@master30:~# kubectl run busybox --rm -it --image=hub.laoma.cloud/library/busybox /bin/sh
 If you don't see a command prompt, try pressing enter.
 / # cat /etc/resolv.conf 
 nameserver 10.96.0.10
 search service.svc.cluster.local svc.cluster.local cluster.local laoma.cloud
 options ndots:5
 / # 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.

 / # 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服务器呢?

 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示例

 # 删除 pod-WordPress 资源,重新创建
 root@master30:~# kubectl delete pod wordpress --force
 root@master30:~# kubectl run wordpress \
 --image=hub.laoma.cloud/library/wordpress \
 --image-pull-policy=IfNotPresent \
 --env=WORDPRESS_DB_USER=tom \
 --env=WORDPRESS_DB_PASSWORD=redhat \
 --env=WORDPRESS_DB_NAME=blog \
 --env=WORDPRESS_DB_HOST=mysql
 ​
 # 访问测试
 root@client:~# firefox 10.1.8.30:31102
 ​
 # 清理环境
 root@master30:~# kubectl delete svc mysql wordpress
 root@master30:~# kubectl delete pod mysql wordpress

Service 类型

Kubernetes Service 支持以下四种类型:

  • ClusterIP:只能在集群内部访问。

  • NodePort:通过物理节点的端口来访问,每个物理节点都提供相同的端口。

  • LoadBalancer:负载均衡,来源于物理网段中一个独立的IP。

  • ExternalName:配置集群内部的CName。

  • Headless:只有服务名,不分配IP地址。

我们的应用可能希望将 Service 暴露在一个外部 IP 地址上。 Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。

环境准备

创建 deployment

 root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2

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。

示例:

 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:
   labels:
     app: web
   name: web
 spec:
   ports:
   - port: 8080
     protocol: TCP
     targetPort: 80
   selector:
     app: web
   type: NodePort
 # 创建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

分析防火墙规则:

 # 与ClusterIP对比,每个节点的iptables中额外增加了下面两条规则:
 root@master30:~# iptables-save | grep 31917
 -A KUBE-NODEPORTS -p tcp -m comment --comment "laoma/web:http" -m tcp --dport 31917 -j KUBE-MARK-MASQ
 -A KUBE-NODEPORTS -p tcp -m comment --comment "laoma/web:http" -m tcp --dport 31917 -j KUBE-SVC-WE5D4GWSX3MMPCHH
 # 规则的含义是:访问当前节点31917端口的请求会应用规则KUBE-SVC-WE5D4GWSX3MMPCHH
 ​
 # 进一步分析,其作用就是负载均衡到每一个Pod。
 root@master30:~# iptables-save |grep KUBE-SVC-WE5D4GWSX3MMPCHH
 -A KUBE-SERVICES -d 10.110.40.0/32 -p tcp -m comment --comment "laoma/web:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-WE5D4GWSX3MMPCHH
 -A KUBE-SVC-WE5D4GWSX3MMPCHH -m comment --comment "laoma/web:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-LUIIA2GDKT4VDBA2
 -A KUBE-SVC-WE5D4GWSX3MMPCHH -m comment --comment "laoma/web:http" -j KUBE-SEP-Y6B6PNSXFIBR4D2K

NodePort默认的是随机选择, 我们可以使用nodePort指定为特定端口。

 apiVersion: v1
 kind: Service
 metadata:
   creationTimestamp: null
   labels:
     app: web
   name: web
 spec:
   ports:
   - port: 8080
     protocol: TCP
     # 指定节点固定端口
     nodePort: 30080
     targetPort: 80
   selector:
     app: web
   type: NodePort
 status:
   loadBalancer: {}

端口说明:

  • nodePort是节点上监听的端口。

  • port是ClusterIP上监听的端口。

  • targetPort是Pod监听的端口。

清理环境

 root@master30:~# kubectl delete svc web 

LoadBalancer

kubernetes并没有真正实现 LoadBalancer,需要借助第三方工具实现,例如metallb。

每个LoadBalancer类型的service需要关联一个公网IP。创建LoadBalancer类型的service只需要将Service 的 Type 改成 LoadBalancer。

这里我们使用 metallb。

官方地址: https://metallb.universe.tf/

github地址:https://github.com/metallb/metallb

部署

部署 metallb

 root@master30:~# wget http://192.168.42.200/course-materials/softwares/stage03/metallb-0.14.8.tar.gz
 root@master30:~# tar -xf metallb-0.14.8.tar.gz
 # 查看镜像
 root@master30:~# grep image metallb-0.14.8/config/manifests/metallb-native.yaml
         image: quay.io/metallb/controller:v0.14.8
         image: quay.io/metallb/speaker:v0.14.8
 # 按需修改镜像
 root@master30:~# sed -i 's/quay.io/hub.laoma.cloud/g' metallb-0.14.8/config/manifests/metallb-native.yaml
 ​
 root@master30:~# kubectl apply -f metallb-0.14.8/config/manifests/metallb-native.yaml
 # 等待着所有pod正常运行再进行下一步
 root@master30:~# kubectl get all -n metallb-system 
 NAME                              READY   STATUS    RESTARTS   AGE
 pod/controller-786f9df989-98bjh   1/1     Running   0          85s
 pod/speaker-gthhx                 1/1     Running   0          85s
 pod/speaker-jwj25                 1/1     Running   0          85s
 pod/speaker-s5zvq                 1/1     Running   0          85s
 ​
 NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
 service/webhook-service   ClusterIP   10.106.37.32   <none>        443/TCP   85s
 ​
 NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
 daemonset.apps/speaker   3         3         3       3            3           kubernetes.io/os=linux   85s
 ​
 NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
 deployment.apps/controller   1/1     1            1           85s
 ​
 NAME                                    DESIRED   CURRENT   READY   AGE
 replicaset.apps/controller-786f9df989   1         1         1       85s

配置地址池

 root@master30:~# cat << 'EOF' > ippool.yaml
 apiVersion: metallb.io/v1beta1
 kind: IPAddressPool
 metadata:
   name: first-pool
   namespace: metallb-system
 spec:
   addresses:
   - 10.1.8.40-10.1.8.80
 EOF
 root@master30:~# kubectl apply -f ippool.yaml

配置 lay2

 root@master30:~# cat << 'EOF' > L2.yaml
 apiVersion: metallb.io/v1beta1
 kind: L2Advertisement
 metadata:
   name: example
   namespace: metallb-system
 EOF
 root@master30:~# kubectl apply -f L2.yaml
测试
 root@master30:~# kubectl expose pod wordpress --type LoadBalancer --port=80 --target-port=80 -o yaml --dry-run=client > service-LoadBalancer.yaml 
 root@master30:~# 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:~# kubectl apply -f service-LoadBalancer.yaml
 root@master30:~# kubectl get service
 NAME   TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
 web    LoadBalancer   10.109.101.67   10.1.8.40     80:32101/TCP   5s
 ​
 # 访问测试,端口号使用service的80,非32101端口
 [root@client ~]# curl -s http://10.1.8.40:80
 <html><body><h1>It works!</h1></body></html>

以博客为例:

总结
  • 客户端流量到达pod路径:客户端请求 → MetalLB 虚拟 IP(LB IP) → 节点 kube-proxy → Service 转发 → 后端 Pod。

  • MetalLB 只做 ARP 宣告 + IP 占坑,转发全靠 kube-proxy + Service

  • MetalLB + Service 是标准 K8s 负载均衡流程。

详细流程图
  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

 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、游戏服务):

  • 登录状态不丢失

  • 购物车不丢失

  • 长连接稳定

准备测试环境

 root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/httpd --replicas=2
 root@master30:~# kubectl expose deployment web --port 80
 root@master30:~# for pod in $(kubectl get pods -o name | awk -F/  '{print $2}'); do    kubectl exec -it $pod -- bash -c "echo $pod > htdocs/index.html"; done
 ​
 root@master30:~# kubectl get svc web
 NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
 web    ClusterIP   10.111.90.183   <none>        80/TCP    9m25s
 ​
 root@master30:~# for i in {1..20};do curl -s 10.111.90.183;done|sort |uniq -c
      10 web-6db76cb4fc-5gbx2
      10 web-6db76cb4fc-b8f7l

设置会话保持

 root@master30:~# kubectl patch svc web -p '{"spec":{"sessionAffinity":"ClientIP"}}'
 ​
 # 再次访问:结果保持一致
 root@master30:~# for i in {1..20};do curl -s 10.111.90.183;done|sort |uniq -c
      20 web-6db76cb4fc-5gbx2

会话保持与 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(加权轮询)

金丝雀发布

学习参考:金丝雀部署

环境准备:

 root@master30:~# mkdir web
 root@master30:~# echo hello nginx 1.28 > web/index28.html
 root@master30:~# echo hello nginx 1.29 > web/index29.html
 root@master30:~# kubectl create configmap web --from-file=./web
 root@master30:~# kubectl get configmaps web -o yaml |grep ^data -A4
 data:
 index28.html: |
 hello nginx 1.28
 index29.html: |
 hello nginx 1.29

使用金丝雀发布部署应用新版本 ,同时保留用旧版本。 这样,新版本在完全发布之前也可以接收实时的生产流量。

例如,你可以使用 track 标签来区分不同的版本。

  1. 主要稳定的发行版将有一个 track 标签,其值为 stable

     root@master30:~# 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:~# kubectl apply -f webapp-1.28.yaml
  2. 创建 service

     root@master30:~# 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:~# kubectl apply -f webapp-svc.yaml
     root@master30:~# kubectl get svc
     NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
     web    ClusterIP   10.98.235.36   <none>        80/TCP    5m31s
  3. 部署应用新版本。新的发行版将有一个 track 标签,其值为 canary

     root@master30:~# 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:~# kubectl apply -f webapp-1.29.yaml

    验证访问比例:

     root@master30:~# for i in {1..50}; do curl -s 10.98.235.36; done | sort -n|uniq -c
          39 hello nginx 1.28
          11 hello nginx 1.29
  4. 总pod数量不变的情况下,逐步减少旧版本和增加新版本副本数量,。

     root@master30:~# kubectl scale deployment web-28 --replicas 3
     root@master30:~# kubectl scale deployment web-29 --replicas 2
     root@master30:~# for i in {1..50}; do curl -s 10.98.235.36; done | sort -n|uniq -c
          31 hello nginx 1.28
          19 hello nginx 1.29

环境清理

 root@master30:~# kubectl delete ns services

kube-proxy

kube-proxy 是 K8s 服务于 Pod 网络的核心组件,负责实现 Service(服务)Endpoint(后端 Pod) 的流量转发与负载均衡。

工作模式类型

kube-proxy 工作模式有以下几种:

模式 地位 性能 适用场景 特点
iptables 默认模式 中(服务数 < 1000) 小规模集群、环境稳定 依赖内核 netfilter,规则多时有性能损耗
IPVS 推荐模式 极高(服务数 10w+) 中大规模生产环境 基于内核 IPVS,哈希表查找,性能碾压 iptables
Userspace 老旧/废弃 仅测试、兼容旧版 全用户态转发,性能最差,K8s 1.25+ 已移除

✅ 生产环境必选 IPVS 模式,配合 Calico 网络插件,性能与稳定性最佳。

工作模式切换

查看工作模式

 root@master30:~# kubectl get cm -n kube-system kube-proxy -o yaml|grep mode
     mode: ""
 # mode值为空。
 ​
 # 查看pod/kube-proxy日志
 root@master30:~# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
 pod/kube-proxy-9xfwh
 pod/kube-proxy-sdflw
 pod/kube-proxy-vkfmm
 ​
 root@master30:~# kubectl logs -n kube-system kube-proxy-9xfwh |grep Using
 I0415 13:03:28.380643       1 server.go:511] "Using lenient decoding as strict decoding failed" err=<
 I0415 13:03:28.380796       1 server_linux.go:69] "Using iptables proxy"
 I0415 13:03:28.473406       1 server_linux.go:165] "Using iptables Proxier"
 ​
 # 输出内容"Using iptables proxy",表明默认使用iptables

修改工作模式

 # 步骤 1:编辑 kube-proxy 配置 ConfigMap
 root@master30:~# kubectl edit configmap -n kube-system kube-proxy
 # 找到并修改 `mode` 字段为相应的值例如ipvs。
 ....
     metricsBindAddress: ""
     mode: "ipvs"
 ....
 ​
 # 步骤 2:重启 kube-proxy DaemonSet
 root@master30:~# kubectl rollout restart daemonset -n kube-system kube-proxy
 ​
 # 步骤 3:验证模式切换
 root@master30:~# kubectl get pods -n kube-system -l k8s-app=kube-proxy -o name
 pod/kube-proxy-72b7j
 pod/kube-proxy-csxlh
 pod/kube-proxy-x2stv
 ​
 root@master30:~# kubectl logs -n kube-system kube-proxy-72b7j |grep Using
 I0415 13:22:14.052593       1 server_linux.go:233] "Using ipvs Proxier"
 ​
 # 输出内容"Using ipvs Proxier",表明使用ipvs

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 规则链。

核心优势

  • 支持多种高级调度算法(轮询 rr、加权轮询 wrr、最少连接 lc 等)。

  • 性能与服务数量无关,支持十万级服务规模。

  • 内置健康检查(与 K8s Endpoint 联动)。

验证原理

前置准备
  1. 已安装 IPVS 依赖(ipvsadmipset)并加载内核模块。

  2. kube-proxy 已切换为 IPVS 模式。

验证步骤
1. 创建测试资源
 # 创建一个 nginx Deployment
 root@master30:~# kubectl create deployment web --image=nginx --replicas=3
 root@master30:~# kubectl get pods -o wide | awk '{print $1,$6}'
 NAME IP
 web-7c56dcdb9b-5r9jm 10.224.113.165
 web-7c56dcdb9b-84bhr 10.224.113.164
 web-7c56dcdb9b-nswcm 10.224.19.38
 ​
 # 设置 pod 主页内容为为自己的pod名称
 root@master30:~# \
 for pod in $( kubectl get pods -o custom-columns=NAME:.metadata.name --no-headers)
 do
   kubectl exec $pod -- bash -c "echo $pod > /usr/share/nginx/html/index.html"
 done
 ​
 # 验证主页内容
 root@master30:~# curl http://10.224.113.165
 web-7c56dcdb9b-5r9jm
 root@master30:~# curl http://10.224.113.164
 web-7c56dcdb9b-84bhr
 root@master30:~# curl http://10.224.19.38
 web-7c56dcdb9b-nswcm
 ​
 # 创建Service
 root@master30:~# kubectl expose deployment web --port=80
 root@master30:~# kubectl get svc web 
 NAME   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
 web    ClusterIP   10.103.143.120   <none>        80/TCP    2m38s
2. 查看 IPVS 规则

在任意集群节点执行,这里在master30节点执行,查看 kube-proxy 创建的 IPVS 虚拟服务:

 # 列出虚拟服务器
 root@master30:~# ipvsadm -Lnt 10.103.143.120:80

看到类似如下的记录,其中 10.97.240.1 是 Service IP,80 是 Service Port:

 Prot LocalAddress:Port Scheduler Flags
   -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
 TCP  10.103.143.120:80 rr
   -> 10.224.19.38:80              Masq    1      0          0         
   -> 10.224.113.164:80            Masq    1      0          0         
   -> 10.224.113.165:80            Masq    1      0          0 

结论:IPVS 已成功为 Service 创建了虚拟服务,并绑定了所有后端 Pod。

3. 验证负载均衡效果

在集群内或外部访问 Service,观察流量是否分发到不同 Pod:

 # 连续访问 60 次
 root@master30:~# for i in {1..60}; do curl -s 10.103.143.120; done| sort | uniq -c
      20 web-7c56dcdb9b-5r9jm
      20 web-7c56dcdb9b-84bhr
      20 web-7c56dcdb9b-nswcm

预期输出:会看到不同的 Pod 名称各自出现20次,证明 IPVS 轮询(rr)算法生效。

调度算法选择

  • rr(轮询):简单均衡,节点配置一致时使用

  • wrr(加权轮询):最推荐,适合通用业务、Web、API、网关。

wrr 权重来源:1. 与 Pod 的 resources.requests.cpu/memory 成正比;2. ipvsadm命令临时设置,只适合临时测试。

  • lc(最少连接):适合长连接、WebSocket、游戏服务

  • sh(源地址哈希):配合 Service sessionAffinity: ClientIP 使用

更改调度算法:

 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

最佳配置

 apiVersion: kubeproxy.config.k8s.io/v1alpha1
 kind: KubeProxyConfiguration
 mode: ipvs
 ​
 # ========== IPVS 核心调优 ==========
 ipvs:
   # 调度算法(生产推荐:rr / wrr / lc 三选一)
   scheduler: "rr"
 ​
   # 会话保持时间(秒),0 表示关闭
   tcpTimeout: 900
   tcpFinTimeout: 120
   udpTimeout: 300
 ​
   # 排除 127.0.0.1 负载均衡(必须开启)
   excludeCIDRs:
   - "127.0.0.1/32"
 ​
   # 严格arp,避免集群访问异常(必须开启)
   strictARP: true
 ​
 # ========== 性能优化 ==========
 # 最大打开文件数(高并发必调)
 oomScoreAdj: -999
 ​
 # Iptables 规则优化
 iptables:
   minSyncPeriod: 5s
   syncPeriod: 30s
 ​
 # 日志级别
 logging:
   format: text
   verbosity: 2

iptables 模式

工作原理

kube-proxy 默认工作模式是 iptables 模式。

kube-proxy 监听 Service 和 Endpoint 变化,在宿主机内核中动态生成 iptables 规则链(KUBE-SERVICES、KUBE-SEP-XXX 等)。当流量进入宿主机时,通过 netfilter 框架逐条匹配 iptables 规则,实现 DNAT(目标地址转换)和负载均衡。

核心缺点

  • 每增加一个 Service 或 Endpoint,都会新增/修改 iptables 规则。

  • 当服务数量超过 1000 时,规则链膨胀,CPU 占用飙升,延迟显著增加。

验证原理

前置准备
  1. kube-proxy 已切换为 iptables 模式。

验证步骤
1. 创建测试资源

使用ipvs实验环境准备的资源。

2. 查看防火墙规则

步骤1:获取防火墙规则。

在任意集群节点执行,这里在master30节点执行,保存防火墙规则:

 root@master30:~# iptables-save > iptables.list

步骤2:分析 svc-nginx 规则。

 root@master30:~# cat iptables.list |grep 10.103.143.120
 -A KUBE-SERVICES -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-SVC-7D76YWGERGEPC4GC
 -A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ

分析防火墙规则:

  • 第一条防火墙规则:在 natKUBE-SERVICES 服务总入口链中,匹配目的 IP 为 10.103.143.120/32、协议 TCP、目的端口 80 的报文,通过注释标识为 services/web 的 ClusterIP 服务,并跳转至该服务专属调度链 KUBE-SVC-7D76YWGERGEPC4GC

  • 第二条防火墙规则:则在服务专属链 KUBE-SVC-7D76YWGERGEPC4GC 中,匹配源地址非 Pod 网段 10.224.0.0/16、目的 IP 10.103.143.120/32、协议 TCP、目的端口 80 的报文,注释标识为 services/web cluster IP,并跳转至 KUBE-MARK-MASQ 进行 SNAT 标记

步骤3:进一步追踪链路KUBE-SVC-7D76YWGERGEPC4GC

 root@master30:~# cat iptables.list | grep KUBE-SVC-7D76YWGERGEPC4GC
 :KUBE-SVC-7D76YWGERGEPC4GC - [0:0]
 -A KUBE-SERVICES -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-SVC-7D76YWGERGEPC4GC
 -A KUBE-SVC-7D76YWGERGEPC4GC ! -s 10.224.0.0/16 -d 10.103.143.120/32 -p tcp -m comment --comment "services/web cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
 -A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.113.164:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-HYZM2VM7RCC7M2HX
 -A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.113.165:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-CHPZVFCOFL4YLCYM
 -A KUBE-SVC-7D76YWGERGEPC4GC -m comment --comment "services/web -> 10.224.19.38:80" -j KUBE-SEP-HM7RLUR2RXSQ3D6F

分析防火墙规则:

  • 第一条防火墙规则:已分析过。

  • 第二条防火墙规则:已分析过。

  • 第三条防火墙规则:在服务调度链 KUBE-SVC-7D76YWGERGEPC4GC 中,对所有协议与地址的访问报文,以 1/3 随机概率 将其跳转至后端端点链 KUBE-SEP-HYZM2VM7RCC7M2HX,注释标识该端点对应 services/web 服务后端 10.224.113.164:80。

  • 第四条防火墙规则:在服务调度链 KUBE-SVC-7D76YWGERGEPC4GC 中,对未被前序规则匹配的报文,以 剩余流量 1/2 随机概率(总概率 1/3)跳转至后端端点链 KUBE-SEP-CHPZVFCOFL4YLCYM,注释标识对应 services/web 后端 10.224.113.165:80。

  • 第五条防火墙规则:在服务调度链 KUBE-SVC-7D76YWGERGEPC4GC 中,对未被前序概率规则匹配的报文,无条件跳转(总概率 1/3)至后端端点链 KUBE-SEP-HM7RLUR2RXSQ3D6F,注释标识对应 services/web 后端 10.224.19.38:80。

步骤4:进一步追踪上面最后三条规则目标链路。

 root@master30:~# cat iptables.list | grep -e KUBE-SEP-HYZM2VM7RCC7M2HX -e KUBE-SEP-CHPZVFCOFL4YLCYM -e KUBE-SEP-HM7RLUR2RXSQ3D6F | grep DNAT
 -A KUBE-SEP-CHPZVFCOFL4YLCYM -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.113.165:80
 -A KUBE-SEP-HM7RLUR2RXSQ3D6F -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.19.38:80
 -A KUBE-SEP-HYZM2VM7RCC7M2HX -p tcp -m comment --comment "services/web" -m tcp -j DNAT --to-destination 10.224.113.164:80

分析防火墙规则:

  • 第一条防火墙规则:在后端端点链 KUBE-SEP-HM7RLUR2RXSQ3D6F 中,匹配TCP 协议报文,注释标识为 services/web 服务,执行DNAT 目标地址转换,将报文目的地址修改为 10.224.19.38:80

  • 第二条防火墙规则:在后端端点链 KUBE-SEP-HYZM2VM7RCC7M2HX 中,匹配TCP 协议报文,注释标识为 services/web 服务,执行DNAT 目标地址转换,将报文目的地址修改为 10.224.113.164:80

  • 第三条防火墙规则:在后端端点链 KUBE-SEP-CHPZVFCOFL4YLCYM 中,匹配TCP 协议报文,注释标识为 services/web 服务,执行DNAT 目标地址转换,将报文目的地址修改为 10.224.113.165:80

环境清理

 root@master30:~# kubectl delete ns services

Kubernetes Ingress

学习参考:Ingress

环境准备

 root@master30:~# kubectl create ns ingress
 root@master30:~# kubectl config set-context --current --namespace ingress

Ingress 介绍

Ingress 可为 Service 提供外部可访问的 URL、对其流量作负载均衡、 终止 SSL/TLS,以及基于名称的虚拟托管等能力。 Ingress 控制器 负责完成 Ingress 的工作,通常会使用某个负载均衡器、边缘路由器或其他前端来帮助处理流量。

下面是 Ingress 的一个简单示例,可将所有流量都发送到同一 Service:

Ingress 不会随意公开端口或协议, 将 HTTP 和 HTTPS 以外的服务开放到 Internet 时,通常使用 Service.Type=NodePortService.Type=LoadBalancer 类型的 Service。

Ingress 控制器

为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。

与作为 kube-controller-manager 可执行文件的一部分运行的其他类型的控制器不同, Ingress 控制器不是随集群自动启动的。 基于此页面,你可选择最适合你的集群的 ingress 控制器实现。

Kubernetes 作为一个项目,目前支持和维护 AWSGCENginx Ingress 控制器。

ingress-nginx 工作流程

本次实验使用 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 部署前提

项目地址: kubernetes/ingress-nginx

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

  2. 你必须部署一个 Ingress 控制器 才能满足 Ingress 的要求,例如 ingress-nginx

ingress-nginx 版本

Supported Ingress-NGINX version k8s supported version Alpine Version Nginx Version Helm Chart Version
🔄 v1.11.2 1.30, 1.29, 1.28, 1.27, 1.26 3.20.0 1.25.5 4.11.2
🔄 v1.11.1 1.30, 1.29, 1.28, 1.27, 1.26 3.20.0 1.25.5 4.11.1
🔄 v1.11.0 1.30, 1.29, 1.28, 1.27, 1.26 3.20.0 1.25.5 4.11.0
🔄 v1.10.2 1.30, 1.29, 1.28, 1.27, 1.26 3.20.0 1.25.5 4.10.2
🔄 v1.10.1 1.30, 1.29, 1.28, 1.27, 1.26 3.19.1 1.25.3 4.10.1
🔄 v1.10.0 1.29, 1.28, 1.27, 1.26 3.19.1 1.25.3 4.10.0
...

ingress-nginx 部署

部署方法参考:https://kubernetes.github.io/ingress-nginx/deploy/

 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,通常用于:设置环境变量的值、设置命令行参数、创建配置文件。

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

  2. 创建ingress-controller。前面提到过,ingress-controller的作用是将新加入的Ingress进行转化为httpd的配置

 # 查看资源使用的镜像
 root@master30:~# 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:~# sed -i 's/registry.k8s.io/hub.laoma.cloud/g' ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
 root@master30:~# sed -i 's/@sha256.*//' ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml
 ​
 # 创建 Ingress
 root@master30:~# kubectl apply -f ingress-nginx-controller-v1.11.2/deploy/static/provider/cloud/deploy.yaml

注意registry.k8s.io 镜像仓库中镜像无法直接下载,需要配置加速。我们也可以从 这里 获取。

查看部署的资源

 root@master30:~# kubectl get all -n ingress-nginx 
 NAME                                           READY   STATUS      RESTARTS   AGE
 pod/ingress-nginx-admission-create-xhvp9       0/1     Completed   0          8m14s
 pod/ingress-nginx-admission-patch-j47tm        0/1     Completed   2          8m14s
 pod/ingress-nginx-controller-596db54d7-cgbdd   1/1     Running     0          8m14s
 ​
 NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
 service/ingress-nginx-controller             LoadBalancer   10.107.255.83   10.1.8.40     80:30973/TCP,443:32344/TCP   14s
 service/ingress-nginx-controller-admission   ClusterIP      10.106.37.158   <none>        443/TCP                      8m15s
 ​
 NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
 deployment.apps/ingress-nginx-controller   1/1     1            1           8m14s
 ​
 NAME                                                 DESIRED   CURRENT   READY   AGE
 replicaset.apps/ingress-nginx-controller-596db54d7   1         1         1       8m14s
 ​
 NAME                                       COMPLETIONS   DURATION   AGE
 job.batch/ingress-nginx-admission-create   1/1           2m26s      8m14s
 job.batch/ingress-nginx-admission-patch    1/1           2m28s      8m14s

Ingress 规则实践

Ingress 规则说明

规则示例
  • 示例1: 没有rule的Ingress规则

指定一个没有rule的defaultBackend的方式,所有发送给该IP的流量都被转发到了defaultBackend 所列的Kubernetes service上。

 yaml 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 标签

Ingress 规则实践

以下主要讲解生产环境 Ingress-Nginx 常用规则。

环境准备
站点 webapp01
 # 创建Deployment
 root@master30:~# kubectl create deployment webapp01 --image=hub.laoma.cloud/library/httpd --replicas=2
 ​
 # 查看pod标签
 root@master30:~# kubectl get pod -L app 
 NAME                        READY   STATUS    RESTARTS   AGE
 webapp01-57b8567f7c-kq56c   1/1     Running   0          18s
 webapp01-57b8567f7c-vzqj7   1/1     Running   0          18s
 ​
 # 准备pod主页内容
 root@master30:~# kubectl exec -it webapp01-57b8567f7c-kq56c -- bash -c "echo hello webapp01 pod1 > htdocs/index.html"
 ​
 root@master30:~# kubectl exec -it webapp01-57b8567f7c-vzqj7 -- bash -c "echo hello webapp01 pod2 > htdocs/index.html"
 ​
 root@master30:~# kubectl expose deployment webapp01 --port=80 --target-port=80
站点 webapp02
 # 创建Deployment
 root@master30:~# kubectl create deployment webapp02 --image=hub.laoma.cloud/library/httpd --replicas=2
 ​
 # 查看pod标签
 root@master30:~# kubectl get pod |grep webapp02
 webapp02-598c447b5b-4j76n   1/1     Running   0          18s
 webapp02-598c447b5b-dddwh   1/1     Running   0          18s
 ​
 # 准备pod主页内容
 root@master30:~# kubectl exec -it webapp02-598c447b5b-4j76n -- bash -c "
 echo hello webapp02 pod1 > htdocs/index.html
 mkdir htdocs/games
 echo hello webapp02 game1 > htdocs/games/index.html"
 ​
 root@master30:~# kubectl exec -it webapp02-598c447b5b-dddwh -- bash -c "
 echo hello webapp02 pod2 > htdocs/index.html
 mkdir htdocs/games
 echo hello webapp02 game2 > htdocs/games/index.html"
 ​
 root@master30:~# kubectl expose deployment webapp02 --port=80 --target-port=80
一、基础通用规则
1. 多域名虚拟主机(多 host 路由)

场景:多个域名转发不同业务

 root@master30:~# vim ingress.yaml
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: multi-host-ingress
 spec:
   # 这里一定要指定ingressClassName为nginx
   ingressClassName: nginx
   rules:
   - host: webapp01.laoma.cloud
     http:
       paths:
       - path: /
         pathType: Prefix
         backend:
           service: 
             name: webapp01
             port: 
               number: 80
   - host: webapp02.laoma.cloud
     http:
       paths:
       - path: /
         pathType: Prefix
         backend:
           service: 
             name: webapp02
             port: 
               number: 80
 root@master30:~# kubectl apply -f ingress.yaml
 root@master30:~# kubectl describe ingress multi-host-ingress 
 Name:             multi-host-ingress
 Labels:           <none>
 Namespace:        ingress
 Address:          10.1.8.40
 Ingress Class:    nginx
 Default backend:  <default>
 Rules:
   Host                  Path  Backends
   ----                  ----  --------
   webapp01.laoma.cloud  
                         /   webapp01:80 (10.224.113.173:80,10.224.19.36:80)
   webapp02.laoma.cloud  
                         /   webapp02:80 (10.224.113.174:80,10.224.19.44:80)
 Annotations:            <none>
 Events:
   Type    Reason  Age              From                      Message
   ----    ------  ----             ----                      -------
   Normal  Sync    2s (x2 over 8s)  nginx-ingress-controller  Scheduled for sync

访问验证

 # 修改www.laoma.cloud解析,确保客户端能解析该名称
 # 如果有多个域名,建议使用配置DNS-wild匹配
 [root@client ~]# echo '10.1.8.40 webapp01.laoma.cloud' >> /etc/hosts
 [root@client ~]# echo '10.1.8.40 webapp02.laoma.cloud' >> /etc/hosts
 ​
 # 注意: 这里我们直接访问域名,没有加端口号
 [root@client ~]# curl webapp01.laoma.cloud
 hello webapp01 pod1
 [root@client ~]# curl webapp01.laoma.cloud
 hello webapp01 pod2
 [root@client ~]# curl webapp02.laoma.cloud
 hello webapp02 pod1
 [root@client ~]# curl webapp02.laoma.cloud
 hello webapp02 pod2

删除 ingress

 root@master30:~# kubectl delete ingress multi-host-ingress 
2. 同一域名多路径路由(path 分流)

场景:一个域名,不同路径转发给不同微服务

 root@master30:~# vim ingress.yaml
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: multi-path-ingress
 spec:
   ingressClassName: nginx
   rules:
   - host: www.laoma.cloud
     http:
       paths:
       - path: /
         pathType: Prefix
         backend:
           service: 
             name: webapp01
             port: 
               number: 80
       - path: /games
         pathType: Prefix
         backend:
           service: 
             name: webapp02
             port: 
               number: 80
 root@master30:~# kubectl apply -f ingress.yaml
 root@master30:~# kubectl describe ingress multi-path-ingress 
 Name:             myingress-3
 Labels:           <none>
 Namespace:        ingress
 Address:          10.1.8.40
 Ingress Class:    nginx
 Default backend:  <default>
 Rules:
   Host             Path  Backends
   ----             ----  --------
   www.laoma.cloud  
                    /        webapp01:80 (10.224.113.173:80,10.224.19.36:80)
                    /games   webapp02:80 (10.224.113.174:80,10.224.19.44:80)
 Annotations:       <none>
 Events:
   Type    Reason  Age              From                      Message
   ----    ------  ----             ----                      -------
   Normal  Sync    2s (x2 over 8s)  nginx-ingress-controller  Scheduled for sync

访问验证

 # 修改www.laoma.cloud解析,确保客户端能解析该名称
 # 如果有多个域名,建议使用配置DNS-wild匹配
 [root@client ~]# echo '10.1.8.40 www.laoma.cloud' >> /etc/hosts
 ​
 # 注意: 这里我们直接访问域名,没有加端口号
 [root@client ~]# curl www.laoma.cloud
 hello webapp01 pod1
 [root@client ~]# curl www.laoma.cloud
 hello webapp01 pod2
 [root@client ~]# curl www.laoma.cloud/games/
 hello webapp02 game1
 [root@client ~]# curl www.laoma.cloud/games/
 hello webapp02 game2

删除 ingress

 root@master30:~# kubectl delete ingress multi-path-ingress 
二、生产核心:路径重写
3. 路径裁剪(等价 Nginx proxy_pass /

场景:前端访问 /api/xxx,后端实际只识别 /xxx,剥离 /api 前缀

 root@master30:~# vim ingress.yaml
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: multi-path-ingress
   annotations:
     nginx.ingress.kubernetes.io/use-regex: "true"
     nginx.ingress.kubernetes.io/rewrite-target: /$1
 spec:
   ingressClassName: nginx
   rules:
   - host: www.laoma.cloud
     http:
       paths:
       - path: /webapp01/(.*)
         pathType: ImplementationSpecific
         backend:
           service: 
             name: webapp01
             port: 
               number: 80
       - path: /webapp02/(.*)
         pathType: ImplementationSpecific
         backend:
           service: 
             name: webapp02
             port: 
               number: 80
 root@master30:~# kubectl apply -f ingress.yaml
 root@master30:~# kubectl describe ingress multi-path-ingress 
 Name:             multi-path-ingress
 Labels:           <none>
 Namespace:        ingress
 Address:          10.1.8.40
 Ingress Class:    nginx
 Default backend:  <default>
 Rules:
   Host             Path  Backends
   ----             ----  --------
   www.laoma.cloud  
                    /webapp01/(.*)   webapp01:80 (10.224.113.173:80,10.224.19.36:80)
                    /webapp02/(.*)   webapp02:80 (10.224.113.174:80,10.224.19.44:80)
 Annotations:       nginx.ingress.kubernetes.io/rewrite-target: /$1
                    nginx.ingress.kubernetes.io/use-regex: true
 Events:
   Type    Reason  Age               From                      Message
   ----    ------  ----              ----                      -------
   Normal  Sync    0s (x4 over 13m)  nginx-ingress-controller  Scheduled for sync

访问验证

 [root@client ~]# curl www.laoma.cloud/webapp01/
 hello webapp01 pod1
 [root@client ~]# curl www.laoma.cloud/webapp01/
 hello webapp01 pod1
 [root@client ~]# curl www.laoma.cloud/webapp02/
 hello webapp02 pod1
 [root@client ~]# curl www.laoma.cloud/webapp02/
 hello webapp02 pod1

删除 ingress

 root@master30:~# kubectl delete ingress multi-path-ingress 
三、HTTPS 强制 & TLS 证书
4. 绑定 TLS 证书 + 强制 HTTPS

环境准备

 root@master30:~# openssl genrsa -out www.key 2048  
 root@master30:~# openssl req -new -key www.key -out www.csr -subj "/C=CN/ST=JS/L=NJ/O=LM/OU=DEVOPS/CN=www.laoma.cloud/emailAddress=webadmin@laoma.cloud" 
 root@master30:~# openssl x509 -req -days 3650 -in www.csr -signkey www.key -out www.crt
 root@master30:~# kubectl create secret tls www-tls --cert=./www.crt --key=./www.key

生产标配:80 自动跳转 443,加密访问

 root@master30:~# vim ingress.yaml
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 metadata:
   name: tls-ingress
   annotations:
     nginx.ingress.kubernetes.io/ssl-redirect: "true"
     nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
 spec:
   ingressClassName: nginx
   tls:
   - hosts:
       - www.laoma.cloud
     secretName: www-tls
   rules:
   - host: www.laoma.cloud
     http:
       paths:
       - path: /
         pathType: Prefix
         backend:
           service:
             name: webapp01
             port: 
               number: 80
 root@master30:~# kubectl apply -f ingress.yaml
 root@master30:~# kubectl describe ingress tls-ingress 
 Name:             tls-ingress
 Labels:           <none>
 Namespace:        ingress
 Address:          
 Ingress Class:    nginx
 Default backend:  <default>
 TLS:
   www-tls terminates www.laoma.cloud
 Rules:
   Host             Path  Backends
   ----             ----  --------
   www.laoma.cloud  
                    /   webapp01:80 (10.224.19.54:80,10.224.19.56:80)
 Annotations:       nginx.ingress.kubernetes.io/force-ssl-redirect: true
                    nginx.ingress.kubernetes.io/ssl-redirect: true
 Events:
   Type    Reason  Age   From                      Message
   ----    ------  ----  ----                      -------
   Normal  Sync    11s   nginx-ingress-controller  Scheduled for sync

访问验证

 # 使用 -L 跟随 301/302/307/308 所有重定向
 [root@client ~]# curl -Lk http://www.laoma.cloud/
 hello webapp01 pod1
 [root@client ~]# curl -Lk http://www.laoma.cloud/
 hello webapp01 pod2
 ​
 # 访问https站点
 [root@client ~]# curl -k https://www.laoma.cloud/
 hello webapp01 pod1
 [root@client ~]# curl -k https://www.laoma.cloud/
 hello webapp01 pod2

删除 ingress

 root@master30:~# kubectl delete ingress tls-ingress
四、限流、防刷、超时优化
5. 连接数限流、单IP限速
 annotations:
   # 单IP最大并发连接
   nginx.ingress.kubernetes.io/limit-connections: "50"
   # 单IP每秒请求数
   nginx.ingress.kubernetes.io/limit-rps: "20"
6. 自定义超时时间
 annotations:
   nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"
   nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
   nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
五、跨域配置(前后端分离必备)
7. 全局跨域放行
 annotations:
   nginx.ingress.kubernetes.io/enable-cors: "true"
   nginx.ingress.kubernetes.io/cors-allow-origin: "*"
   nginx.ingress.kubernetes.io/cors-allow-methods: "GET,POST,PUT,DELETE,OPTIONS"
六、白名单访问控制(内网 / 后台系统)
8. 限制指定IP段访问

场景:管理后台、内部系统,只允许公司内网IP

 annotations:
   # 只放行 10.1.8.0/24 网段
   nginx.ingress.kubernetes.io/whitelist-source-range: "10.1.8.0/24,127.0.0.1/32"
七、静态资源缓存、请求头透传
9. 透传真实客户端IP
 annotations:
   nginx.ingress.kubernetes.io/x-forwarded-for: "true"
   nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "10.0.0.0/8"
10. 静态资源缓存优化
 annotations:
   nginx.ingress.kubernetes.io/proxy-cache: "true"
   nginx.ingress.kubernetes.io/proxy-cache-valid: "200 302 10m"
八、灰度 / 权重分流(金丝雀发布)
11. 权重流量拆分

场景:线上灰度,90%流量走稳定版,10%走测试版

 annotations:
   nginx.ingress.kubernetes.io/canary: "true"
   nginx.ingress.kubernetes.io/canary-weight: "10"
九、错误页面、自定义配置
12. 自定义后端错误页、关闭目录浏览
 annotations:
   nginx.ingress.kubernetes.io/custom-http-errors: "404,500,502,503"
十、生产高频注解 速查表
注解 作用
force-ssl-redirect: true 80 强制跳转 HTTPS
rewrite-target 路径重写、裁剪路由前缀
whitelist-source-range IP白名单
limit-rps / limit-connections 防CC、限流
enable-cors 前后端跨域
proxy-read-timeout 解决接口超时断开
x-forwarded-for 后端获取真实客户端IP

环境清理

 root@master30:~# kubectl delete ns ingress
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐