Kubernetes Multiple Path Rewrites - nginx

Alright, various permutations of this question have been asked and I feel terrible asking; I'm throwing the towel in and was curious if anyone could point me in the right direction (or point out where I'm wrong). I went ahead and tried a number of examples from the docs, but to no avail (see below).
I'm trying to route traffic to the appropriate location under Kubernetes using an Ingress controller.
Server Setup
I have a server, myserver.com and three services running at:
myserver.com/services/
myserver.com/services/service_1/
myserver.com/services/service_2/
Note that I'm not doing anything (purposefully) to myserver.com/.
At each of the three locations, there's a webapp running. For example, myserver.com/services/service_2 needs to load css files at myserver.com/services/service_2/static/css, etc...
Kubernetes Ingress
To manage the networking, I'm using a Kubernetes Ingress controller, which I've defined below. The CORS annotations aren't super relevant, but I've included them to clear up any confusion.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myServices
namespace: myServices
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: '$http_origin'
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myserver.com
rules:
- host: myserver.com
http:
paths:
- path: /services
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
- path: /services/service_1(/|$)
pathType: Prefix
backend:
service:
name: web-service-1
port:
number: 80
- path: /services/service_2(/|$)
pathType: Prefix
backend:
service:
name: web-service-2
port:
number: 80
Targets
I noticed that one helpful thing to do is give some path examples. From the examples below it looks like the paths aren't that complicated. I think this is what I'm after. Note that I'd like each service to be able to resolve its css and image files.
myserver.com/services -> myserver.com/services
myserver.com/services/xxx/xxx -> myserver.com/services/xxx/xxx
myserver.com/services/service_1 -> myserver.com/services/service_1
myserver.com/services/service_1/xxx/xxx -> myserver.com/services/service_1/xxx/xxx
myserver.com/services/service_2/xxx/xxx -> myserver.com/services/service_2/xxx/xxx
Attempts
I know that this issue has to do a lot with the nginx.ingress.kubernetes.io/rewrite-target rule and its interaction with the paths I've defined.
I know that I don't want nginx.ingress.kubernetes.io/rewrite-target: $1 because that gives a 500 when visiting myserver.com/services
I know that I don't want nginx.ingress.kubernetes.io/rewrite-target: $1/$2 because when I visit myserver.com/services/service_1 I actually get part of the content at myserver.com/services rendered on the page.
SO Attempt 1
I also attempted to replicate the accepted solution from this question.
In this attempt I set
nginx.ingress.kubernetes.io/rewrite-target: "/$1" and one of the service paths to
- path: /(services/service_1(?:/|$).*)
When I visit myserver.com/services/service_1/xyz, the HTML from myserver.com/services/service_1 gets rendered.
Concluding Thoughts
Something ain't quite right with the path rewrite and paths rules. Any suggestions?

