githubhtml
近日發現一個問題:應用程序在返回Http Redirect的時候丟失了原先訪問的端口。好比,咱們這樣訪問http://IP-A:Port-A/app/delete
,這個url會響應302,可是它返回的Response header Location
裏丟失了端口,正確的結果應該是這樣:http://IP-A:Port-A/app/index
,但返回的倒是:http://IP-A/app/index
,把端口丟失了。java
咱們的部署狀況是這樣的:nginx
服務器信息:git
Server Name | NAT Server | K8S Node | Nginx Ingress Svc | Nginx Ingress Pod | App Svc | App Pod |
---|---|---|---|---|---|---|
IP | IP-A | IP-B | IP-C(Cluster IP/VIP) | IP-D(Cluster IP) | IP-E(Cluster IP/VIP) | IP-F(ClusterIP) |
Port | Port-A | Port-B(Nginx Ingress Svc's NodePort) | Port-C | 80(Container Port) | Port-E | Port-F |
其實以上也不全是服務器,其中有兩個K8S Service不是服務器,它們是VIP,關於這個請看K8S - Using Source IP一文,當訪問http://IP-A:Port-A/app/delete
的時候,這個請求從左到右貫穿了這些服務器。github
順便一提上面的NAT Server是一臺普通的服務器,咱們用它作了PAT使咱們的Nginx Ingress可以被外網訪問到。apache
咱們使用以前提到過的Echo Server來觀察透過Ingress訪問Echo Server時傳遞給Echo Server的Request header:http://IP-A:Port-A/echo-server
,獲得了這些有趣的Request header:segmentfault
host=IP-A:Port-A x-original-uri=/echo-server x-forwarded-for=IP-B x-forwarded-host=IP-A:Port-A x-forwarded-port=80 x-forwarded-proto=http
而後直接訪問Echo Server Svc,發現是沒有上面提到的x-*
Request header的。因而懷疑問題出在這幾個header上。api
來說一下這些頭各自表明什麼意思。瀏覽器
注意,前三個是事實標準,MDN有收錄,x-forwarded-port
和x-original-uri
彷佛是私有擴展。tomcat
找一個趁手的Http Request工具(我用的是Postman),記得把Follow redirect關掉,而後模擬Nginx請求的方式(就是把上面提到的x-*
header帶上/去掉/修改值)直接請求App Svc。
結果發現x-forwarded-port
是Response header Location
的關鍵,即若是x-forwarded-port=Port-A
的話,Location
就會帶上正確的端口。
能夠推測,App利用了host
和x-forwarded-*
這些header來構造redirect url。
在Java Servlet API中,在描述HttpServletResponse#sendRedirect的時候提到,其返回的URL必須是Absolute URL。
Tomcat的org.apache.catalina.connector.Response
的toAbsolute方法負責構造Absolute URL。
那麼它又是如何知道選用什麼Port的呢?這個和RemoteIPValve有關,有興趣的話你能夠查閱相關文檔。
上面只是講了Tomcat是如何構造redirect url的,但這個方法不是標準的,不一樣的容器有各自的實現,畢竟Java Servlet API也沒有規定如何構造Absolute URL。
我以前也寫過一篇相關話題的文章《反向代理使用https協議,後臺tomcat使用http,redirect時使用錯誤協議的解決辦法》,你能夠看一看。
那麼問題來了,我明明訪問的是IP-A:Port-A
,爲什麼Nginx取到的值是80?
這是由於在整個請求鏈路的前段:NAT Server > K8S Node > Nginx Ingress Svc 都是在第4層工做的,能夠認爲它們乾的事情都是NAT,Nginx Ingress Pod是不知道這些服務器/網絡節點的端口,所以它只能把本身的端口80(容器內Port)給x-forwarded-port。
關於這個邏輯你能夠查看Nginx Ingress的配置文件就可以知道了:
kubectl -n kube-system exec -it <nginx-ingress-controller-pod-name> -- cat /etc/nginx/nginx.conf
查看Nginx Ingress配置文件發現若是最初請求的時候帶上x-forwarded-port
的話,就可以改變它傳遞到後面的值,可是這有兩個問題:
因此這個方法很差。
雖然能夠經過修改tomcat的代碼,讓它從x-forward-host/host
header來取port,可是這個不現實。
這個方法比較靠譜,只要將NAT Server的端口改爲80就沒有問題了。
事實上,若是你直接訪問K8S Node的話(NodePort方式),也是要將NodePort設置爲80,記得前面說的嗎?Nginx Ingress沒法知道上層NAT的端口。
總而言之,就是你最初請求的URL不能是80以外的端口,必須是http://some-ip/app
才能夠。
使用Nginx Ingress提供的Proxy redirect annotations,將Location
的值作文本替換。