Search code examples
nginxkubernetesnginx-ingress

nginx ingress & rewrite-target


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?


Solution

  • 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))
    }