0
votes

Is it possible with the Nginx ingress controller for Kubernetes to have an ingress rule that routes to different services based on if a query string exists? For example..

/foo/bar -> route to serviceA

/foo/bar?x=10 -> route to 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
1
What have you already tried and what behavior is it producing for you? - mdaniel

1 Answers

1
votes

I managed to find a working solution for what you described with two ingress objects. With the example that you provided ingress won't be able to direct you towards service-b since nginx does not match query string at all. This is very well explained here.

Ingress selects the proper backed based on path. So I have prepared separate path for the second backend and put a conditional redirect to it to the first path so when request reach the /tmp path it uses service-b backend and trims the tmp part from the request.

So here's the ingress that matches /foo/bar for the 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

And here is the ingress that matches /foo/bar? and whatever comes after for the 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

Please note, that previous configuration leftovers can prevent that solution from working well. Clean up, redeploy and ingress controller restart should help in that situation.

Here are some tests to prove the case. First I have added the xxxx.com to /etc/hosts:

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

- Here we are testing the firs path /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.

- And here we are testing the firs path /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",
  "headers": {
    "host": "xxxx.com",
    "x-request-id": "96a949a407dae653f739db01fefce7bf",
    "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": {
    "x": "10"
  },
  "subdomains": [],
  "xhr": false,
  "os": {
    "hostname": "service-b" <-----Service-b host name!
  },
  "connection": {}

For the responses I've used the mendhak/http-https-echo image:

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