K8s私有云:Nginx真实客户端IP透传全攻略(HAProxy + Ingress 实战)

一、背景

记一次私有云 K8s 项目交付中,Ingress‑NGINX 采用外部 HAProxy 作为四层入口后,发现业务 Pod(如 Nginx)的访问日志记录的并非客户端真实 IP,而是K8s 内部地址或 HAProxy 的地址。要使最终的 Pod 准确采集到客户端真实 IP。

实测后发现,需要同时完成链路"透传":HAProxy 发送 PROXY v2 保留真实 IP,Ingress‑NGINX 打开 透传配置,并由后端应用读取 X‑Forwarded‑For,ingress-nginx 的Service 侧使用 externalTrafficPolicy=Local 保源 IP。

可以看我👇的图:

二、实战

在实战开始之前,我们需要先了解下 K8s service中的externalTrafficPolicy参数

2.1 关于service中的externalTrafficPolicy参数

两种取值

  • Cluster(默认):集群内访问会负载到任意就绪后端,可跨节点。
  • Local:仅转发给"源节点的本地后端",没有本地后端则失败;适合就近访问、降低跨节点带宽/延迟,或依赖本地资源。

2.1.1 当 service 的externalTrafficPolicy=Cluster

集群内访问会负载到任意就绪后端,可跨节点。目标是任何节点都能访问

  • 我们以一个案例:K8s中运行一个Nginx ,以 NodePort 暴露

    #demo.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: nginx
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: nginx
    template:
    metadata:
    labels:
    app: nginx
    spec:
    containers:
    - name: nginx
    image: nginx
    ports:
    - containerPort: 80

    apiVersion: v1
    kind: Service
    metadata:
    name: nginx
    spec:
    type: NodePort
    selector:
    app: nginx
    externalTrafficPolicy: Cluster
    ports:
    - port: 80
    targetPort: 80
    protocol: TCP
    nodePort: 31043

yaml 复制代码
#apply应用
$ kubectl apply -f demo.yaml 
deployment.apps/nginx created
service/nginx created

#查看 pod 状态以及 nodeport 端口
$ kubectl get pod,svc
NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-5869d7778c-4z7kd   1/1     Running   0          15s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        7d
service/nginx        NodePort    10.111.180.29   <none>        80:31043/TCP   7s

访问:http://K8s中任意节点ip:31043

现象与结论:

  • master / worker 任意节点的 31043 都能访问。
  • 源 IP 通常是节点 IP,不保留真实客户端 IP。

2.1.2 当 service 的externalTrafficPolicy=Local

仅转发给有源节点的本地后端,没有本地后端则失败

  • 我们还是以一个案例:K8s中运行一个Nginx-1 ,以 NodePort 暴露

    #demo-1.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: nginx-1
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: nginx-1
    template:
    metadata:
    labels:
    app: nginx-1
    spec:
    containers:
    - name: nginx
    image: nginx
    ports:
    - containerPort: 80

    apiVersion: v1
    kind: Service
    metadata:
    name: nginx-1
    spec:
    type: NodePort
    selector:
    app: nginx-1
    externalTrafficPolicy: Local
    ports:
    - port: 80
    targetPort: 80
    protocol: TCP
    nodePort: 32043

    #apply应用
    $ kubectl apply -f demo-1.yaml
    deployment.apps/nginx-1 created
    service/nginx-1 created

    #查看 pod 状态以及 nodeport 端口
    $ kubectl get pods -n default nginx-1-74569994c4-65wjx -o wide
    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
    nginx-1-74569994c4-65wjx 1/1 Running 0 3m40s 10.244.135.156 k8s-node03 <none> <none>

    $ kubectl get svc -n default nginx-1 -o wide
    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
    nginx-1 NodePort 10.96.39.34 <none> 80:32043/TCP 3m19s app=nginx-1

看到 nginx-1 这个 pod 运行在 k8s-node3 节点上,我们试下用不同的节点访问,看下对比的效果

现象与结论:

  • 只有 k8s-node3 节点的 ip 可以访问到,且保留了真实客户端 IP
  • 如需让某节点可达,必须在该节点上跑后端 Pod(role/容忍/DaemonSet)

