# Kubernetes(K8s)笔记Day12 :K8s 七层代理(Ingress 和 Ingress Controller)

一、回顾四层负载均衡器 Service

Service 自带四层(L4)负载均衡能力,它会维护一个后端 Pod 列表(通过 Endpoints 或 EndpointSlice),并按照轮询等策略将请求分发到多个 Pod,解决了 Pod IP 地址不固定的问题,并且同时通过 NodePort 方式将服务暴露到集群之外,从而实现了如下的数据流:

复制代码
客户端请求 → Node节点的IP:端口 → Service的IP:端口 → Pod的IP:端口

Service 四层负载均衡存在的问题

问题 说明
端口管理混乱 一个 Service 需要对应一个宿主机端口,十个 Service 需要十个不同端口,客户端无法管理这么多端口
无法智能路由 Service 只能做四层转发(L4),无法识别域名、URL 路径等 HTTP 层信息,无法实现基于内容的智能路由

Service vs Ingress 对比

对比维度 Service(主要指 LoadBalancer 类型) Ingress
工作层级 第四层(L4),基于 IP 地址、端口号分发,不解析 HTTP 协议内容 第七层(L7),工作在应用层,能识别域名、URL 路径、HTTP 头等
核心能力 流量分发与内部服务发现。支持轮询等简单的 L4 负载均衡策略 高级路由 。支持基于域名、路径的智能路由,如将 a.com/web 发往 Service A,a.com/api 发往 Service B
入口方式 每个 LoadBalancer Service 通常都有一个独立的公网 IP 地址 多个 Service 可共享一个公网 IP(Ingress Controller 的 IP),通过不同规则区分
协议支持 支持广泛的 TCP/UDP 协议,适合数据库、消息队列等非 HTTP 应用 原生支持 HTTP/HTTPS,对 gRPC 的支持取决于具体的 Controller 实现

二、Ingress 七层负载均衡介绍

2.1 Ingress 介绍

Ingress 是 Kubernetes 中专门用来管理集群外部流量如何访问集群内部服务的 API 资源(规则文件)。它工作在第七层(应用层,HTTP/HTTPS),能够提供比 Service 更智能、更灵活的路由能力。

Ingress 规则是"全集群生效的逻辑规则",它不依赖于具体的物理节点。无论流量从哪个节点进入,只要集群中的 Ingress Controller 正常运行,规则就会执行。你只需维护一份 Ingress 文件,就能为整个集群提供七层路由能力

2.2 Ingress Controller 介绍

Ingress Controller 是一个七层负载均衡调度器 。客户端的请求先到达这个七层负载均衡调度器,由七层负载均衡器再反向代理到后端 Pod。

简单说,Ingress是规则文件,Ingress Controller 是规则的执行者

注意:Ingress Controller 在执行Ingress规则时,我们是观测不到的,它是在后台起作用的

Ingress Controller 是什么

简单说,Ingress Controller 是一个运行在 Kubernetes 集群中的 Pod ,这个 Pod 里运行着一个成熟的七层负载均衡软件(比如 Nginx 或 Traefik)。你可以把它想象成一台"智能路由网关"------所有外部 HTTP/HTTPS 流量进入集群后,第一个接住它们的就是这个 Pod

它是怎么实现"实时更新"的

关键在于它内部有一个 "控制循环(Control Loop)"。这个循环会持续 watch 集群 API Server:

监听:它会时刻盯着 Ingress、Service 和 Endpoint 三类资源的变化。

翻译:一旦发现新规则(比如你创建了一个 Ingress 规则),它立刻把 K8s 的抽象语法,翻译成 Nginx 能理解的具体的 nginx.conf 配置文件。

加载:翻译完成后,它会自动执行 nginx -s reload,让新规则立即生效,整个过程无需手动干预

为什么后端需要加 Service

回忆一下service的特性:service的IP(ClusterIP)是固定的,它的域名(..svc.cluster.local)也是固定的,解析service的域名可以得到service的IP。 service可以通过标签选择器可以访问到带有特定标签的pod,因此访问service就可以固定的访问到特定的pod

因为后端 Pod 的 IP 是动态变化的(比如 Pod 重启或漂移)。如果在 Nginx 的 upstream 里直接写死 Pod IP,Pod 一重启规则就失效了。而 Service(ClusterIP)是固定的,相当于在 Nginx 和 Pod 之间加了一层"静态中间层"。Ingress Controller 只需要把流量转发给 Service 的 ClusterIP,然后由 kube-proxy 完成到实际 Pod 的最终分发

text 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      Ingress Controller 工作机制                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 监听阶段:                                                              │
│     Ingress Controller (Pod) → Watch API Server → 发现新的 Ingress 规则    │
│                              ↓                                              │
│  2. 翻译阶段:                                                              │
│     将 K8s 规则翻译为 → 生成 nginx.conf 配置文件                           │
│                              ↓                                              │
│  3. 生效阶段:                                                              │
│     自动执行 nginx -s reload → 新规则加载完成                              │
│                              ↓                                              │
│  4. 流量转发阶段:                                                          │
│     客户端请求 → Ingress Controller (Nginx) → Service → Pod                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

一句话总结:Ingress 是规则,Ingress Controller 是具体的负载均衡器。

2.3 Ingress 规则文件字段解析

好的,我来帮你解析 kubectl explain Ingress 输出的这些字段:


1、顶层字段(Ingress 资源本身)
字段 类型 必填 说明
apiVersion string API 版本,固定为 networking.k8s.io/v1
kind string 资源类型,固定为 Ingress
metadata Object 元数据,包含 namenamespacelabelsannotations
spec Object 核心部分:定义 Ingress 的路由规则(域名、路径、后端服务等)
status Object 只读字段:由 Ingress Controller 自动填充,记录当前 Ingress 的负载均衡器地址、路由状态等信息

注意status 字段由系统自动维护,用户无需(也无法)手动配置。


2、spec 字段(Ingress 核心配置)
字段 类型 必填 说明
defaultBackend Object 默认后端 :当请求不匹配任何 rules 中的规则时,转发到该后端。如果未设置,不匹配的请求将由 Ingress Controller 自行处理(通常返回 404)。
ingressClassName string 指定 Ingress Controller :值为集群中已存在的 IngressClass 名称(如 nginx),告诉集群由哪个 Ingress Controller 来执行该规则。 ⚠️ 这是 新版本推荐写法 ,替代旧的 kubernetes.io/ingress.class 注解。
rules \[\]Object 路由规则列表 :定义具体的域名、路径与后端服务的映射关系。如果没有配置 rules,则必须配置 defaultBackend
tls \[\]Object HTTPS 配置:定义 TLS 证书和对应的域名。配置后,Ingress Controller 会为该域名提供 HTTPS 服务(通常使用 443 端口)。

