nginx只用一个公网端口转发多个内网服务
1. 问题
一个常见的问题,生产环境的一台有公网ip的服务器,为了安全起见,运维只给开一个端口,于是考虑用nginx根据url的前缀路径来转发到不同服务。例如:
location /path1 {
proxy_pass http://ip1:port1;
}
location /path2 {
proxy_pass http://ip2:port2;
}
以springboot工程为例,我们自己的web服务一般都会通过设置
server:
servlet:
context-path: /xxxxxx
来给该服务的所有url设置一个统一的前缀路径/xxxxxx
。这时候nginx就可以将所有带有前缀/xxxxxx
的请求转发到该服务。这是一个很简单的事。
问题是有很多第三方服务,例如kafka manager,他是没有这样的前缀的,假设kafka安装在192.168.0.182
机器上,kafka manager的访问地址就是http://192.168.0.182:3000
,我们当然可以在首次访问时通过地址http://192.168.0.182:3000/kafka
让nginx根据/kafka
识别到这是kafka manager服务并转发到http://192.168.0.182:3000
,但是我们打开kafka manager页面上的链接时是不会有我们自己添加的/kafka
前缀的,上面的方法失效。
2. 解决方法
我们都知道http服务是无状态的,就像上面所说即使访问了kafka manager的主页,点击主页上按钮发起的请求也无法被识别为kafka manager请求。但是web服务绝大多是都是需要有状态的,主要有session/cookie、token两种方式来解决这个问题。也可以用来解决上面的问题。
在nginx中配置:
// 精确匹配到/kafka,转到kafka manager服务并设置好cookie
location =/kafka {
proxy_pass http://10.95.123.66:19900/;
add_header Set-Cookie key=kafka;
}
// 后续的访问,统一进入根匹配,通过cookie判断转发到哪个服务
location ~ / {
if ($http_cookie ~* "kafka") {
proxy_pass http://192.168.0.182:3000;
break;
}
}
这时候又有一个问题,使用同样的ip:port来访问这些服务,在浏览器看来所有的服务都是一个域的,他们的cookie就会是同一个cookie,这时候nginx没办法根据cookie的不同转发到不同的服务。
我们想到了给每个服务配置一个域名(没有那么多公网域名可用,就在本地hosts文件配置了),产生跨域的效果,让不同的服务有独立的cookie。
从来都是解决跨域问题,从没想过有一天要主动跨域
3. 思考一下
如果配置了域名,完全可以让nginx根据域名来做识别转发,为什么要用cookie呢?如果你都是配置了公网域名,那确实可以这么干,但是我们是改的hosts文件,保不齐谁的域名就写错了,或者因为只是本地hosts文件的修改根本就没必要统一域名,这时候根据域名做转发就不行了。
4. 另外一个小问题
kafka manager的访问是不需要认证的,暴露在公网上、面向所有人是一件可怕的事,我们是设置了VPN和访问白名单。