Kubernetes NGINX Ingress 控制器 - 如果查询字符串存在,则不同的路由

问题描述

Kubernetes 的 Nginx 入口控制器是否可以有一个入口规则,根据查询字符串是否存在路由到不同的服务?例如..

/foo/bar -> 路由到 serviceA

/foo/bar?x=10 -> 路由到 serviceB

apiVersion: networking.k8s.io/v1
kind: Ingress
Metadata:
  name: my-ingress
spec:
  rules:
  - host: xxxx.com
    http:
      paths:
      - path: /foo/bar(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: serviceA
            port:
              number: 8001
      - path: /foo/bar(/|$)(.*)\?
        pathType: Prefix
        backend:
          service:
            name: serviceB
            port:
              number: 8002

解决方法

我设法为您描述的两个入口对象找到了一个可行的解决方案。对于您提供的示例,入口将无法将您引导至 service-b,因为 nginx 根本不匹配查询字符串。这很好解释here

Ingress 根据路径选择适当的支持。因此,我为第二个后端准备了单独的路径,并将条件重定向到第一个路径,这样当请求到达 /tmp 路径时,它使用 service-b 后端并从请求中修剪 tmp 部分。

这里是与 /foo/bar 匹配的入口 backend-a

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/configuration-snippet: |
            if ($args ~ .+){
                      rewrite ^ http://xxxx.com/foo/bar/tmp permanent;
                      }
spec:
  rules:
  - host: xxxx.com
    http:
      paths:
      - path: /foo/bar
        pathType: Prefix
        backend:
          serviceName: service-a
          servicePort: 80

这里是匹配 /foo/bar? 的入口以及 backend-b 后面的任何内容

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress-rewrite
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /foo/bar$1
spec:
  rules:
  - host: xxxx.com
    http:
      paths:
      - path: /foo/bar/tmp(.*)
        backend:
          serviceName: service-b
          servicePort: 80

请注意,以前的配置剩余部分可能会阻止该解决方案正常运行。在这种情况下,清理、重新部署和入口控制器重启应该会有所帮助。

这里有一些测试可以证明这种情况。首先,我将 xxxx.com 添加到 /etc/hosts

➜  ~ cat /etc/hosts
127.0.0.1       localhost
192.168.59.2 xxxx.com

- 我们在这里测试第一个路径 /foo/bar

➜  ~ curl -L -v http://xxxx.com/foo/bar        
*   Trying 192.168.59.2...
* TCP_NODELAY set
* Connected to xxxx.com (192.168.59.2) port 80 (#0)
> GET /foo/bar HTTP/1.1 <----- See path here! 
> Host: xxxx.com
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Tue,13 Apr 2021 12:30:00 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 644
< Connection: keep-alive
< X-Powered-By: Express
< ETag: W/"284-P+J4oZl3lklvyqdp6FEGTPVw/VM"
< 
{
  "path": "/foo/bar","headers": {
    "host": "xxxx.com","x-request-id": "1f7890a47ca1b27d2dfccff912d5d23d","x-real-ip": "192.168.59.1","x-forwarded-for": "192.168.59.1","x-forwarded-host": "xxxx.com","x-forwarded-port": "80","x-forwarded-proto": "http","x-scheme": "http","user-agent": "curl/7.52.1","accept": "*/*"
  },"method": "GET","body": "","fresh": false,"hostname": "xxxx.com","ip": "192.168.59.1","ips": [
    "192.168.59.1"
  ],"protocol": "http","query": {},"subdomains": [],"xhr": false,"os": {
    "hostname": "service-a" <------ Pod hostname that response came from.

- 这里我们正在测试第一个路径 /foo/bar

➜  ~ curl -L -v http://xxxx.com/foo/bar\?x\=10 
*   Trying 192.168.59.2...
* TCP_NODELAY set
* Connected to xxxx.com (192.168.59.2) port 80 (#0)
> GET /foo/bar?x=10 HTTP/1.1 <--------- The requested path! 
> Host: xxxx.com
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Date: Tue,13 Apr 2021 12:31:58 GMT
< Content-Type: text/html
< Content-Length: 162
< Connection: keep-alive
< Location: http://xxxx.com/foo/bar/tmp?x=10
< 
* Ignoring the response-body
* Curl_http_done: called premature == 0
* Connection #0 to host xxxx.com left intact
* Issue another request to this URL: 'http://xxxx.com/foo/bar/tmp?x=10'
* Found bundle for host xxxx.com: 0x55d6673218a0 [can pipeline]
* Re-using existing connection! (#0) with host xxxx.com
* Connected to xxxx.com (192.168.59.2) port 80 (#0)
> GET /foo/bar/tmp?x=10 HTTP/1.1
> Host: xxxx.com
> User-Agent: curl/7.52.1
> Accept: */*
>  
{
  "path": "/foo/bar","x-request-id": "96a949a407dae653f739db01fefce7bf","query": {
    "x": "10"
  },"os": {
    "hostname": "service-b" <-----Service-b host name!
  },"connection": {}

对于我使用 mendhak/http-https-echo 图像的回复:

apiVersion: v1
kind: Pod
metadata:
  name: service-b
  labels:
    app: echo2
spec:
  containers:
  - name: service-b #<-------- service-b host name
    image: mendhak/http-https-echo
    ports:
    - containerPort: 80