3、rules 子字段(路由规则)

rules 是一个数组,每个元素包含以下字段:

字段 类型 说明
host string 域名匹配 :指定需要匹配的域名(如 nginx.jx.com)。如果留空,则匹配所有域名的请求。
http Object HTTP 路由配置:定义该域名下的路径匹配规则和对应的后端服务。

4、http 子字段(路径匹配)
字段 类型 说明
paths \[\]Object 路径规则列表:定义该域名下具体的路径匹配规则和对应的后端服务。请求按顺序匹配,命中第一个即转发。
paths 中的每个元素包含:
字段 类型 说明
path string 匹配的路径 :如 //api/product 等。配合 pathType 决定匹配方式。
pathType string 路径匹配类型 : • Prefix:前缀匹配(/api 匹配 /api/api/v1/api/v1/user) • Exact:精确匹配(必须完全一致) • ImplementationSpecific:由 Ingress Controller 自行解释(如 Nginx 支持正则表达式)
backend Object 后端服务:指定将请求转发到哪个 Service 的哪个端口。
backend 包含:
字段 说明
service.name 后端 Service 的名称(必须与 Ingress 在同一命名空间)
service.port.numberservice.port.name 后端 Service 的端口号或端口名称

5、tls 子字段(HTTPS 配置)

tls 是一个数组,每个元素包含:

