1. 项目概述
在私有云Kubernetes环境中,当使用HAProxy作为四层负载均衡器配合Ingress-NGINX作为七层入口时,业务Pod(如Nginx)的访问日志中往往无法记录真实的客户端IP地址,而是显示K8s内部地址或HAProxy的地址。这个问题源于多层代理架构对源IP的层层改写,本文将详细解析如何通过完整的链路配置实现真实客户端IP的透传。
2. 核心原理解析
2.1 多层代理架构下的IP传递问题
在典型的"客户端 → HAProxy → Ingress-NGINX → 业务Pod"架构中,每一层代理都会修改请求的源IP信息:
- HAProxy层:作为四层负载均衡器,默认使用TCP模式转发流量,原始客户端IP信息会丢失
- Ingress-NGINX层:作为七层入口控制器,默认会使用自己的IP作为请求源
- 业务Pod层:最终接收到的请求源IP是上一跳的Ingress-NGINX Pod IP
2.2 解决方案设计思路
要实现真实IP透传,需要在三个关键位置进行配置:
- HAProxy配置:启用PROXY协议v2,在四层转发时携带原始连接信息
- Ingress-NGINX配置:
- Service设置externalTrafficPolicy=Local保留源IP
- ConfigMap启用PROXY协议和X-Forwarded-For处理
- 业务Nginx配置:使用realip模块从HTTP头中提取真实IP
3. 详细配置步骤
3.1 Ingress-NGINX Service配置
3.1.1 externalTrafficPolicy参数详解
Kubernetes Service的externalTrafficPolicy参数有两种模式:
yaml复制apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
externalTrafficPolicy: Local # 关键配置
type: NodePort
ports:
- name: http
port: 80
targetPort: http
nodePort: 32280
selector:
app.kubernetes.io/component: controller
-
Cluster模式(默认):
- 流量会负载均衡到所有节点的后端Pod,可以跨节点转发
- 源IP会经过SNAT转换,通常变为节点IP
- 优点:高可用性好,任何节点都可以接收流量
- 缺点:无法保留真实客户端IP
-
Local模式:
- 流量只会转发到接收请求的节点上的本地Pod
- 如果当前节点没有对应Pod,连接会失败
- 优点:保留真实客户端IP
- 缺点:需要确保每个入口节点都有Ingress Pod
生产建议:使用DaemonSet方式部署Ingress-NGINX,确保每个Worker节点都有实例
3.1.2 NodePort端口规划
建议为HTTP和HTTPS服务分配固定的NodePort范围:
yaml复制ports:
- name: http
port: 80
targetPort: http
nodePort: 32280 # 3xxxx范围内的端口
- name: https
port: 443
targetPort: https
nodePort: 32443
3.2 Ingress-NGINX ConfigMap配置
需要修改ingress-nginx-controller的ConfigMap:
yaml复制apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
use-proxy-protocol: "true" # 启用PROXY协议
compute-full-forwarded-for: "true" # 完整记录代理链
forwarded-for-header: "X-Forwarded-For" # 自定义头名称
# 其他配置...
关键参数说明:
use-proxy-protocol:启用后Ingress-NGINX会解析HAProxy发送的PROXY协议头compute-full-forwarded-for:在X-Forwarded-For中保留完整的代理链IPforwarded-for-header:指定用于传递客户端IP的HTTP头字段
3.3 HAProxy配置
HAProxy需要配置TCP模式并启用PROXY协议v2:
haproxy复制frontend kube-https
bind *:443
mode tcp # 四层TCP模式
option tcplog
default_backend kube-https
backend kube-https
mode tcp
option tcplog
option tcp-check
balance roundrobin
server kube-node01 172.22.33.110:32443 send-proxy-v2 check # 关键配置
server kube-node02 172.22.33.111:32443 send-proxy-v2 check
server kube-node03 172.22.33.112:32443 send-proxy-v2 check
关键配置说明:
mode tcp:四层转发模式,不解析HTTP内容send-proxy-v2:向后端发送PROXY协议v2格式的连接信息check:启用健康检查确保后端可用
3.4 业务Nginx配置
在业务Pod的Nginx配置中需要添加realip模块相关配置:
nginx复制http {
set_real_ip_from 10.244.0.0/16; # 信任K8s Pod网段的代理
real_ip_header X-Forwarded-For; # 从哪个头获取真实IP
real_ip_recursive on; # 递归跳过可信代理
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
}
配置说明:
set_real_ip_from:指定可信代理的IP范围(K8s Pod CIDR)real_ip_header:指定包含真实IP的HTTP头字段real_ip_recursive:启用后会跳过所有可信代理IP,取第一个不可信IP作为客户端IP
4. 完整部署示例
4.1 业务应用部署
完整的业务应用部署示例(包含ConfigMap、Deployment和Service):
yaml复制apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-main-conf
data:
nginx.conf: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
events {
worker_connections 1024;
}
http {
set_real_ip_from 10.244.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
server {
listen 80;
location / {
return 200 "Hello from $hostname\n";
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: main-app
spec:
replicas: 3
selector:
matchLabels:
app: main-app
template:
metadata:
labels:
app: main-app
spec:
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-conf
configMap:
name: nginx-main-conf
---
apiVersion: v1
kind: Service
metadata:
name: main-app-service
spec:
selector:
app: main-app
ports:
- protocol: TCP
port: 80
targetPort: 80
4.2 Ingress资源定义
为业务应用创建Ingress资源:
yaml复制apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: main-app-ingress
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: example-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: main-app-service
port:
number: 80
5. 验证与问题排查
5.1 验证真实IP记录
- 从外部客户端访问服务:
bash复制curl https://app.example.com
- 检查业务Pod的Nginx日志:
bash复制kubectl logs -l app=main-app --tail=10
预期输出应显示真实客户端IP而非Ingress或HAProxy的IP。
5.2 常见问题排查
问题1:Nginx日志仍显示Ingress Pod IP
可能原因:
- HAProxy未正确发送PROXY协议头
- Ingress-NGINX未启用use-proxy-protocol
- 业务Nginx的set_real_ip_from配置不正确
检查步骤:
- 确认HAProxy配置有send-proxy-v2参数
- 检查Ingress-NGINX ConfigMap中的use-proxy-protocol是否为true
- 验证业务Nginx的set_real_ip_from是否包含Ingress Pod的IP范围
问题2:部分节点无法访问
可能原因:
- externalTrafficPolicy=Local但某些节点没有Ingress Pod
- 节点防火墙阻止了NodePort端口
解决方案:
- 确保所有入口节点都运行了Ingress-NGINX Pod
- 检查节点防火墙规则,确保NodePort端口开放
- 或者改用externalTrafficPolicy=Cluster(但会丢失真实IP)
问题3:HTTPS连接失败
可能原因:
- HAProxy的SSL终止配置不正确
- Ingress-NGINX的TLS Secret配置错误
检查步骤:
- 确认HAProxy是否正确配置了SSL证书
- 验证K8s中的TLS Secret是否包含有效证书
- 检查Ingress资源是否正确引用了TLS Secret
6. 生产环境建议
-
网络拓扑设计:
- 将HAProxy部署在独立的安全区域
- Worker节点专门用于运行Ingress和业务Pod
- Master节点不参与业务流量处理
-
高可用保障:
- 部署多个HAProxy实例并使用Keepalived实现VIP
- 使用DaemonSet方式部署Ingress-NGINX确保每个Worker都有实例
- 为关键业务配置Pod反亲和性避免单点故障
-
安全加固:
- 限制set_real_ip_from的范围,只信任必要的IP段
- 在HAProxy上配置ACL限制源IP
- 为Ingress-NGINX配置WAF规则防止注入攻击
-
性能优化:
- 调整HAProxy的maxconn参数匹配业务规模
- 为Ingress-NGINX配置合适的worker_processes和worker_connections
- 启用Nginx的gzip和缓存减少后端压力
7. 架构演进思考
随着业务规模增长,可以考虑以下架构演进方向:
-
边缘架构升级:
- 用云厂商的LB服务替代HAProxy
- 部署专有的四层负载均衡设备
- 考虑使用BGP+ECMP实现更高性能的入口
-
服务网格集成:
- 引入Istio等Service Mesh方案统一管理东西向流量
- 通过Envoy实现更精细的流量控制和观测
- 实现全链路追踪和日志关联
-
多云部署方案:
- 设计跨云、混合云的流量调度方案
- 实现全局负载均衡和故障转移
- 统一多集群的入口管理
在实际项目中,我们通过这套配置成功解决了金融级应用中的客户端审计需求,使得业务系统能够准确记录终端用户的访问IP,满足了合规性要求。关键点在于确保HAProxy、Ingress-NGINX和业务Nginx三层的配置协同工作,任何一环缺失都会导致IP透传失败。