K8s Pod中应用如何获取客户端 IP
✨ 在 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中获取到的请求头信息,可通过代码日志打印实现,此处我借助如下开源项目来快速查看。
https://github.com/traefik/whoamiK8s 部署配置如下:
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信息。
参考
- https://github.com/kubernetes/ingress-nginx/issues/5970
- https://kubernetes.github.io/ingress-nginx/user-guide/miscellaneous/#source-ip-address
更多相关信息
当使用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。