字段 说明
hosts \[\]string:需要启用 HTTPS 的域名列表
secretName 存储 TLS 证书和私钥的 Secret 名称(类型必须为 kubernetes.io/tls

6、defaultBackend 字段(默认后端)

当请求不匹配任何 rules 时,转发到该后端。结构同 backend

字段 说明
service.name 默认后端的 Service 名称
service.port.numberservice.port.name 默认后端的 Service 端口
范例

假设你有一个域名 myapp.com,背后有三个微服务:

前端服务(frontend-svc):处理主页面,路径为 /

API 服务(api-svc):处理业务接口,路径为 /api

后台管理(admin-svc):处理管理后台,路径为 /admin

通过 Ingress 实现同一个域名、不同路径,路由到不同 Service。

bash 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress              # Ingress 名称
spec:
  ingressClassName: nginx          # 使用 Nginx Ingress Controller(必须与集群中安装的 Controller 一致)

  rules:
  - host: myapp.com                # 域名:只有访问 myapp.com 的请求才匹配
    http:
      paths:
      # ---------- 规则1:根路径 → 前端服务 ----------
      - path: /                    # 匹配所有以 "/" 开头的请求
        pathType: Prefix           # 前缀匹配
        backend:
          service:
            name: frontend-svc     # 后端 Service 名称
            port:
              number: 80           # 后端 Service 端口

      # ---------- 规则2:/api/* → API 服务 ----------
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-svc
            port:
              number: 80

      # ---------- 规则3:/admin/* → 后台管理服务 ----------
      - path: /admin
        pathType: Prefix
        backend:
          service:
            name: admin-svc
            port:
              number: 80

快速查询技巧

如果你想查看某个字段的更详细定义,可以继续使用 kubectl explain

bash 复制代码
# 查看 rules 的详细结构
kubectl explain Ingress.spec.rules

# 查看 http 的详细结构
kubectl explain Ingress.spec.rules.http

# 查看 backend 的详细结构
kubectl explain Ingress.spec.rules.http.paths.backend

三、安装 Ingress Controller

3.1 下载和安装

bash 复制代码
# 创建目录
[root@hd1 ~]# mkdir ingress-controller
[root@hd1 ~]# cd ingress-controller/

# 获取 ingress-nginx,本次案例使用的是 1.8.1 版本
# 下载前建议先查看兼容性:https://github.com/kubernetes/ingress-nginx/releases
[root@hd1 ingress-controller]# wget https://github.com/kubernetes/ingress-nginx/archive/refs/tags/controller-v1.8.1.tar.gz

注意,下载的 Ingress Controller 版本要和我们的k8s集群版本相对应

bash 复制代码
# 解压缩软件包
[root@hd1 ~]# tar xf controller-v1.8.1.tar.gz
[root@hd1 ~]# cd ingress-nginx-controller-v1.8.1/deploy/static/provider/cloud/

[root@hd1 cloud]# ls
deploy.yaml  kustomization.yaml

3.2 修改镜像源(国内加速)

由于默认镜像源可能无法访问,需要修改为国内镜像源:

bash 复制代码
[root@hd1 cloud]# cat deploy.yaml | grep -n image
441:        image: registry.cn-shenzhen.aliyuncs.com/xiaohh-docker/ingress-nginx-controller:v1.8.1
442:        imagePullPolicy: IfNotPresent
538:        image: registry.cn-shenzhen.aliyuncs.com/xiaohh-docker/ingress-nginx-kube-webhook-certgen:v20230407
539:        imagePullPolicy: IfNotPresent
587:        image: registry.cn-shenzhen.aliyuncs.com/xiaohh-docker/ingress-nginx-kube-webhook-certgen:v20230407
588:        imagePullPolicy: IfNotPresent

如果在应用文件时无法正常使用,可参考下文踩坑记录

bash 复制代码
# 部署 Ingress Controller
[root@k8s-master01 cloud]# kubectl apply -f deploy.yaml

# 查看 Pod 状态
[root@hd1 cloud]# kubectl get pod -n ingress-nginx
NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-tm8rz       0/1     Completed   0          4m22s
ingress-nginx-admission-patch-pmqzx        0/1     Completed   1          4m22s
ingress-nginx-controller-ccb6c97f4-zn97f   1/1     Running     0          4m22s

# 查看 Service
[root@k8s-master01 cloud]# kubectl -n ingress-nginx get svc
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.10.103.132   <pending>     80:31502/TCP,443:31020/TCP   96s
ingress-nginx-controller-admission   ClusterIP      10.10.227.21    <none>        443/TCP                      96s

注意 :如果 EXTERNAL-IP 显示 <pending>,说明当前环境不支持 LoadBalancer 类型。对于非公有云环境(如本地虚拟机、私有云),需要修改 Service 类型为 NodePort。

bash 复制代码
# 修改 ingress-nginx-controller 服务的类型为 NodePort
[root@h1 cloud]# kubectl edit svc ingress-nginx-controller -n ingress-nginx
# 将 type: LoadBalancer 改为 type: NodePort

修改后的 Service 配置示例:

yaml 复制代码
spec:
  type: NodePort  # 原来是 LoadBalancer,改为 NodePort 
  ports:
  - name: http
    port: 80
    targetPort: 80
    nodePort: 31875  # 随机分配,也可手动指定
  - name: https
    port: 443
    targetPort: 443
    nodePort: 32463

再次查看,确认已经变成 NodePort 即可

bash 复制代码
[root@hd1 cloud]# kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.96.132.197   <none>        80:31886/TCP,443:31662/TCP   6m2s
ingress-nginx-controller-admission   ClusterIP   10.96.236.12    <none>        443/TCP                      6m1s
踩坑记录

创建 Ingress Controller 的时候,出现了问题

bash 复制代码
[root@hd1 cloud]# kubectl apply -f deploy.yaml

[root@hd1 cloud]# kubectl -n ingress-nginx get pod
NAME                                       READY   STATUS              RESTARTS   AGE
ingress-nginx-admission-create-fv6jr       0/1     ImagePullBackOff    0          85s
ingress-nginx-admission-patch-frrvs        0/1     ImagePullBackOff    0          85s
ingress-nginx-controller-ccb6c97f4-pjd8z   0/1     ContainerCreating   0          85s

这里第三个pod一直是ContainerCreating状态。查看详细信息:

bash 复制代码
[root@hd1 cloud]# kubectl describe pod ingress-nginx-controller-ccb6c97f4-pjd8z  -n ingress-nginx
Events:
  Type     Reason       Age                 From               Message
  ----     ------       ----                ----               -------
  Normal   Scheduled    115s                default-scheduler  Successfully assigned ingress-nginx/ingress-nginx-controller-ccb6c97f4-pjd8z to hd2
  Warning  FailedMount  51s (x8 over 115s)  kubelet            MountVolume.SetUp failed for volume "webhook-cert" : secret "ingress-nginx-admission" not found
Error from server (NotFound): pods "get" not found

Pod 无法启动不是因为镜像拉取失败(虽然那两个 Admission Pod 确实失败了),而是因为 ingress-nginx-controller 缺少了一个名为 ingress-nginx-admission 的 Secret,这个 Secret 本应由 admission-create 这个 Job 在启动时生成,但由于它自己也没启动成功(可能卡在 ContainerCreating),导致依赖链断裂

重启之后:

bash 复制代码
# 1. 删除当前的 Ingress Controller 部署
[root@hd1 cloud]# kubectl delete -f deploy.yaml

# 2. 等待几秒后,重新部署
[root@hd1 cloud]# kubectl apply -f deploy.yaml

依旧不行。

反复试错不下1十遍,终于找到了解决办法!按步骤照做!

步骤 1:清除掉之前的数据

bash 复制代码
[root@hd1 cloud]# kubectl apply -f deploy.yaml

步骤 2:生成一对匹配的 TLS 证书

bash 复制代码
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -keyout ingress-nginx-admission.key \
  -out ingress-nginx-admission.crt \
  -subj "/CN=ingress-nginx-admission.default.svc"

步骤 3:手动创建命名空间(如果已存在则跳过)

bash 复制代码
kubectl create namespace ingress-nginx --dry-run=client -o yaml | kubectl apply -f -

步骤 4:用这对文件创建 Secret

bash 复制代码
kubectl create secret tls ingress-nginx-admission -n ingress-nginx \
  --key ingress-nginx-admission.key \
  --cert ingress-nginx-admission.crt

步骤 5:应用

bash 复制代码
[root@hd1 cloud]# kubectl apply -f deploy.yaml

#查看(需要等待一小会儿)
[root@hd1 cloud]# kubectl -n ingress-nginx get pod
NAME                                       READY   STATUS             RESTARTS      AGE
ingress-nginx-admission-create-2lq6c       0/1     CrashLoopBackOff   4 (68s ago)   3m10s
ingress-nginx-admission-patch-rv6bk        0/1     CrashLoopBackOff   4 (87s ago)   3m10s
ingress-nginx-controller-ccb6c97f4-qx2zv   1/1     Running            0             3m10s

问题解决

3.3 查看各种资源

bash 复制代码
# 查看集群已经存在的 IngressClass
[root@hd1 cloud]# kubectl get ingressclass
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       6m19s

# 查看 Ingress Controller Service
[root@hd1 cloud]#  kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.96.132.197   <none>        80:31886/TCP,443:31662/TCP   6m32s
ingress-nginx-controller-admission   ClusterIP   10.96.236.12    <none>        443/TCP                      6m31s

# 查看 Ingress Controller Pod
[root@hd1 cloud]# kubectl -n ingress-nginx get pod
NAME                                       READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-ccb6c97f4-qx2zv   1/1     Running   0          18m

四、准备业务环境

4.1 准备一个普通的 Service 和三个对应的 Pod

准备三个 Pod ,作为测试用的后端应用

这个ClusterIP 类型的 Service,作为 3 个 Nginx Pod 的统一访问入口

yaml 复制代码
# nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-deploy
  template:
    metadata:
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: docker.io/library/nginx:latest
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx-deploy
  name: nginx-svc
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx-deploy
  type: ClusterIP      #类型:服务暴露在集群内部
bash 复制代码
# 创建资源
[root@hd1 cloud]# kubectl apply -f nginx.yaml
deployment.apps/nginx-deploy created
service/nginx-svc created

# 查看 Pod
[root@hd1 cloud]# kubectl get pod
NAME                            READY   STATUS    RESTARTS      AGE
nginx-deploy-7576f7f4fd-2gmdp   1/1     Running   0             10s
nginx-deploy-7576f7f4fd-7pnrv   1/1     Running   0             10s
nginx-deploy-7576f7f4fd-lqxc5   1/1     Running   0             10s

# 查看 Service
[root@hd1 cloud]# kubectl get svc
NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)    AGE
kubernetes            ClusterIP      10.96.0.1       <none>          443/TCP    14d
nginx-svc             ClusterIP      10.96.232.179   <none>          80/TCP     34s

4.2 配置 Ingress 规则

yaml 复制代码
# ingress-http.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress     # 资源类型:Ingress。表示这是一个七层路由规则的定义
metadata:
  name: nginx-ingress   #Ingress 资源的名称。此名称在命名空间内必须唯一
spec:            
  ingressClassName: nginx   #指定使用哪个 Ingress Controller 来执行该规则
  rules:          # rules ,规则,是一个数组,可以定义多条路由规则
  - host: nginx.jx.com       # 指定匹配的域名。只有访问该域名的请求才会应用下面的路径规则。若省略此字段,则匹配所有域名的请求
    http:                    # 匹配 HTTP 协议的请求
      paths:
      - backend:             # 指定请求转发到的后端服务(Service)
          service:           
            name: nginx-svc     # 后端 Service 的名称。该 Service 必须与 Ingress 在同一个命名空间
            port:
              number: 80         # 后端 Service 的端口号。Ingress Controller 会将请求转发到该端口
        path: /   # 匹配的路径,这里表示匹配所有以 "/" 开头的请求
        pathType: Prefix