The problem you reported in your most recent comment is resolved by looking at the rewrite example in the nginx-ingress documentation.
The rewrite-target annotation configures the ingress such that matching paths will be rewritten to that value. Since you've specified a static value of /, anything matching your ingress rules will get rewritten to /, which is exactly the behavior you're seeing.
The solution is to capture the portion of the path we care about, and then use that in the rewrite-target annotation. For example:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myservices
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: '$http_origin'
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
ingressClassName: nginx
rules:
- host: myserver.com
http:
paths:
- path: /services/service_1(/|$)(.*)
pathType: Prefix
backend:
service:
name: webservice-service1
port:
number: 80
- path: /services/service_2(/|$)(.*)
pathType: Prefix
backend:
service:
name: webservice-service2
port:
number: 80
- path: /services(/|$)(.*)
pathType: Prefix
backend:
service:
name: webservice
port:
number: 80
Here, we've modified the match expression so that they look like:
- path: /services/service_1(/|$)(.*)
The second capture group (.*) captures everything after the path
portion that matches literally. We then use that capture group ($2,
because it's the second group) in the rewrite-target annotation:
nginx.ingress.kubernetes.io/rewrite-target: /$2
With this configuration in place, a request to /services/service_2
results in:
This is service2.
But a request to /services/service_2/foo/bar results in:
<html><head><title>404 Not Found</title></head><body>
<h1>Not Found</h1>
The URL you requested (/foo/bar) was not found.
<hr>
</body></html>
And looking at the backend server logs, we see:
10.42.0.32 - - [21/Jan/2022:20:33:23 +0000] "GET / HTTP/1.1" 200 211 "" "curl/7.79.1"
10.42.0.32 - - [21/Jan/2022:20:33:45 +0000] "GET /foo/bar HTTP/1.1" 404 311 "" "curl/7.79.1"
I've updated my example repository to match this configuration.

Related

Handle overlapping ingress paths

I have one ingress which handles all requests to my-domain.example.com/api and forwards them to backend-service/api:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: default-backend
spec:
ingressClassName: nginx
rules:
- host: my-domain.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8080
Now I added this second ingress, which should only handle the sub-path my-domain.example.com/api/log and forward it to logger-service/api:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: logger
annotations:
ingress.kubernetes.io/rewrite-target: /log/$2
spec:
ingressClassName: nginx
rules:
- host: my-domain.example.com
http:
paths:
- path: /api/log(/|$)(.*)
pathType: Prefix
backend:
service:
name: logger-service
port:
number: 8080
But this doesn't work reliable. The log of the ingress controller tells me, that the request to my-domain.example.com/api/log was forwarded to the backend-service. I guess the overlapping paths make it unpredictable.
How do I have to configure my ingresses if I have one service which should handle all defaults (/api/) and some other services which should handle specific sub-paths (/api/log)?
Each path in an Ingress is required to have a corresponding path type. Paths that do not include an explicit pathType will fail validation.
The only supported wildcard character for the path field of an Ingress is the * character. The * character must follow a forward slash (/) and must be the last character in the pattern. For example, /, /foo/, and /foo/bar/* are valid patterns, but , /foo/bar, and /foo//bar are not.A more specific pattern takes precedence over a less specific pattern. If you have both /foo/ and /foo/bar/, then /foo/bar/bat is taken to match /foo/bar/.For more information about path limitations and pattern matching, see the URL Maps documentation.
Suggestion : For second ingress try by mentioning the path as /api/log .*
As shown below
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/rewrite-target: /
name: logger
spec:
ingressClassName: nginx
rules:
-
host: my-domain.example.com
http:
paths:
-
backend:
service:
name: logger-service
port:
number: 8080
path: /api/log*
pathType: Prefix
Above YAML is working, find below Screenshot :
Additional reference doc :
Link1
Link2

Kubernetes: Ingress-nginx does not work well with subpaths in the /api route

I'm trying to access the subpaths in my backend, but it doesn't seem to work for anything, such as /api/hello, other than /api/.
This is my config:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
# ADD ANNOTATION
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: example.com
http:
paths:
- path: /api(/|$)(.*)
# UPDATE PATH
pathType: Prefix
backend:
service:
name: server-srv
port:
number: 3000
- path: /
pathType: Prefix
backend:
service:
name: client-srv
port:
number: 3000
When trying to access the subpaths in /api, I will receive 404 not found.
Any help would be very appreciated ~!
Each path in an Ingress is required to have a corresponding path type. Paths that do not include an explicit pathType will fail validation.
The only supported wildcard character for the path field of an Ingress is the * character. The * character must follow a forward slash (/) and must be the last character in the pattern.
For example, /, /foo/, and /foo/bar/* are valid patterns, but , /foo/bar, and /foo//bar are not.A more specific pattern takes precedence over a less specific pattern. If you have both /foo/ and /foo/bar/, then /foo/bar/bat is taken to match /foo/bar/.For more information about path limitations and pattern matching, see the URL Maps documentation.
Suggestion : Try by mentioning the path as /api/*
Additional reference doc :
https://github.com/kubernetes/ingress-nginx/issues/1120#issuecomment-322007251
https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#multiple_backend_services

ISTIO HTTPS-HTTP 404 NR route_not_found

I am trying to configure TLS termination via Istio HTTPS -> HTTP.
HTTP 80 works fine.
HTTPS 443 works only for / path.
HTTP 200:
curl https://serviceA.example.com
HTTP 404:
curl https://serviceA.example.com/blabla
Istio access logs:
GET /blabla HTTP/2" 404 NR route_not_found
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: serviceA-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: HTTP
protocol: HTTP
hosts:
- "serviceA.example.com"
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: serviceA.example.com
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: serviceA-swearl
namespace: default
spec:
hosts:
- serviceA.example.com
gateways:
- serviceA-gateway
HTTP:
- route:
- destination:
host: serviceA.default.svc.cluster.local
port:
number: 80
I am not sure what I did wrong. By looking at the docs everything should be working. Setup is ISTIO operator on AWS EKS with NLB.
Also, I have a certificate - secret in the istio-system namespace.
Service and Deployment have required labels.
FIX: The issue was that I had on Ingress definition
pathType: ImplementationSpecific
It should be:
pathType: Prefix
Configure Ingress pathType ImplementationSpecific behavior #26883
Community wiki answer for better visibility.
As the OP mentioned in the question, problem is solved by setting
pathType: Prefix
in the ingress.
Original message:
FIX: The issue was that I had on Ingress definition
pathType: ImplementationSpecific
It should be pathType: Prefix
https://github.com/istio/istio/issues/26883
You can find an explanation in this official documentation:
Each path in an Ingress is required to have a corresponding path type. Paths that do not include an explicit pathType will fail validation. There are three supported path types:
ImplementationSpecific: With this path type, matching is up to the IngressClass. Implementations can treat this as a separate pathType or treat it identically to Prefix or Exact path types.
Exact: Matches the URL path exactly and with case sensitivity.
Prefix: Matches based on a URL path prefix split by /. Matching is case sensitive and done on a path element by element basis. A path element refers to the list of labels in the path split by the / separator. A request is a match for path p if every p is an element-wise prefix of p of the request path.

Regex path matching for Ingress-Nginx

I am working with ingress-nginx in kubernetes to set up a server.
The issue is that the paths are not routing at all and I get a 404 error from the nginx server on any request I make.
Below is my code for ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-srv
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
# defaultBackend:
# service:
# name: auth-srv
# port:
# number: 3000
rules:
- host: app.dev
- http:
paths:
- pathType: Prefix
path: /api/auth/?(.*)
backend:
service:
name: auth-srv
port:
number: 3000
- path: /api/coms/?(.*)
pathType: Prefix
backend:
service:
name: coms-srv
port:
number: 3000
If I uncomment the default backend service I get a response but as soon as I remove it I get the 404 nginx error. So I know its connecting to the services I set.
I don't know where I'm going wrong how to go about fixing this as I'm copying straight from the docs. Any help or insight would be great. Thank you in advance!
Edit 1: I removed the regex from the path and commented out the /api/auth path so no requests should be going to the auth-srv. For some reason, all requests route to the auth-srv even though there is no mapping to it. NOTE: Both the auth and coms pods/services are running in the background, just ingress-nginx still isn't routing properly.
So the reason why this wasn't routing properly was because of the:
- host: app.dev
- http:
The "-" in front of the "http" made the controller think it was its own ruleset so the following routes had a host of "*". After I Removed the "-" in front of the "http", the rules were set to the proper host of app.dev and it started routing accordingly.
Thank you for your help everyone! What a long day it has been :')

nginx-ingress works well for /, but not for subpaths

I have setup a name based ingress controller, but it doesn't seem to work for anything other than /.
So http://metabase.domain.com works but http://metabase.domain.com/style/app.css does not.
This is my config:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
namespace: domain
annotations:
kubernetes.io/ingress.global-static-ip-name: "domain"
name: domain-ingress
spec:
rules:
- host: metabase.domain.com
http:
paths:
- path: /
backend:
serviceName: metabase
servicePort: 80
- host: jenkins.domain.com
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 80
From the nginx.conf in the everything looks normal too. For some reason the nginx access and error logs are also empty so can't find anything from there too
As you mentioned, there is no error in the log files, and everything looks normal from
your perspective. I may suggest to tune up ingress using annotations tags.
I've checked documentation of ingress-nginx and found that below annotations may help a bit.
In some scenarios, the exposed URL in the backend service differs from the specified path
in the Ingress rule. Without a rewrite, any request will return 404.
Set the annotation
nginx.ingress.kubernetes.io/rewrite-target
to the path expected by the service.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
name: rewrite
namespace: default
spec:
rules:
- host: rewrite.bar.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /something
If the Application Root is exposed in a different path and needs to be redirected, set the annotation
nginx.ingress.kubernetes.io/app-root
to redirect requests for /.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/app-root: /app1
name: approot
namespace: default
spec:
rules:
- host: approot.bar.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /
If the application contains relative links, it is possible to add an additional annotation
nginx.ingress.kubernetes.io/add-base-url
that will prepend a base tag in the header of the returned HTML from the backend.
I got around to solving this, turns out somehow there was another ingress on my namespace that was a very old version of the nginx controller. I cleared my cluster and relaunched my ingress. Things worked out. Thanks for all the help everyone!

Resources