K8s Pod中应用如何获取客户端 IP

status
Published
type
Post
slug
get-ip-info-in-k8s-pod
date
Sep 4, 2021
tags
Java
K8s
Nginx
summary
在 Kubernetes (K8s) 的 Pod 中运行的应用,想要获取客户端的真实 IP 地址信息,可以通过检查 "X-Original-Forwarded-For" 请求头来获取。在 K8s 集群中,请求经过 Ingress Controller 处理后,会将真实的客户端 IP 地址添加到该请求头中。因此,应用需要通过提取该请求头来获取客户端 IP 信息。需要注意的是,在 Ingress 资源中配置 "nginx.ingress.kubernetes.io/proxy-real-ip-cidr" 注解来启用对该请求头的支持。通过这种方式,应用可以准确获取到客户端的 IP 地址信息。
近期项目中需要记录下用户操作时IP信息的功能,在现有的项目架构中,客户端的IP信息会携带在HTTP请求头中。因该服务是Java实现的,即我们需要在 HttpServletRequest 中去提取 IP 信息。
IP工具类代码如下:
import javax.servlet.http.HttpServletRequest; public class IPUtils { public static String getClientIP(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddress().getAddress().getHostAddress(); if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) { // 取本机IP try { InetAddress inet = InetAddress.getLocalHost(); ipAddress = inet.getHostAddress(); } catch (UnknownHostException e) { log.error("获取本机网卡IP异常", e); } } } // 如果客户端 IP 是通过代理获取的,实际的客户端 IP 可能是以逗号分隔的,如果有多个代理。在这种情况下,第一个 IP 是实际的客户端 IP。 int commaIndex = ipAddress.indexOf(","); if (commaIndex != -1) { ipAddress = ipAddress.substring(0, commaIndex); } return ipAddress; } }
以上代码中有多个请求头名称,下面依次介绍:
  • X-Forwarded-For:是一个扩展头,用于识别通过HTTP代理或负载均衡器传输的客户端IP地址。当请求经过多个代理时,每个代理服务器都会将自己的IP地址添加到该头部的末尾。
  • Proxy-Client-IP:是一种特定于代理服务器的请求头,它用于指示连接到代理的客户端的IP地址。
  • WL-Proxy-Client-IP:是WebLogic服务器特定的请求头,用于获取连接到WebLogic服务器的客户端的IP地址。
  • HTTP_CLIENT_IP:是一种特定于代理服务器的请求头,用于指示连接到代理的客户端的IP地址。
  • HTTP_X_FORWARDED_FOR:是一种常见的请求头,用于指示连接到代理服务器的客户端的IP地址。
这些请求头中的信息多依赖于各类负载均衡器,代理服务器等的配置,因此想要获取到还需对访问链路中涉及到的相关服务进行配置。
 
此工具类在应用直接部署在Linux服务器上时,可以正常获取到,但应用生产环境是部署在K8s集群中的,此时的获取到IP为链路中Nginx服务所在主机的IP,并非是客户端IP,也就是代表上面几个HTTP请求头中未包含客户端IP信息。
查看应用在Pod中获取到的请求头信息,可通过代码日志打印实现,此处我借助如下开源项目来快速查看。
K8s 部署配置如下:
apiVersion: apps/v1 kind: Deployment metadata: name: whoami-deployment spec: replicas: 1 selector: matchLabels: app: whoami template: metadata: labels: app: whoami spec: containers: - name: whoami-container image: whoami:latest --- apiVersion: v1 kind: Service metadata: name: whoami-service spec: ports: - name: http targetPort: 80 port: 80 selector: app: whoami --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: whoami-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - host: example.com http: paths: - path: /whoami(/|$)(.*) backend: serviceName: whoami-service servicePort: http
测试发现客户端 IP 信息只存在于 X-Original-Forwarded-For 中。故调整上面代码中增加一行对此请求头信息的提取即可。
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("X-Original-Forwarded-For"); }
 
需求是解决了,那么为什么在 K8s 中时,客户端 IP 的信息就只在 X-Original-Forwarded-For 请求头中了呢?
下面是访问链路的简化示意图
+---------------------------+ | | | 客户端 | | | +--------------+------------+ | v +----------------------------------+ | | | ISP 路由 | | | +----------------------------------+ | v +----------------------------------------------------+ | | | 域名解析IP所在机器(负载均衡) | | | +-------------+---------------------+---------------+ | | v v +-----------------------------------+----------------+ | | | | WAF | SLB | | | | +---------------+-------------------+----------------+ | | v v +----------------------------------------------------+ | | | Kubernetes Ingress | | | +---------------+--------------------+---------------+ | | v v +----------------------------------------------------+ | | | Kubernetes Service | | | +---------------+--------------------+---------------+ | | v v +-----------------------------------+----------------+ | | | | Kubernetes Pod | Container | | | | +-----------------------------------+----------------+ | | v v +----------------------------------------------------+ | | | 应用 | | | +----------------------------------------------------+
K8s 部署下与直接部署的区别就是流量需要经过 K8s Ingress 的转发。Ingress Controller 是Kubernetes集群中用于管理入站网络流量的组件,它充当着入口点和流量路由器的角色。当请求到达Ingress控制器时,它会检查原始的请求头信息,并根据配置进行适当的处理和转发。通常情况下,X-Original-Forwarded-For 请求头是在 Ingress Controller 读取原有的请求头信息来设置的,因此请求最终到达应用时我们需要通过 X-Original-Forwarded-For 请求头来提取IP信息。
 

参考

更多相关信息
当使用Kubernetes集群和NGINX Ingress Controller 时,x-original-forwarded-for是一个HTTP请求标头,用于传递原始客户端IP地址。它被添加到传入请求的标头中,以便应用程序可以获取到真实的客户端IP地址,尤其在经过代理或负载均衡器之后。
使用NGINX Ingress控制器时,你可以通过在Ingress资源中配置nginx.ingress.kubernetes.io/proxy-real-ip-cidr注解来启用x-original-forwarded-for标头的支持。例如:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress annotations: nginx.ingress.kubernetes.io/proxy-real-ip-cidr: "0.0.0.0/0" spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80
在上述示例中,nginx.ingress.kubernetes.io/proxy-real-ip-cidr设置为0.0.0.0/0,这将允许任何IP地址发送x-original-forwarded-for标头。可以根据需要设置特定的CIDR。