定义了一条七层路由规则,将访问 nginx.jx.com 的 HTTP 请求,转发到后端的 nginx-svc Service 的 80 端口,最终由 3 个 Nginx Pod 提供服务

bash 复制代码
# 创建 Ingress
[root@k8s-master01 ~]# kubectl create -f ingress-http.yaml
ingress.extensions/ingress-http created

# 查看 Ingress
[root@hd1 ~]# kubectl get ingress nginx-ingress
NAME            CLASS   HOSTS          ADDRESS         PORTS   AGE
nginx-ingress   nginx   nginx.jx.com   10.96.132.197   80      4h28m

# 查看 Ingress 详情
[root@hd1 ~]# kubectl get ingress nginx-ingress
NAME            CLASS   HOSTS          ADDRESS         PORTS   AGE
nginx-ingress   nginx   nginx.jx.com   10.96.132.197   80      4h28m

[root@hd1 ~]# kubectl describe ingress nginx-ingress
Name:             nginx-ingress
Labels:           <none>
Namespace:        default
Address:          10.96.132.197
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host          Path  Backends
  ----          ----  --------
  nginx.jx.com  
                /   nginx-svc:80 (10.244.169.85:80,10.244.59.170:80,10.244.59.171:80)
Annotations:    <none>
Events:
  Type    Reason  Age                  From                      Message
  ----    ------  ----                 ----                      -------
  Normal  Sync    4m49s (x3 over 10m)  nginx-ingress-controller  Scheduled for sync
踩坑记录

这个报错是一个典型的 Admission Webhook 证书错误。意思是:当你创建 Ingress 时,Kubernetes API Server 尝试调用 Nginx Ingress Controller 的 validate.nginx.ingress.kubernetes.io Webhook 进行规则校验,但 TLS 握手失败了,具体原因是 tls: internal error

结合我们之前排错的记录,推测问题的根源在于:Webhook 使用的证书(ingress-nginx-admission Secret)与当前 Ingress Controller 的配置不匹配、已损坏或未正确加载。换句话说,这次出错原因就是我们上次排错留下的技术负债

实验好几种方法,都没有作用,只能采用临时避险的方式跳过这一步验证了:

直接禁用 Webhook(临时避险)

bash 复制代码
[root@hd1 ~]# kubectl delete validatingwebhookconfiguration ingress-nginx-admission
validatingwebhookconfiguration.admissionregistration.k8s.io "ingress-nginx-admission" deleted

这里我们直接删除 Webhook ,API Server 不再向 Ingress Controller 发起任何校验请求,也就跳过了这一步的安全审查。

再次创建:

bash 复制代码
[root@hd1 ~]# kubectl create -f ingress-http.yaml
ingress.networking.k8s.io/nginx-ingress created

这次成功了

4.3 测试

Linux 主机测试
bash 复制代码
# 修改 /etc/hosts 文件,将域名解析到集群节点 IP
[root@hd1 cloud]# vim /etc/hosts
192.168.1.12 nginx.jx.com


#查看Ingress Controller 的 NodePort
[root@hd1 provider]# kubectl get svc -n ingress-nginx ingress-nginx-controller
NAME                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   NodePort   10.96.132.197   <none>        80:31886/TCP,443:31662/TCP   118m

# 用域名测试(端口为 Ingress Controller 的 NodePort)
[root@hd1 provider]# curl nginx.jx.com:31886
<h1>Welcome to nginx!</h1>
Windows 主机测试

修改本地 hosts 文件,增加如下一行(IP 为 K8s 任意节点 IP):

复制代码
192.168.1.12  nginx.jx.com

浏览器访问 nginx.jx.com,应能看到 Nginx 欢迎页。

⚠️ 常见问题:测试失败的原因

默认 K8s 中的 Service 有一个属性叫做 externalTrafficPolicy(外部流量策略):

  • Cluster(默认策略):将流量路由到所有端点(节点)
  • Local仅将流量路由到具有相关 Pod 的节点上(若节点上没有相关的 Pod 则丢弃流量),但 保留流量的源 IP

解决方法:如果是本地测试环境,应使用 baremetal (裸金属/本地环境)目录的配置文件

或者修改 ingress-nginx-controller Service 的 externalTrafficPolicyCluster

bash 复制代码
[root@hd1 ~]# kubectl edit svc ingress-nginx-controller -n ingress-nginx

修改内容:

yaml 复制代码
spec:
  clusterIP: 10.96.78.1
  clusterIPs:
  - 10.96.78.1
  externalTrafficPolicy: Cluster  # 将 Local 改成 Cluster 
  internalTrafficPolicy: Cluster

修改后重新测试即可。

而本章节我们所安装的Ingress Controller路径为cloud ,它的默认策略就是Local;但是我们本应该安装到baremetal目录,它没有externalTrafficPolicy这一字段,所以默认就是Cluster

五、三个微服务的七层代理

5.1 案例一:基于请求路径转发不同服务

场景描述

复制代码
假设有一个网站,网站包含多个微服务,每个微服务对应不同的 URI 路径:
- 请求路径 /          → 转发到网站前端服务 (protal)
- 请求路径 /product   → 转发到产品服务 (product)
- 请求路径 /developer → 转发到开发服务 (developer)
步骤 1:删除原有部分资源
bash 复制代码
#删除Ingress
[root@hd1 cloud]# kubectl delete ingress nginx-ingress
ingress.networking.k8s.io "nginx-ingress" deleted
#删除deployment
[root@hd1 cloud]# kubectl delete deployment nginx-deploy
deployment.apps "nginx-deploy" deleted
步骤 2:创建三个服务

服务 1:protal(前端服务)

yaml 复制代码
# service1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app1
  name: app1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
      - image: docker.io/library/nginx:latest
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: app1
  name: protal
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app1
  type: ClusterIP
bash 复制代码
[root@h1 ingress]# kubectl apply -f service1.yaml

服务 2:product(产品服务)

yaml 复制代码
# service2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app2
  name: app2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app2
  template:
    metadata:
      labels:
        app: app2
    spec:
      containers:
      - image: docker.io/library/nginx:latest
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: app2
  name: product
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app2
  type: ClusterIP
bash 复制代码
[root@h1 ingress]# kubectl apply -f service2.yaml

服务 3:developer(开发服务)

yaml 复制代码
# service3.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: app3
  name: app3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app3
  template:
    metadata:
      labels:
        app: app3
    spec:
      containers:
      - image: docker.io/library/nginx:latest
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: app3
  name: developer
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app3
  type: ClusterIP
