silgon
silgon

Reputation: 7191

kubernetes ingress with multiple target-rewrite

Usually ingress rewrite target works as follows:

nginx.ingress.kubernetes.io/rewrite-target: /

This will rewrite the target of your service names as they are in the root directory. So if I have this:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: demo-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  tls:
  rules:
    http:
      paths:
      - path: /
        backend:
          serviceName: front-main
          servicePort: 80
      - path: /api
        backend:
          serviceName: back-main
          servicePort: 80

My services are going to receive data as they are in /. However, I would like for my service front-main to send root / and for the server back-main to send /someotherpath/. How can I do this?

Is there something like the following line?

nginx.ingress.kubernetes.io/rewrite-target: "front-main: / ; back-main: /someotherpath"

I don't seem to find the answer in the documentation.

Upvotes: 26

Views: 21149

Answers (4)

Giulio
Giulio

Reputation: 302

It was a colossal pain to get this to work, but you can.

The trick is:

"nginx.ingress.kubernetes.io/configuration-snippet" = "if ($request_uri !~* ^/(api|assets)) { rewrite ^/.*$ / break; }"

Assuming you're using frontend routing and all your assets are in /assets.

Here is how I set it up using terraform:

resource "kubernetes_ingress_v1" "main-ingress" {
  metadata {
    name        = "main-ingress"
    annotations = {
      "kubernetes.io/ingress.class"                       = "nginx"
      "certmanager.k8s.io/issuer"                         = module.infra.cert_issuer_name
      "certmanager.k8s.io/acme-challenge-type"            = "dns01"
      "certmanager.k8s.io/acme-dns01-provider"            = "digitalocean"
      "kubernetes.io/ingress.allow-http"                  = false
      "kubernetes.io/tls-acme"                            = true
      "nginx.ingress.kubernetes.io/configuration-snippet" = "if ($request_uri !~* ^/(api|assets)) { rewrite ^/.*$ / break; }"
    }
  }

  spec {
    tls {
      hosts       = [local.prod_hostname]
      // Needs to be different than "local.certIssuerSecretName"
      secret_name = "main-ingress-auth-tls"
    }
    rule {
      host = local.prod_hostname
      http {
        path {
          path      = "/api"
          path_type = "Prefix"
          backend {
            service {
              name = module.prod.be_service_name
              port {
                number = local.be_app_port
              }
            }
          }
        }
        path {
          path      = "/"
          path_type = "Prefix"
          backend {
            service {
              name = module.prod.fe_service_name
              port {
                number = local.fe_app_port
              }
            }
          }
        }
      }
    }
  }
}

and I'm using:

resource "helm_release" "nginx-ingress" {
  depends_on = [ kubernetes_manifest.install-cert-manager-issuer ]
  name       = "nginx"
  version    = "4.5.2"
  chart      = "ingress-nginx"
  repository = var.nginx_helm_stable_repo
  timeout    = 10 * 60
  # timeout for each k8s action - 10 minutes

  set {
    name  = "controller.publishService.enabled"
    value = "true"
  }
}

Upvotes: 5

benterris
benterris

Reputation: 851

The new syntax for the rewrite annotation allows you to use capture groups to define the rewrite-target, which can be used in some situations to achieve what you're looking for.

For instance, if you want one of your services to keep the matching url in its rewrite but not the other, you could use matching groups as follow:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1$2  # Here we concatenate the two matched groups
spec:
  rules:
    - http:
        paths:
        - path: /front()(.*)  # Here the first matching group is always empty
          pathType: Prefix
          backend:
            service:
              name: front-main
              port:
                number: 80
        - path: /(back)(.*)  # Here the first matching group matches 'back'
          pathType: Prefix
          backend:
            service:
              name: back-main
              port:
                number: 80

So /back/foo would redirect to the back-service on /back/foo, but /front/foo would redirect to the front-service simply to /foo.

As far as I know, this can't be used for the more general case you asked for, like rewriting /back/foo to /some-completely-different-path.

Upvotes: 7

Anton Kostenko
Anton Kostenko

Reputation: 8983

Unfortunately, Ingress based on free version of Nginx do not have that feature.

But, if you can use Nginx Plus based Ingress, you can do it by annotation.

Here is an example from official repo:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cafe-ingress
  annotations:
    nginx.org/rewrites: "serviceName=tea-svc rewrite=/;serviceName=coffee-svc rewrite=/beans/"
spec:
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea/
        backend:
          serviceName: tea-svc
          servicePort: 80
      - path: /coffee/
        backend:
          serviceName: coffee-svc
          servicePort: 80

Below are the examples of how the URI of requests to the tea-svc are rewritten (Note that the /tea requests are redirected to /tea/).

/tea/ -> /
/tea/abc -> /abc

Below are the examples of how the URI of requests to the coffee-svc are rewritten (Note that the /coffee requests are redirected to /beans/).

/coffee/ -> /beans/
/coffee/abc -> /beans/abc

Upvotes: 16

Juan Enciso
Juan Enciso

Reputation: 306

Another solution is create two ingress yaml files

Each one using different annotations. It works!

Upvotes: 14

Related Questions