ingress rules 匹配问题导致生产环境大量服务 404 故障记录
· 技术积累 · K8s · 张晨阳

本文记录一下发生在2023年10月份的一次线上问题的排查过程。一开始是收到生产环境网关下大量请求404的告警,但是没过几分钟自己恢复了。因为报404的都是业务真实在用的接口,于是开始排查原因。最后发现是运维同事新增了一个单独看起来挺正常的 ingress 资源配置,却因为没有考虑当前 k8s 集群下 ingress 资源现状,导致 rules 匹配出了意外。希望大家也引以为戒。

收到告警

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

企业微信群收到告警,网关服务对应域名请求存在大量 404 。到 Grafana 查看请求状态分析的监控面板,确实这会每分钟2000的404了,并且好像都是通知服务对应的接口。因为都是 404 ,没有 5xx 的状态码,所以一开始想着是不是这个接口已经下掉了?找对应开发确认被告知该接口是当前业务上真实在用的接口。那是谁偷偷发版发出问题了?喊了一嗓子都说没动、没发版。正说着呢,有人说恢复了,去查看确实 404 确实没了,好吧,不用着急了,慢慢查吧。。。

检查发版:查看通知服务对应 pod 的状态,根据 AGE 列的值可以看出都是已经创建了 39 小时的 pod ,确实没发版。RESTARTS 列的值都为 0 ,说明 pod 刚才也没异常重启。

[root@imzcy ~]# kubectl -n prod get pods -o wide |grep "NAME\|notificationproducer"
NAME                                                              READY   STATUS    RESTARTS      AGE    IP              NODE           NOMINATED NODE   READINESS GATES
deployment-java-notificationproducer-v2-997bfd4dc-5jnr6           1/1     Running   0             39h    192.18.54.57    192.18.52.11   <none>           <none>
deployment-java-notificationproducer-v2-997bfd4dc-dvwtk           1/1     Running   0             39h    192.18.54.251   192.18.52.12   <none>           <none>
deployment-java-notificationproducer-v2-997bfd4dc-mdxvb           1/1     Running   0             39h    192.18.54.64    192.18.52.11   <none>           <none>

分析日志

检索状态为非 200 的请求

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

按请求 path 分类发现不止 notification 一个服务,包括 paycenter 服务也有问题

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

按 upstream_addr 分组,我擦,怎么后端地址都是 127.0.0.1:8181 这个!

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

查看 upstream 对应的服务名,咋是 upstream-default-backend 这个呀!难道是 ingress-nginx-controller 有啥情况?

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

根据 ingress_addr 分类发现两个节点都有异常日志

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

检查 ingress-nginx-controller 状态

确认 controller 对应 pod 状态 - AGE 最少为 2d6h ,说明没有重建。RESTARTS 为0,说明也没有重启。

[root@imzcy ~]# kubectl -n prod-ingress-nginx-wan get pods -A |grep "NAME\|nginx-controller"
NAMESPACE                NAME                                                              READY   STATUS      RESTARTS       AGE
prod-ingress-nginx-wan   ingress-nginx-controller-hsp8w                                    1/1     Running     0              2d6h
prod-ingress-nginx-wan   ingress-nginx-controller-xw4bh                                    1/1     Running     0              57d

查看 controller 运行日志 - 首先看时间,10点的日志就不用看了,14:20 和故障开始时间差不多了。
看如下 L3 行开始的日志内容是发现了名为 prod/ing-java-wechat-dm 这个 ingress 资源导致配置 reload 了。并且在 14:26 因配置发生更改 controller 又重载了配置。看来是动配置了。和另一个运维同事确认,他说刚才确实新建了一个 ingress 但是已经删掉了。。。