bash 复制代码
[root@h1 ingress]# kubectl apply -f service3.yaml

至此,我们创建了三个app服务,每一个app服务对应一个service

步骤 3:验证服务
bash 复制代码
# 查看三个 Pod
[root@hd1 ~]# kubectl get pod
NAME                    READY   STATUS    RESTARTS      AGE
app1-7bbc67d96d-svg94   1/1     Running   0             4m21s
app2-6555f4f95f-89www   1/1     Running   0             4m13s
app3-859cb4c9d6-tkm27   1/1     Running   0             7s

# 修改每个 Pod 的网页内容以区分不同服务
[root@h1 ingress]# echo "app1 ----" > index.html
[root@h1 ingress]# kubectl cp index.html app1-7bbc67d96d-svg94:/usr/share/nginx/html/
[root@h1 ingress]# echo "app2 ----" > index.html
[root@h1 ingress]# kubectl cp index.html app2-6555f4f95f-89www:/usr/share/nginx/html/
[root@h1 ingress]# echo "app3 ----" > index.html
[root@h1 ingress]# kubectl cp index.html app3-859cb4c9d6-tkm27:/usr/share/nginx/html/

# 查看三个 Service
[root@hd1 ~]# kubectl get svc -l app
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
developer   ClusterIP   10.96.22.103    <none>        80/TCP    103s
nginx-svc   ClusterIP   10.96.232.179   <none>        80/TCP    19h
product     ClusterIP   10.96.15.185    <none>        80/TCP    5m49s
protal      ClusterIP   10.96.254.188   <none>        80/TCP    5m57s

# 分别访问三个 Service
[root@h1 ingress]# curl 10.96.254.188
app1 ----
[root@h1 ingress]# curl 10.96.15.185 
app2 ----
[root@h1 ingress]# curl 10.96.22.103 
app3 ----
步骤 4:配置 Ingress 规则(基于路径转发)

这个规则会根据用户访问的不同url来指向不同的服务

当用户访问的路径带有product,如:www.jx.com/product/... 则指向product主机,当用户访问

www.jx.com/developer/... 则指向developer主机

yaml 复制代码
# test1.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress1
  namespace: default
  annotations:   #注解
    nginx.ingress.kubernetes.io/rewrite-target: /$2   #地址重写
    nginx.ingress.kubernetes.io/use-regex: "true"   #使用正则表达式