2.2 Ingress-nginx 的 service 修改为 externalTrafficPolicy=Local

  • 部署 ingress-nginx 的类型为 deployement,三副本方式;且只部署在所有的 node 节点上,master 节点不参与外部的流量转发,当然也可以调整成DaemonSet。

  • Ingress-nginx 的 service 类型为 Nodeport

  • 修改 externalTrafficPolicy: Local


    apiVersion: v1
    kind: Service
    metadata:
    labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.14.0
    name: ingress-nginx-controller
    namespace: ingress-nginx
    spec:
    externalTrafficPolicy: Local
    ipFamilies:
    - IPv4
    ipFamilyPolicy: SingleStack
    ports:
    - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: http
    nodePort: 32280
    - appProtocol: https
    name: https
    port: 443
    protocol: TCP
    targetPort: https
    nodePort: 32443
    selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    type: NodePort

  • 查看ingress 命名空间下的所有资源对象

    $ kubectl get all -n ingress-nginx
    NAME READY STATUS RESTARTS AGE
    pod/ingress-nginx-controller-6f58887cd5-d4mxn 1/1 Running 0 3d17h
    pod/ingress-nginx-controller-6f58887cd5-t8hhq 1/1 Running 0 3d17h
    pod/ingress-nginx-controller-6f58887cd5-xszld 1/1 Running 0 3d17h

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/ingress-nginx-controller NodePort 10.96.227.186 <none> 80:32280/TCP,443:32443/TCP 3d17h
    service/ingress-nginx-controller-admission ClusterIP 10.106.60.85 <none> 443/TCP 3d17h

    NAME READY UP-TO-DATE AVAILABLE AGE
    deployment.apps/ingress-nginx-controller 3/3 3 3 3d17h

    NAME DESIRED CURRENT READY AGE
    replicaset.apps/ingress-nginx-controller-6f58887cd5 3 3 3 3d17h

2.3 Ingress-NGINX 的 ConfigMap 开启 PROXY Protocol 与 X-Forwarded-For

  • Ingress-nginx 的部署内容省略,可直接修改 configmap.data 中定义的内容

    --
    apiVersion: v1
    data:
    use-proxy-protocol: "true"
    compute-full-forwarded-for: "true"
    forwarded-for-header: "X-Forwarded-For"
    kind: ConfigMap
    metadata:
    labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.14.0
    name: ingress-nginx-controller
    namespace: ingress-nginx

  • 参数解读

    use-proxy-protocol:启用 PROXY 协议(v1/v2),让 NGINX 从负载均衡器/代理接收客户端的真实 IP、端口等信息(需上游也支持 PROXY 协议)。

    compute-full-forwarded-for:当启用时,NGINX 会将客户端的原始 IP 追加到 X-Forwarded-For头中(而非直接覆盖),保留完整代理链。

    forwarded-for-header:指定用于存储客户端 IP 的头部名称(默认是 X-Forwarded-For,可自定义为其他如 X-Real-IP等)。

