45

I have a pod that responds to requests to /api/

I want to do a rewrite where requests to /auth/api/ go to /api/.

Using an Ingress (nginx), I thought that with the ingress.kubernetes.io/rewrite-target: annotation I could do it something like this:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: myapi-ing
  annotations:
    ingress.kubernetes.io/rewrite-target: /api
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: api.myapp.com
    http:
      paths:
      - path: /auth/api
        backend:
          serviceName: myapi
          servicePort: myapi-port

What's happening however is that /auth/ is being passed to the service/pod and a 404 is rightfully being thrown. I must be misunderstanding the rewrite annotation.

Is there a way to do this via k8s & ingresses?

Avius
  • 5,504
  • 5
  • 20
  • 42
matt
  • 708
  • 1
  • 6
  • 11

3 Answers3

54

I don't know if this is still an issue, but since version 0.22 it seems you need to use capture groups to pass values to the rewrite-target value From the nginx example available here

Starting in Version 0.22.0, ingress definitions using the annotation nginx.ingress.kubernetes.io/rewrite-target are not backwards compatible with previous versions. In Version 0.22.0 and beyond, any substrings within the request URI that need to be passed to the rewritten path must explicitly be defined in a capture group.

For your specific needs, something like this should do the trick

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: myapi-ing
annotations:
  ingress.kubernetes.io/rewrite-target: /api/$2
  kubernetes.io/ingress.class: "nginx"
spec:
 rules:
 - host: api.myapp.com
   http:
    paths:
     - path: /auth/api(/|$)(.*)
       backend:
         serviceName: myapi
         servicePort: myapi-port
Kerruba
  • 1,779
  • 1
  • 17
  • 25
34

I have created the following example that works and which I will explain. To run this minimal example, run these commands:

$ minikube start  
$ minikube addons enable ingress # might take a while for ingress pod to bootstrap  
$ kubectl apply -f kubernetes.yaml 
$ curl https://$(minikube ip)/auth/api/ --insecure
success - path: /api/
$ curl https://$(minikube ip)/auth/api --insecure
failure - path: /auth/api
$ curl https://$(minikube ip)/auth/api/blah/whatever --insecure
success - path: /api/blah/whatever

As you'll notice, the ingress rewrite annotation appears to be very particular about trailing slashes. If a trailing slash is not present, the request will not be rewritten. However, if a trailing slash is provided, the request uri will be rewritten and your proxy will function as expected.

After inspecting the generated nginx.conf file from inside the ingress controller, the line of code responsible for this behavior is:

rewrite /auth/api/(.*) api/$1 break;

This line tells us that only requests matching the first argument will be rewritten with the path specified by the second argument.

I believe this is bug worthy.

kubernetes.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: ingress-rewite-example
spec:
  selector:
    app: ingress-rewite-example
  ports:
  - name: nginx
    port: 80
    protocol: TCP
    targetPort: 80
  type: NodePort

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ingress-rewite-example
spec:
  template:
    metadata:
      labels:
        app: ingress-rewite-example
    spec:
      containers:
      - name: ingress-rewite-example
        image: fbgrecojr/office-hours:so-47837087
        imagePullPolicy: Always
        ports:
        - containerPort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-rewite-example
  annotations:
    ingress.kubernetes.io/rewrite-target: /api
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - http:
      paths:
      - path: /auth/api
        backend:
          serviceName: ingress-rewite-example
          servicePort: 80

main.go

package main

import (
  "fmt"
  "strings"
  "net/http"
)

func httpHandler(w http.ResponseWriter, r *http.Request) {
  var response string
  if strings.HasPrefix(r.URL.Path, "/api") {
    response = "success"
  } else {
    response = "failure"
  }
  fmt.Fprintf(w, response + " - path: " + r.URL.Path + "\n")
}

func main() {
    http.HandleFunc("/", httpHandler)
    panic(http.ListenAndServe(":80", nil))
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
frankgreco
  • 1,426
  • 1
  • 18
  • 31
  • interesting - if I add on to the url: curl https://$(minikube ip)/auth/api/blah/whatever it doesn't catch it either – matt Dec 18 '17 at 16:25
  • @matt The example you provided in your comment works correctly (see my updated examples). Note that I am running version 0.9.0-beta.15 of the NGINX ingress controller and v1.8.0 of Kubernetes. – frankgreco Dec 18 '17 at 17:16
  • Hi There, where did you find the file nginx.conf ? Cheers – gxvigo Jul 06 '18 at 10:00
  • @GiovanniVigorelli the way the k8s ingress controller works is it dynamically recreates the nginx.conf every time you add a new ingress resource. Hence, to view the config, apply the ingress resource in the original question and then exec into the nginx ingress controller pod and cat out the contents of the current nginx.conf. – frankgreco Jul 06 '18 at 18:42
  • On version nginx-ingress-1.4.0 you must specify nginx.ingress.kubernetes.io/rewrite-target: /api (note the prepended nginx.) – Gabriel Avellaneda Apr 05 '19 at 16:45
  • @GiovanniVigorelli In other words, do "kubectl exec -n NAMESPACE PODNAME cat /etc/nginx/nginx.conf". – Fumisky Wells Sep 04 '19 at 08:07
0

Could use configuration-snippet annotation:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: myapi-ing
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite ^/auth/api/(.*) /api/$1 break;
spec:
  rules:
  - host: api.myapp.com
    http:
      paths:
      - path: /auth/api
        backend:
          serviceName: myapi
          servicePort: myapi-port
Hongbo Liu
  • 2,818
  • 1
  • 24
  • 18