As per my understanding the main problem in the question is DNS resolution of the variable in the proxy_pass
directive.
Based on the official documentation and comments from Nginx support, only commercial version of Nginx supports dynamic resolution of the upstream. Free Nginx version only supports resolution on the start. So to update upstream IP address, you have to restart nginx process (it looks like reload does work as well).
The service
variable isn't defined during the nginx process start. It brakes the resolution procedure and proxy_pass gets improper value which produce 502 error.
There are two possible ways to achieve similar to requested result, but with certain drawbacks.
Note: I've adjusted the regexp from the question sample to produce the path close to mentioned in the question:
api.domain.com/service/v0/api/resource/10 -> http://servicev0/api/resource/10
Details about the regexp can be obtained by following the link
1. Using upstream module
Drawback: you have to define upstreams for all possible services in advance, but you can adjust the path using regexp.
The following section added to the /etc/nginx/conf.d/default.conf
allows to resolve the service name and send the proper path to the backend:
resolver 10.96.0.10;
upstream dynamic {
server servicea0;
}
server {
...
location ~ \/(?<service>\w+)\/v(?<version>[0-9]+(\.[0-9]+)*)(?<path>.*) {
proxy_pass http://dynamic/$service$version$path;
}
I've used default nginx container for testing. At this time it has version 1.19.5
To reload the nginx process configuration in the container I've exec into the Pod and run :
root@nginx1-74cf9547fb-8bldg:/# nginx -s reload
The following commands were also run inside the nginx container to skip network configuration details:
root@nginx1-74cf9547fb-8bldg:/# curl -v 127.0.0.1/serviceA/v0/api/resource/17
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /serviceA/v0/api/resource/17 HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.19.5
# Respond from backend shows request headers
{
"path": "/serviceA0/api/resource/17",
"headers": {
"host": "dynamic",
"connection": "close",
"user-agent": "curl/7.64.0",
"accept": "*/*"
},
"method": "GET",
"protocol": "http",
If service host name is defined in the proxy_pass
directly, it sends request path without changes:
proxy_pass http://servicea0;
# Respond from backend shows request headers
{
"path": "/serviceA/v0/api/resource/17",
If proxy_path
specified using variables like the following, it also brakes the resolution procedure and proxy_pass gets improper value and returns 502 error.
proxy_pass http://servicea0/$service$version$path;
2. Using redirect
Drawback: It adds one more step and the second request will be initiated from the client, not from the reverse proxy, which limits its use cases, because of service discovery or DNS resolution issues. (e.g. for use inside the Kubernetes cluster only).
The following section was added to the /etc/nginx/conf.d/default.conf
:
location ~ \/(?<service>\w+)\/v(?<version>[0-9]+(\.[0-9]+)*)(?<path>.*) {
resolver 10.96.0.10;
#proxy_pass http://$service$version$path;
return 301 http://$service$version$path;
}
The following commands were also run inside the nginx Pod container:
root@nginx1-74cf9547fb-8bldg:/# curl -v 127.0.0.1/serviceA/v0/api/resource/10
* Expire in 0 ms for 6 (transfer 0x561d30fecf50)
* Trying 127.0.0.1...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x561d30fecf50)
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /serviceA/v0/api/resource/10 HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.19.5
< Date: Tue, 02 Mar 2021 16:45:12 GMT
< Content-Type: text/html
< Content-Length: 169
< Connection: keep-alive
< Location: http://serviceA0/api/resource/10 <--- redirect link looks as expected
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.19.5</center>
</body>
</html>
I removed unimportant lines from the second test output:
root@nginx1-74cf9547fb-8bldg:/# curl -v 127.0.0.1/serviceC/v2.3.8/api/resource/12
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /serviceC/v2.3.8/api/resource/12 HTTP/1.1
> Host: 127.0.0.1
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.19.5
< Location: http://serviceC2.3.8/api/resource/12
<
<html>
<head><title>301 Moved Permanently</title></head>
How it may look like from the cluster microservice that supports redirects:
(curl -L
- follow redirects)
root@nginx1-74cf9547fb-8bldg:/# curl -L 127.0.0.1/serviceA/v0/api/resource/17
{
"path": "/api/resource/17",
"headers": {
"host": "serviceA0",
"user-agent": "curl/7.64.0",
"accept": "*/*"
},
"method": "GET",
"body": "",
"fresh": false,
"hostname": "serviceA0",
"ip": "::ffff:192.168.228.99",
"ips": [],
"protocol": "http",
"query": {},
"subdomains": [],
"xhr": false,
"os": {
"hostname": "servicea0-66fcd9f788-2z4pk"
},
"connection": {}
}