[root@imzcy ~]# kubectl -n prod-ingress-nginx-wan logs ingress-nginx-controller-hsp8w --tail=10
I1019 10:10:53.007585      11 event.go:285] Event(v1.ObjectReference{Kind:"Pod", Namespace:"prod-ingress-nginx-wan", Name:"ingress-nginx-controller-hsp8w", UID:"02f6a383-f965-4a9f-9380-b40e2a819d61", APIVersion:"v1", ResourceVersion:"49397301372", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
I1019 14:20:22.116245      11 store.go:430] "Found valid IngressClass" ingress="prod/ing-java-wechat-dm" ingressclass="prod-nginx-wan"
I1019 14:20:22.117263      11 controller.go:168] "Configuration changes detected, backend reload required"
I1019 14:20:22.118805      11 event.go:285] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"prod", Name:"ing-java-wechat-dm", UID:"45930fad-fb93-40a0-9b55-31ceaded1714", APIVersion:"networking.k8s.io/v1", ResourceVersion:"49576864925", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync
I1019 14:20:22.253860      11 controller.go:185] "Backend successfully reloaded"
I1019 14:20:22.254591      11 event.go:285] Event(v1.ObjectReference{Kind:"Pod", Namespace:"prod-ingress-nginx-wan", Name:"ingress-nginx-controller-hsp8w", UID:"02f6a383-f965-4a9f-9380-b40e2a819d61", APIVersion:"v1", ResourceVersion:"49397301372", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration
I1019 14:20:45.571822      11 event.go:285] Event(v1.ObjectReference{Kind:"Ingress", Namespace:"prod", Name:"ing-java-wechat-dm", UID:"45930fad-fb93-40a0-9b55-31ceaded1714", APIVersion:"networking.k8s.io/v1", ResourceVersion:"49576888560", FieldPath:""}): type: 'Normal' reason: 'Sync' Scheduled for sync
I1019 14:26:59.078674      11 controller.go:168] "Configuration changes detected, backend reload required"
I1019 14:26:59.206761      11 controller.go:185] "Backend successfully reloaded"
I1019 14:26:59.207456      11 event.go:285] Event(v1.ObjectReference{Kind:"Pod", Namespace:"prod-ingress-nginx-wan", Name:"ingress-nginx-controller-hsp8w", UID:"02f6a383-f965-4a9f-9380-b40e2a819d61", APIVersion:"v1", ResourceVersion:"49397301372", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration

查看 K8S 审计日志

筛选资源类型为 ingress 发现有51条日志

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

按 verb 分组后,找到了!

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

排除读的动作,看到三条日志:

14:20:23 创建的 ingress 资源
14:20:46 更新了 ingress 资源
14:27:00 删除了 ingress 资源

看这个 ingress 资源的创建时间和删除时间,和故障开始和结束时间基本是对上了,大概率就是这个导致的了。

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

查看 create 详情。将日志中requestObject字段内容展开后得到如下json内容。

{
  "kind": "Ingress",
  "apiVersion": "networking.k8s.io/v1",
  "metadata": {
    "name": "ing-java-wechat-dm",
    "namespace": "prod",
    "creationTimestamp": null,
    "annotations": {
      "nginx.ingress.kubernetes.io/cors-allow-headers": "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,X-Requested-With,Content-Type,Accept,traceparent",
      "nginx.ingress.kubernetes.io/cors-allow-methods": "PUT, GET, POST, OPTIONS",
      "nginx.ingress.kubernetes.io/cors-allow-origin": "*",
      "nginx.ingress.kubernetes.io/enable-cors": "true"
    }
  },
  "spec": {
    "ingressClassName": "prod-nginx-wan",
    "rules": [
      {
        "host": "gateway.example.com",
        "http": {
          "paths": [
            {
              "path": "/wechat",
              "pathType": "Prefix",
              "backend": {
                "service": {
                  "name": "svc-java-wechat",
                  "port": {
                    "number": 80
                  }
                }
              }
            }
          ]
        }
      }
    ]
  },
  "status": {
    "loadBalancer": {}
  }
}


# 查看 update 内容请求体

{
  "kind": "Ingress",
  "apiVersion": "networking.k8s.io/v1",
  "metadata": {
    "name": "ing-java-wechat-dm",
    "namespace": "prod",
    "selfLink": "/apis/networking.k8s.io/v1/namespaces/prod/ingresses/ing-java-wechat-dm",
    "uid": "45930fad-fb93-40a0-9b55-31ceaded1714",
    "resourceVersion": "49576864925",
    "generation": 1,
    "creationTimestamp": "2023-10-19T06:20:22Z",
    "annotations": {
      "nginx.ingress.kubernetes.io/cors-allow-headers": "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,X-Requested-With,Content-Type,Accept,traceparent",
      "nginx.ingress.kubernetes.io/cors-allow-methods": "PUT, GET, POST, OPTIONS",
      "nginx.ingress.kubernetes.io/cors-allow-origin": "*",
      "nginx.ingress.kubernetes.io/enable-cors": "true"
    },
    "managedFields": [
      {
        "manager": "kubectl-create",
        "operation": "Update",
        "apiVersion": "networking.k8s.io/v1",
        "time": "2023-10-19T06:20:22Z",
        "fieldsType": "FieldsV1",
        "fieldsV1": {
          "f:metadata": {
            "f:annotations": {
              ".": {},
              "f:nginx.ingress.kubernetes.io/cors-allow-headers": {},
              "f:nginx.ingress.kubernetes.io/cors-allow-methods": {},
              "f:nginx.ingress.kubernetes.io/cors-allow-origin": {},
              "f:nginx.ingress.kubernetes.io/enable-cors": {}
            }
          },
          "f:spec": {
            "f:ingressClassName": {},
            "f:rules": {}
          }
        }
      }
    ]
  },
  "spec": {
    "ingressClassName": "prod-nginx-wan",
    "rules": [
      {
        "host": "gateway.example.com",
        "http": {
          "paths": [
            {
              "path": "/wechat",
              "pathType": "Prefix",
              "backend": {
                "service": {
                  "name": "svc-java-wechat",
                  "port": {
                    "number": 80
                  }
                }
              }
            }
          ]
        }
      }
    ]
  },
  "status": {
    "loadBalancer": {
      "ingress": [
        {
          "ip": "192.18.52.4"
        },
        {
          "ip": "192.18.53.11"
        }
      ]
    }
  }
}

将上面 json 转换为 yaml 格式后,对比 create 和 update 的内容发现更新只是 nginx.ingress.kubernetes.io/cors-allow-methods 的值有没有双引号的区别,双引号本身应该关系不大。

ingress rules 匹配问题导致生产环境大量服务 404 故障记录

确认新增加的 ingress 是否会引起故障

查看当前 ingress 列表

[root@imzcy ~]# kubectl -n prod get ingress
NAME                                 CLASS            HOSTS                   ADDRESS                    PORTS   AGE
ing-express-butlerapp                prod-nginx-wan   butlerapp.example.com   192.18.52.4,192.18.53.11   80      31d
ing-express-signinapp                prod-nginx-wan   signinapp.example.com   192.18.52.4,192.18.53.11   80      83d
ing-java-logisticlogic               prod-nginx-wan   *                       192.18.52.4,192.18.53.11   80      2d1h
ing-java-notification-producer-biz   prod-nginx-wan   *                       192.18.52.4,192.18.53.11   80      2d1h
ing-java-wechat                      prod-nginx-wan   *                       192.18.52.4,192.18.53.11   80      2d3h
ing-paycentertask                    prod-nginx-wan   *                       192.18.52.4,192.18.53.11   80      2d1h
ing-paycenterweb                     prod-nginx-wan   *                       192.18.52.4,192.18.53.11   80      2d1h

当前通知服务 ingress 配置

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/cors-allow-headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,X-Requested-With,Content-Type,Accept,traceparent
    nginx.ingress.kubernetes.io/cors-allow-methods: PUT, GET, POST, OPTIONS
    nginx.ingress.kubernetes.io/cors-allow-origin: '*'
    nginx.ingress.kubernetes.io/enable-cors: "true"
  name: ing-java-notification-producer-biz
  namespace: prod
spec:
  ingressClassName: prod-nginx-wan
  rules:
  - http:
      paths:
      - backend:
          service:
            name: svc-java-notification-producer-biz
            port:
              number: 80
        path: /notification
        pathType: Prefix


#今天新建的 ingress 资源配置

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ing-java-wechat-dm
  namespace: prod
  creationTimestamp: "2023-10-19T06:20:22Z"
  annotations:
    nginx.ingress.kubernetes.io/cors-allow-headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,X-Requested-With,Content-Type,Accept,traceparent
    nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
    nginx.ingress.kubernetes.io/enable-cors: "true"
spec:
  ingressClassName: prod-nginx-wan
  rules:
    - host: gateway.example.com
      http:
        paths:
          - path: /wechat
            pathType: Prefix
            backend:
              service:
                name: svc-java-wechat
                port:
                  number: 80

嗯??? 不会是新增加的 ingress 显式指定了 host 配置,导致所有访问 gateway.example.com 这个域名的请求都被指向了 svc-java-wechat 这个服务了吧? 真有可能啊

ingress 没有指定 host 的话,好像就会成为默认规则去匹配所有没被其他规则匹配的请求,因为上面我创建的两个 dotnet 项目的 ingress 显式指定了其他独立的域名,而所有其他 java 项目都没有指定 host 并且本身都认为自己使用的 gateway.example.com 这个域名,都是默认规则大家级别都一样所以刚好相安无事(只要没有冲突的一级路径),现在新增加的 ingress 显式指定了 gateway.example.com 这个域名,域名直接匹配上了并且没有其他 ingress 同样指定 gateway.example.com 这个域名监听其他路径,那真的会将所有流量都匹配过去。

由于新增的 ingress 资源 host 指定了 gateway.example.com 域名但是 path 只匹配了 /wechat 这个路径,那么本来类似通过 /notification 访问通知服务的其他请求就会匹配失败,ingress-nginx-controller 就将这些请求转发给了默认后端(upstream-default-backend),对,就是这样。

到 ingress-nginx-controller 上一级 LB 上检查日志,可以看到故障期间,server_name 为 gateway域名的请求状态码正常为200的,只有 /wechat/ 开头的接口。果然,石锤了!!!

单独看挺正常的一个 ingress 配置,却导致了线上故障,真的是有点一言难尽。不过话说回来,如果这个新增的 ingress 提前在灰度环境做了测试,肯定能验证出来的;或者说如果还是按照之前那些 java 项目 ingress 资源一样没有显式指定 host 绑定 gateway.example.com 域名也不会出现这个问题。归根到底还是对生产环境没有敬畏之心

最后两点:
1、对要操作的对象一定要熟悉其工作原理,并且结合当前的实际环境去判断会不会有影响。
2、一定要先在灰度环境做验证!


本文最后记录时间 2024-05-31
文章链接地址:
https://me.jinchuang.org/archives/1531.html
本站文章除注明[转载|引用],均为本站原创内容,转载前请注明出处
k8s 重启维护节点,这样操作才安全!
避坑!deployment 生成的 pod 名称竟然有多种格式变化
Kubernetes 核心组件有哪些
Kubernetes部署Dashboard UI

Axure RP 10 共享激活无限制免费使用

避坑!deployment 生成的 pod 名称竟然有多种格式变化

我要留言