現象
領導反饋生產環境的用戶ip有問題。登陸到這個頁面,發現是所有的用戶ip都是172.30.94.97,這是個內部網絡ip.
排查過程
1 登陸到應用前端nginx, 查看nginx的請求日志
172.30.94.97 - - [17/Jul/2024:02:02:54 +0000] "POST /***/notify/my-page HTTP/1.1" 200 182 "/report/home?type=2&id=2612&lang=zh_CN" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" "10.72.44.200"
172.30.94.97 - - [17/Jul/2024:02:02:54 +0000] "POST /***/notify/my-page HTTP/1.1" 200 182 "/home?lang=zh_CN" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "10.49.140.102"
172.30.94.97 - - [17/Jul/2024:02:02:56 +0000] "POST /***/msg/notify/my-page HTTP/1.1" 200 59 "/user/message/info?lang=zh_CN" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" "10.13.52.192"
發現第一列展示的ip正好是我們的Java應用代碼拿到的iP,而真實的ip展示在最后一列
2 查看nginx的日志輸出格式。第一列取的是remote_addr變量,說明這個變量是有問題的 。我們要取的是最后一列http_x_forwarded_for變量
log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';
3 查看Java代碼獲取客戶ip的邏輯。Java代碼從6個Header變量中依次找可以用的ip。
public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) {String[] headers = new String[]{"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};if (ArrayUtil.isNotEmpty(otherHeaderNames)) {headers = (String[])ArrayUtil.addAll(new String[][]{headers, otherHeaderNames});}return getClientIPByHeader(request, headers);}
4 查看nginx傳遞過來了哪些Header變量。nginx傳遞過來了X-Real-IP和X-Forwarded-For,其中X-Real-IP取的是有問題的remote_addr,正好我們Java代碼取到的是這個變量。X-Forwarded-For沒有值。
location ~ ^/(admin-api|rpc-api)/ {client_max_body_size 32m;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://***:30001;
}
5 根據第二步的分析,將remote_addr
修改為http_x_forwarded_for
6 重啟nginx,問題解決
K8S網絡拓撲
所有的外部流量一定會通過一個ingress controller進入到K8S的內部。ingress controller的一個常見實現是Nginx,正好我們的k8s選擇的就是Nginx。也就是說我們的業務前端nginx前面還有一個nginx。所以我們的前端nginx的remote_addr拿到的是k8s入口ingress的內部ip地址。
總結
用戶請求經過兩層nginx轉發才到達后端java業務應用。remote_addr僅存儲上一個轉發節點的ip,所以我們的業務應用一直拿的是ingress的ip。http_x_forwarded_for存儲的是原始用戶的請求ip。