2.4 使用Nginx自带的Realip模块获取用户真实IP

  • Nginx 默认只能看到 上一层代理的 IP ,上面的案例我们之所以可以看到真实的 ip 地址,是因为只经过了一层转发。现在我们的访问是

    客户端 → Haproxy(最外侧)→ Ingress-NGINX → Nginx Pod(你的服务) 。每一层代理都会修改请求的元数据(如 IP 头),Nginx 默认只能看到「上一层代理的 IP」(而非真实客户端 IP)。下面的这个三个参数就是为了让你的 Nginx Pod 从多层代理的混乱 IP 中"捞出"真实客户端 IP

  • 这段 Nginx 配置属于 ngx_http_realip_module模块 (用于处理"真实客户端 IP"),核心作用是:当 Nginx 前方有代理(如负载均衡器、CDN、Ingress)时,从代理传递的 HTTP 头中提取客户端的真实 IP,并替换 Nginx 默认的 $remote_addr变量 (默认 $remote_addr是上一层代理的 IP,而非真实客户端 IP)。

    #指定「可信代理服务器的 IP 段」,10.244.0.0/16 是k8s 的子网地址,表示信任只信任这段ip的代理头,即任何代理传递的 X-Forwarded-For都被认为是合法的,一般我们会添加上游服务器的 IP 地址。
    set_real_ip_from 10.244.0.0/16;

    #指定「从哪个 HTTP 头中提取真实客户端 IP
    real_ip_header X-Forwarded-For;

    #递归跳过可信代理 IP,取第一个非可信 IP(真实客户端 IP)
    real_ip_recursive on;

  • 创建一个 main-app 的案例 : 包含 configmap,deployment,svc

    main-app.yaml

    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: nginx-main-conf
    namespace: default
    data:
    nginx.conf: |
    user nginx;
    worker_processes auto;

    复制代码
      error_log  /var/log/nginx/error.log notice;
      pid        /run/nginx.pid;
    
      events {
          worker_connections  1024;
      }
    
      http {
          include       /etc/nginx/mime.types;
          default_type  application/octet-stream;
    
          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;
    
          sendfile        on;
          keepalive_timeout  65;
    
          include /etc/nginx/conf.d/*.conf;
      }

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: main-app
    namespace: default
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: main-app
    template:
    metadata:
    labels:
    app: main-app
    spec:
    containers:
    - name: main-app
    image: nginx:latest
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
    volumeMounts:
    - name: nginx-main-conf
    mountPath: /etc/nginx/nginx.conf
    subPath: nginx.conf
    readOnly: true
    volumes:
    - name: nginx-main-conf
    configMap:
    name: nginx-main-conf

    apiVersion: v1
    kind: Service
    metadata:
    name: main-app-service
    namespace: default
    spec:
    type: ClusterIP
    selector:
    app: main-app
    ports:
    - name: http
    port: 80
    targetPort: 80

    #apply
    kubectl apply -f main-app.yaml

    #查看资源
    [root@k8s-master01 ~]# kubectl get all -n default
    NAME READY STATUS RESTARTS AGE
    pod/main-app-895f5b55d-t8tjq 1/1 Running 0 101s

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d18h
    service/main-app-service ClusterIP 10.100.5.238 <none> 80/TCP 101s

    NAME READY UP-TO-DATE AVAILABLE AGE
    deployment.apps/main-app 1/1 1 1 101s

    NAME DESIRED CURRENT READY AGE
    replicaset.apps/main-app-895f5b55d 1 1 1 101s

2.5 配置 Ingress 域名

2.6 HAProxy 配置 send-proxy-v2参数

  • send-proxy-v2是 HAProxy 的一个配置指令,用于 向后端服务发送「代理协议 v2 版本」的元数据。

  • HAProxy 监听本地的 443 端口,并代理到 所有node 节点上的 Nodeport 端口上。

    frontend kube-https
    bind *:443
    mode tcp
    option tcplog
    default_backend kube-https

    backend kube-https
    mode tcp
    option tcplog
    option tcp-check
    balance roundrobin
    default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
    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

  • 为了方便测试本地电脑,配置 test.srebro.cn 域名解析到 这个 haproxy 服务器172.22.33.100上

    sudo vim /etc/hosts

    172.22.33.100 test.srebro.cn

2.7 访问测试

  • 页面
  • 日志

三、结语

  • 问题本质:多层代理(HAProxy → Ingress‑NGINX → Nginx Pod)会改变源地址,后端默认只能看到"上一层代理"的 IP,导致真实客户端 IP丢失。
  • 策略选择:externalTrafficPolicy=Cluster:任意节点可达,易用但一般不保留真实 IP(跨节点转发+SNAT),externalTrafficPolicy=Local:仅本地后端节点可达,保留源 IP;入口节点必须有 Ingress Pod。
  • 注意事项:set_real_ip_from 只信任你控制的网段(如 K8s Pod 网段),避免被伪造头欺骗。生产中建议仅在 worker 暴露入口;master 不承载外部流量。
相关推荐
进击的雷神2 小时前
现代软件架构全景解析:从B/S到云原生的演进之路
云原生·系统架构
2401_840192272 小时前
ZooKeeper 集群部署指南(Kubernetes StatefulSet 方式)
分布式·zookeeper·kubernetes
观测云2 小时前
云原生 Profiling:零侵入、随用随取的动态采集实战
云原生·profiling
开发者联盟league2 小时前
k8s 创建 serviceAccount 并配置自定义ClusterRole 再授权用于 api-server 访问
云原生·容器·kubernetes
上海云盾-小余2 小时前
云原生内网横向渗透?微隔离+身份服务网格,东西向流量先过“零信任闸机”
云原生
牛奶咖啡132 小时前
Prometheus+Grafana构建云原生分布式监控系统(九)_pushgateway的使用
云原生·grafana·prometheus·pushgateway·pushgateway使用场景·推数据到pushgateway·pushgateway的使用
2401_840192273 小时前
ZooKeeper 单机部署指南
分布式·zookeeper·云原生
江畔何人初3 小时前
Linux 重要目录:/boot、/dev、/etc、/home
linux·运维·云原生
cyber_两只龙宝6 小时前
LVS-DR模式实验配置及原理详解
linux·网络·云原生·智能路由器·lvs·dr模式