spec:
  ingressClassName: nginx
  rules:
  - host: www.jx.com    #解析的域名
    http:
      paths:                     #路径匹配规则
      - path: /product(/|$)(.*)   #正则匹配。&是截至符,匹配:域名/product/ 或者 域名/product/xxx.xxx
        pathType: ImplementationSpecific
        backend:
          service:
            name: product
            port:
              number: 80
      - path: /developer(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: developer
            port:
              number: 80
      - path: /
        pathType: Prefix     #前缀匹配。匹配所有以 / 开头的路径
        backend:
          service:
            name: protal
            port:
              number: 80

因为使用了正则表达式,所以需要用 ImplementationSpecific 类型而不能使用 Prefixrewrite-target: /$2 表示将匹配的路径重写为第二个捕获组的内容,确保请求路径正确转发。

配置中/product 这个字段的唯一作用就是作为路由匹配的条件(或者说是一个路由标签),地址重写才导向真正的地址文件。

例如:

用户请求的 URL 是 http://www.jx.com/product/api/v1/orders

客户端请求路径为: /product/api/v1/orders

正则匹配拆分: /product + 1=/ + 2=api/v1/orders

重写后: /api/v1/orders,剥离了 /product 前缀,只保留实际业务路径

bash 复制代码
[root@h1 ingress]# kubectl apply -f test1.yaml
ingress.networking.k8s.io/nginx-ingress created

# 修改 hosts 文件
[root@h1 ingress]# cat /etc/hosts | grep www
192.168.1.12 www.jx.com

#查看我们的nodeport:
[root@hd1 ~]# kubectl get svc -n ingress-nginx ingress-nginx-controller
NAME                       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller   NodePort   10.96.132.197   <none>        80:31886/TCP,443:31662/TCP   3h14m


# 测试
[root@hd1 ~]# curl www.jx.com:31886
app1 ----
[root@hd1 ~]# curl www.jx.com:31886/product
app2 ----

5.2 案例一(扩展):基于域名转发不同服务

这个案例很简单,就是根据访问的不同域名指向不同的服务

更好的方案是:一个服务对应一个域名,如 dev.jx.comwww.jx.compro.jx.com

yaml 复制代码
# test1-domain.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress1
  namespace: default
spec:
  ingressClassName: nginx  # 指定使用 Nginx Ingress Controller
  rules:
  - host: www.jx.com       # ① 匹配域名 www.jx.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: protal
            port:
              number: 80
  - host: pro.jx.com      # ② 匹配域名 pro.jx.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: product
            port:
              number: 80
  - host: dev.jx.com      # ③ 匹配域名 dev.jx.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: developer
            port:
              number: 80

5.3 案例二:Ingress 配置 HTTPS

HTTPS 已经成为网站的标准配置。将网站配置为 HTTPS,需要准备域名证书文件,可以使用 openssl 工具生成自签名证书,然后将证书内容保存在 Secret 对象中,供 Ingress 引用。

生成证书和私钥
bash 复制代码
# 方式一:使用 openssl 生成自签名证书(一步到位)
[root@h1 ingress]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out selfsigned.crt
# 期间会提示输入证书信息,CN(Common Name)需要填写域名,如 blog.jx.com

# 查看生成的文件
[root@h1 ingress]# ls
private.key  selfsigned.crt

# 方式二:分步生成(更详细的流程,可选)
# a. 生成私钥
[root@h1 ingress]# openssl genpkey -algorithm RSA -out private.key
# b. 创建证书签名请求
[root@h1 ingress]# openssl req -new -key private.key -out csr.csr
# c. 自签名证书
[root@h1 ingress]# openssl x509 -req -days 365 -in csr.csr -signkey private.key -out selfsigned.crt
创建 TLS Secret
bash 复制代码
# 创建 TLS 类型的 Secret
[root@h1 ingress]# kubectl create secret tls jx --key=private.key --cert=selfsigned.crt
secret/jx created

# 查看 Secret
[root@h1 ingress]# kubectl get secret jx
NAME   TYPE                DATA   AGE
jx     kubernetes.io/tls   2      10s
配置 Ingress 使用 HTTPS
yaml 复制代码
# ingress-blog.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpsingress
  namespace: default
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - blog.jx.com
    secretName: jx
  rules:
  - host: blog.jx.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: product
            port:
              number: 80

tls.hosts 用于指定应用这个 TLS 配置的域名列表,secretName 指向包含证书和私钥的 Secret 名称。

bash 复制代码
# 更新资源
[root@h1 ingress]# kubectl apply -f ingress-blog.yaml
ingress.networking.k8s.io/httpsingress created

# 查看 Ingress(多了一个 443 端口)
[root@h1 ingress]# kubectl get ingress
NAME             CLASS   HOSTS         ADDRESS         PORTS     AGE
httpsingress     nginx   blog.jx.com   10.96.212.255   80, 443   69s
nginx-ingress1   nginx   www.jx.com    10.96.212.255   80        68m

# 查看 HTTPS 对应的 NodePort
[root@h1 ingress]# kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.96.212.255   <none>        80:31875/TCP,443:32463/TCP   6h27m
测试 HTTPS
bash 复制代码
# Linux 测试(-k 表示忽略证书验证)
[root@h1 ingress]# curl -k -H "Host: blog.jx.com" https://192.168.1.12:32463
app2 ----

Windows 客户端测试 :修改 hosts 文件 192.168.1.12 blog.jx.com,浏览器访问 https://blog.jx.com:32463

⚠️ 由于使用的是自签名证书,浏览器会提示"您的连接不是私密连接",点击"高级"→"继续访问"即可。生产环境应使用受信任的 CA 签发的证书。

5.4 案例三:Ingress 自定义配置(重定向)

Ingress Nginx 控制器使用注解(annotations)来实现配置的更精细控制,比如超时、重定向等。

场景 :将网站 nginx.jx.com 重定向到 www.jx.com

yaml 复制代码
# ingress-rewrite.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress1
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: http://www.jx.com
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.jx.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: protal
            port:
              number: 80
bash 复制代码
# 测试
[root@h1 ingress]# curl nginx.jx.com:31886
<html>
<head><title>302 Found</title></head>
<body>
<center><h1>302 Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

5.5 会话保持(Session Affinity)

用于确保客户端请求在一定时间范围内都被转发到同一台后端服务器。在无会话保持的情况下,负载均衡器可能将客户端的请求分发到不同的后端服务器,导致会话数据丢失。

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpsingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/affinity: "cookie"
spec:
  ingressClassName: nginx
  # ... 其他规则

5.6 灰度发布(金丝雀发布)

使用 Nginx Ingress 实现灰度发布的适用场景主要取决于业务流量切分的策略。目前 Nginx Ingress 支持基于 HeaderCookie服务权重 三种流量切分策略。

灰度发布注解说明
注解 说明
nginx.ingress.kubernetes.io/canary 开启 Canary,值为 "true"
nginx.ingress.kubernetes.io/canary-weight 基于权重的流量切分,权重值为 0-100 的正整数,按百分比将流量进行路由
nginx.ingress.kubernetes.io/canary-by-header 基于 HTTP 请求头的流量切分,可选值为 alwaysnever
nginx.ingress.kubernetes.io/canary-by-header-value canary-by-header 配合使用,当请求头值匹配时路由到 Canary 版本
nginx.ingress.kubernetes.io/canary-by-header-pattern 通过正则表达式匹配 HTTP 请求头
nginx.ingress.kubernetes.io/canary-by-header-cookie 基于 Cookie 的流量切分,可选值为 alwaysnever
5.6.1 基于权重的流量切分

场景:网站希望升级新版本能做到平滑升级,使用户不会感知到这一点。通过 Ingress 基于流量的切分功能,逐步将流量路由到新版本,直至完成全部流量切换。

text 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        基于权重的金丝雀发布流程                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────┐    20%流量     ┌─────────────────────┐                       │
│  │ 客户端   │ ──────────────→│ Canary (v2 版本)     │                       │
│  │ 请求     │                │ Service: product     │                       │
│  └──────────┘                └─────────────────────┘                       │
│       │                                                                    │
│       │ 80%流量                                                            │
│       ↓                                                                    │
│  ┌─────────────────────────────────────────┐                               │
│  │ Stable (v1 版本)                        │                               │
│  │ Service: protal                         │                               │
│  └─────────────────────────────────────────┘                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

步骤 1:部署 v1 版本(稳定版)

这里这个svc是我们在5.1步骤2创建的

bash 复制代码
# 验证当前 v1 版本(protal 服务)
[root@hd1 ~]# kubectl get svc | grep protal
protal                ClusterIP      10.96.254.188   <none>          80/TCP     37m

[root@hd1 ~]# curl 10.96.254.188
app1 -----

步骤 2:创建基础 Ingress 规则

yaml 复制代码
# blog-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
spec:
  ingressClassName: nginx
  rules:
  - host: blog.com.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: protal
            port:
              number: 80
  • 定义稳定版路由:所有访问 blog.com.cn 的请求,全部转发到 protal 服务(v1 版本)
bash 复制代码
[root@h1 ingress]# kubectl apply -f blog-ingress.yaml

# 修改 hosts 文件
[root@hd1 ~]# cat  /etc/hosts | grep blog.com.c
192.168.1.12 blog.com.cn

# 测试当前版本
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" http://192.168.1.11:31886
app1 ----

步骤 3:部署 v2 版本(Canary 版本)

这里的v2版本用之前创建的app2代替

bash 复制代码
# 验证 v2 版本(product 服务)
[root@hd1 ~]# kubectl get pod | grep app2
app2-6555f4f95f-89www   1/1     Running   0               49m

[root@hd1 ~]# kubectl get svc | grep product
product               ClusterIP      10.96.15.185    <none>          80/TCP     50m

[root@hd1 ~]# curl 10.96.15.185
app2 ----

步骤 4:创建 Canary Ingress(20% 流量到 v2)

定义金丝雀路由:将 20% 的流量切到 product 服务(v2 版本),其余 80% 仍由基础规则(blog-ingress)转发到 protal 服务

yaml 复制代码
# blog-ingress-canary.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog-canary
  annotations:
    # 开启 Canary
    nginx.ingress.kubernetes.io/canary: "true"
    # 设置权重:20% 流量到 v2 版本
    nginx.ingress.kubernetes.io/canary-weight: "20"
spec:
  ingressClassName: nginx
  rules:
  - host: blog.com.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: product
            port:
              number: 80

金丝雀 Ingress 可以和基础 Ingress 同时存在,而且正是为了"切割流量"而设计的

bash 复制代码
[root@h1 ingress]# kubectl apply -f blog-ingress-canary.yaml

此时,20% 的流量被路由到 v2 版本,80% 的流量路由到 v1 版本。

bash 复制代码
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" http://192.168.1.11:31886
app1 ----

步骤 5:全部流量切换到 v2 版本

这是稳定版本的 Ingress 规则的更新版。更新完成后,将原有的 Ingress 对象指向 v2 版本:

yaml 复制代码
# blog-ingress.yaml(更新)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog     # 保持同名(覆盖更新)
spec:
  ingressClassName: nginx
  rules:
  - host: blog.com.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: product   # 从 protal 改为 product
            port:
              number: 80

全量切换:将基础规则从 protal 改为 product,此时 100% 流量走 v2 版本

bash 复制代码
#应用
[root@h1 ingress]# kubectl apply -f blog-ingress.yaml
#删掉之前的流量分流的Ingress
[root@h1 ingress]# kubectl delete -f blog-ingress-canary.yaml

#访问验证,流量已经全部被指向app2了
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" http://192.168.1.11:31886
app2 ----

至此,灰度发布升级完成。

5.6.2 基于客户端请求的流量切分

场景 :当 HTTP 请求头包含 canary=always 时,流量被路由到新版本。这种策略比较适合将特定的客户端路由到新版本中,以便进行具体的测试和验证。

举个简单的例子:VIP用户与普通用户看的东西是不一样的

text 复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      基于请求头的金丝雀发布流程                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────┐                                                              │
│  │ 普通用户 │ ────────→ v2 版本 (稳定版)                                   │
│  └──────────┘                                                              │
│                                                                             │
│  ┌──────────┐  Header: canary=always                                       │
│  │ 测试用户 │ ────────────────→ v3 版本 (Canary 版)                        │
│  └──────────┘                                                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

步骤 1:部署 v3 版本

这个developer就是我们之前部署的app3

bash 复制代码
# 验证 v3 版本(developer 服务)
[root@hd1 ~]# kubectl get svc | grep developer
developer             ClusterIP      10.96.22.103    <none>          80/TCP     72m

[root@hd1 ~]# curl 10.96.22.103
app3 ----

步骤 2:创建 Canary Ingress(基于 Header)

当访问 blog.com.cn 的请求带有特定的 HTTP 请求头(canary: always)时,将该请求转发到金丝雀版本(developer 服务);否则,请求走稳定版(由基础 Ingress blog-ingress 决定)

yaml 复制代码
# blog-ingress-canary.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog-canary
  annotations:
    # 开启 Canary
    nginx.ingress.kubernetes.io/canary: "true"
    # 基于请求头进行流量切分,检查请求中是否包含名为 "canary" 的 Header
    nginx.ingress.kubernetes.io/canary-by-header: "canary"
    #当 "canary" 请求头的值为 "always" 时,流量才被路由到这个金丝雀版本
    nginx.ingress.kubernetes.io/canary-by-header-value: "always"
spec:
  ingressClassName: nginx
  rules:
  - host: blog.com.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: developer   # ⚠️ 金丝雀版本的后端 Service
            port:
              number: 80
bash 复制代码
[root@h1 ingress]# kubectl apply -f blog-ingress-canary.yaml

步骤 3:测试

-s:静默模式

-H:自定义请求头

bash 复制代码
# 测试 1:不带特殊 Header,路由到稳定版
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" http://192.168.1.11:31886
app2 ----
# 测试 2:带 Header "canary: always",路由到 Canary 版
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" -H "canary: always" http://192.168.1.11:31886
app3 ----

如果测试的时候出了问题,无法正常切换,就清理一下其他的ingress规则,避免干扰

步骤 4:全量切换并下线 Canary

bash 复制代码
# 修改原 Ingress 指向 v3 版本
[root@h1 ingress]# cat blog-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
spec:
  ingressClassName: nginx
  rules:
  - host: blog.com.cn
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: developer
            port:
              number: 80

# 应用更新并删除 Canary Ingress
[root@h1 ingress]# kubectl apply -f blog-ingress.yaml
[root@h1 ingress]# kubectl delete -f blog-ingress-canary.yaml
ingress.networking.k8s.io "blog-canary" deleted


#访问验证
[root@hd1 ~]# curl -s -H "Host: blog.com.cn" http://192.168.1.11:31886
app3 ----
总结

这两种金丝雀更新策略思路上是一致的,一个是先进行流量分割,让一部分用户体验新的业务;一个是通过不同请求头访问不同的版本业务,方便内部人员测试功能。最后的操作都是一致的,就是修改完基础的ingress规则,把版本改成最新的之后,然后删掉金丝雀规则就行了

六、扩展:常见新版本发布策略总结

策略 回滚速度 风险等级 实现复杂度 流量切换方式 主要适用场景
替换部署 (Recreate) 慢(需重新部署旧版本) (中断期间服务不可用) 一次性销毁全部旧 Pod → 创建新 Pod 开发/测试环境,或可接受停机的批处理/夜间任务
滚动发布 (Rolling Update) 中等(需反向滚动) 中等(新旧版本短暂共存) 低(K8s 原生支持) 逐步替换实例,始终保持部分副本在线 大部分常规业务,可容忍短暂混合版本
蓝绿部署 (Blue/Green) 秒级(切回蓝环境) 低(验证后一次性切换) 中(需流量调度层) 切换负载均衡器指向(全量瞬间切换) 对稳定性要求极高、资源充足的核心系统
金丝雀发布 (Canary) (停止放量或切回) 极低(仅影响小部分用户) 高(需流量拆分、监控、自动决策) 按比例(如 1%→10%→100%)逐步增加新版本流量 大型分布式系统、希望真实生产流量验证
A/B 测试 (A/B Testing) 快(调整分流规则即可) 低(仅影响特定用户组) 高(需基于用户特征路由) 基于用户属性(地域、ID、Cookie 等)定向分流 前端、推荐系统、业务功能效果对比

七、关键要点速查

Ingress vs Ingress Controller

概念 本质 类比
Ingress 路由规则(YAML 资源配置) 类似于 Nginx 的配置文件(定义域名、路径 → 后端)
Ingress Controller 实际的负载均衡器进程 类似于实际运行的 Nginx 进程,读取配置并处理流量

常用 Ingress 注解

注解 用途
nginx.ingress.kubernetes.io/rewrite-target URL 重写目标
nginx.ingress.kubernetes.io/use-regex 启用正则表达式匹配
nginx.ingress.kubernetes.io/affinity 会话保持(设为 cookie
nginx.ingress.kubernetes.io/canary 开启金丝雀发布
nginx.ingress.kubernetes.io/canary-weight 金丝雀流量权重(0-100)
nginx.ingress.kubernetes.io/canary-by-header 基于请求头的金丝雀路由

常用命令

bash 复制代码
# 查看 Ingress
kubectl get ingress
kubectl describe ingress <name>

# 查看 Ingress Controller
kubectl -n ingress-nginx get pod
kubectl -n ingress-nginx get svc

# 创建 TLS Secret
kubectl create secret tls <secret-name> --key=<key-file> --cert=<cert-file>

# 修改 Ingress Controller Service 类型
kubectl edit svc ingress-nginx-controller -n ingress-nginx

八、常见问题排查

问题 可能原因 解决方法
curl 访问返回 404 hosts 文件未配置或域名错误 检查 /etc/hosts 是否正确配置
访问返回 502 Bad Gateway 后端 Service 或 Pod 不存在 检查 kubectl get svckubectl get endpoints
访问返回 Connection Refused NodePort 端口未正确暴露 检查 kubectl get svc -n ingress-nginx 的 PORT(S) 列
HTTPS 访问证书错误 使用了自签名证书 使用 -k 参数忽略证书验证,或导入 CA 证书
访问极其卡顿或超时 externalTrafficPolicy 为默认值 修改为 Local 模式
Ingress 规则不生效 IngressClass 不匹配 检查 spec.ingressClassName: nginx 是否正确

九、关键补充说明与谬误纠正

1. 关于 externalTrafficPolicy: Local 的说明

在 4.3 节测试中,当访问失败时需要修改 Service 的 externalTrafficPolicy。这里补充说明两种策略的区别:

策略 行为 优点 缺点
Cluster(默认) 流量可转发到集群内任意节点上的 Pod 负载均衡更均匀 丢失客户端真实 IP
Local 只转发到本节点上有对应 Pod 的后端 保留客户端真实 IP 若节点上没有对应 Pod,则丢弃流量

适用建议 :在 Ingress 场景下,通常需要获取客户端真实 IP(用于日志记录、限流等),因此建议设置为 Local

2. 关于 Ingress 正则表达式匹配

在 5.1 节中,使用了 pathType: ImplementationSpecific 配合正则表达式。各 pathType 的区别:

pathType 说明
Prefix 前缀匹配,如 / 匹配所有以 / 开头的路径
Exact 精确匹配,路径必须完全相同
ImplementationSpecific 由 Ingress Controller 自行解释,通常支持正则表达式

在 Nginx Ingress 中,如需使用正则表达式,必须配合注解 nginx.ingress.kubernetes.io/use-regex: "true"

3. 关于 rewrite-target 注解的说明

rewrite-target: /$2 的工作原理:

  • 正则表达式 /product(/|$)(.*) 匹配路径
    • (/|$) 匹配 / 或字符串结尾 → 捕获为 $1
    • (.*) 匹配剩余路径 → 捕获为 $2
  • /$2 表示将路径重写为 / + 第二个捕获组的内容

示例

  • 请求 /product/api/v1 → 重写为 /api/v1
  • 请求 /product/ → 重写为 /

这样可以确保请求路径正确转发到后端服务,而不会把 /product 路径也带到后端。

4. 关于自签名证书

在 5.3 节中,使用 openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out selfsigned.crt 生成的是自签名证书 ,浏览器会提示不安全。生产环境应使用受信任的 CA(如 Let's Encrypt、云厂商证书服务)签发的证书。自签名证书仅适用于开发测试环境

5. 关于 Ingress Controller Service 修改

文中将 Ingress Controller 的 Service 类型从 LoadBalancer 改为 NodePort,这仅适用于非公有云环境 (如本地虚拟机、裸金属服务器)。如果在云环境(阿里云、AWS 等)中,建议保留 LoadBalancer 类型,云厂商会自动分配公网 IP 和负载均衡器。

6. 关于证书生成的两种方式

文中 5.3 节提供了两种生成证书的方式:

  1. 一步到位openssl req -x509 ...):直接生成自签名证书,适合快速测试
  2. 分步生成(生成私钥 → 生成 CSR → 签名):更详细的流程,适合需要了解证书原理的场景

两种方式生成的证书效果相同,推荐使用一步到位的方式,更简洁高效。

7. 关于灰度发布的注解使用场景总结

注解组合 适用场景
canary: "true" + canary-weight: "20" 按百分比灰度放量,适合逐步验证
canary: "true" + canary-by-header: "canary" + canary-by-header-value: "always" 内部测试用户验证,适合 QA 团队提前测试
canary: "true" + canary-by-header-pattern: ".*" 基于正则表达式匹配更复杂的 Header 规则
canary: "true" + canary-by-header-cookie: "always" 基于 Cookie 进行灰度,适合特定浏览器会话的测试

十、总结

Kubernetes Ingress 是集群七层负载均衡的核心组件,通过 Ingress 资源定义路由规则,由 Ingress Controller 实际执行流量转发。相比 Service 的四层负载均衡,Ingress 能够基于域名、路径等 HTTP 层信息进行智能路由,并且多个服务可以共享同一个入口 IP 和端口。

本文涵盖的核心知识点:

  1. Ingress 与 Service 的区别:L7 vs L4
  2. Ingress 与 Ingress Controller 的关系:规则 vs 执行者
  3. 多服务路由:基于路径和基于域名
  4. HTTPS 配置:通过 TLS Secret 实现
  5. 自定义配置:通过注解实现重定向、会话保持等
  6. 灰度发布:基于权重和基于 Header 的金丝雀发布
  7. 常见发布策略对比:Recreate、RollingUpdate、蓝绿、金丝雀、A/B 测试
相关推荐
大黄说说1 小时前
SQL Server 执行计划怎么看?快速定位 SQL 慢的根源
java·linux·数据库
雪之下雪乃的代码日记1 小时前
Python快速入门(Java开发者版)
java·开发语言·笔记·python
zzh___zzh2 小时前
Java Stream API 常用方法笔记
java·笔记
zzm6282 小时前
安全嵌套子博弈求解与非完美信息博弈——NIPS 2017 最佳论文阅读笔记
论文阅读·笔记·nips·博弈论
砚凝霜3 小时前
软考网络工程师|第 6 章 网络安全基础、攻击、等保完整备考笔记
网络·笔记·web安全
hongmai6668883 小时前
耐高温小钢炮:ESP32-C3-MINI-1U-H4模组选型分享
笔记·单片机·嵌入式硬件·物联网·risc-v
yunwei373 小时前
eBPF 教程:精准隔离已建立的 TCP 连接
linux·安全·开源
菜冻鱼3 小时前
Python-pandas-索引与筛选
开发语言·笔记·python·numpy·pandas·学习方法
一只小菜鸡..3 小时前
南京大学 操作系统 (JYY) 学习笔记:最终章——操作系统的宇宙,与我们的星辰大海
笔记·学习