亚马逊AWS官方博客

在Amazon EKS中获取客户端真实源IP不同方式

背景介绍

客户的一些业务场景需要明确服务的请求来源,需要准确获取和记录请求客户端的真实源IP。例如:安全团队需要对请求的来源进行审计及安全事件的处理,数据团队需要通过源IP做数据分析等。

结合客户在使用Amazon EKS过程中不同的工作场景,对各种获取源IP的实现方法进行总结,供大家参考。

具体场景

一、原生kubernetes Service 资源的配置选项保留客户端源IP

Kubernetes 依靠 kube-proxy 组件实现 Service 的通信与负载均衡。默认情况下Kube-proxy在做转发的时候会做一次SNAT (source network address translation),

在这个过程中,由于使用了 SNAT 对源地址进行了转换,导致 Pod 中的服务拿不到真实的客户端 IP 地址信息。如下图所示:

如果要启用保留客户IP 功能,可以对Service对象中“externalTrafficPolicy”字段进行设置

apiVersion: v1
kind: Service
metadata:
  name: Test-Service
spec:
  selector:
    app: Test-Service
  ports:
    - port: 80
      targetPort: 80
  externalTrafficPolicy: Local
  type: LoadBalancer # 

ExternalTrafficPolic有两个选项值:Cluster(默认)和 Local。Cluster模式下流量可以转发到其他节点上的Pod,Kube-proxy转发时会替换掉报文的源IP;Local模式下

流量只发给本机的Pod,Kube-proxy转发时会保留源IP。对比图如下:

虽然Local模式可以保留客户端源IP,但是在生产环境下,通常会有多个节点同时接收客户端的流量,如果使用Local模式,负载均衡可能就不是很好,因为一旦容器(pod)分布在多个节点上,它只转发给本机,不跨节点转发流量,这将会导致服务可访问性变低。

二、通过 Application Load Balancer ingress -> Service获取真实源IP

Kubernetes Ingress 是一种 API 对象,借助它可以管理对集群中运行的 Kubernetes 服务的外部(或)内部 HTTP[s] 访问,Amazon Elastic Load Balancing Application Load Balancer (ALB) 是一个非常受欢迎的负载均衡服务,它可在应用程序层(第 7 层)跨一个区域的多个目标(例如EKS 多个pod)调整传入流量的负载平衡。

Application Load Balancer 负载均衡器(七层)默认会将客户端真实源 IP 放至 HTTP Header 的 X-Forwarded-For 和 X-Real-IP 字段中。后端应用通过获取 HTTP Header 中的 X-Forwarded-For 或 X-Real-IP 字段值就可以得到客户端真实源 IP。例如:只需要在配置/etc/nginx/nginx.conf配置文件LogFormat 部分中,添加$http_x_forwarded_for,类似于以下内容:

http {
    ...
    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;
    ...
}

 三、通过 NLB -> Nginx Ingress -> Service 访问获取真实 IP

ALB 入口控制器非常优秀,但也有一些使用场景下更适合将 NLB 与 NGINX 入口控制器配合使用。NLB每秒能够处理数百万个请求,同时保持极低的延迟,因此非常适合 TCP 流量的负载均衡。NLB 专为处理突增和波动流量模式而优化,同时每个可用区使用单个静态 IP 地址。Nginx Ingress 可以自定义组件实现特定功能比如:限流,动静分离等。

具有 TCP/SSL 侦听器的四层负载均衡器Network Load Balancer,后端应用程序可以通过两种方式获取源IP。

(1)通过启用Proxy Protocol v2 来取得 Client IP

此方式需要在负载均衡器上启用代理协议版本 2

有关说明,请参阅启用代理协议

另外启用代理协议 v2 之前,请确保您的应用程序目标可以处理代理协议标头,否则您的应用程序可能会中断。

例如:Ningx 需要更改 server 部分的 listen 行,以启用 proxy_protocol。确保更改 http 部分的 log_format 行,以设置 proxy_protocol_addr:

http {
    ...
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$proxy_protocol_addr"';
    access_log  /var/log/nginx/access.log  main;
    ...
}
server {
        ...
        listen  80  default_server proxy_protocol;        
        ...
        }
...
}

(2)NLB 现在已支持IP 透传的模式,通过启用Client IP preservation,后端应用无需额外配置。也建议大家使用此种方式。

有关说明,请参阅客户端IP 保留

注:在Amazon EKS环境下,NLB与Kubernetes Service 和 Ingress 资源结合时,可以通过AWS Load Balancer Controller 自动创建 AWS NLB 资源以及相应的 Target Group。也可使用 AWS Load Balancer Controller 中的新功能 TargetGroupBinding,它允许客户在 EKS 集群之外自己创建和管理 NLB 和 TargetGroup。通过 TargetGroupBinding 可以将 Kubnernetes Service和指定的 Target Group 进行关联。由于此内容不是本文的重点,本文不作过多介绍,详细内容请大家可以查看官方文档以及相关blog。

总结

Kubernetes原生的负载均衡机制以及相关集成的负载均衡组件Nginx、AWS Network Load Balancer、Application Load Balancer等都使业务上获取客户源IP变的复杂,本文主要介绍了在Amazon EKS使用场景下服务端如何获取客户端真实源IP:

通过 Service 资源的配置选项保留客户端源 IP,只需修改 Kubernetes Service 资源配置即可,但可能会存在潜在的 Pods(Endpoints)流量负载不均衡风险;在七层(HTTP/HTTPS)服务转发场景下,可以通过获取 HTTP Header 中 X-Forwarded-For 和 X-Real-IP 字段的值来获取客户端真实源 IP;在四层(tcp/udp)服务转发场景下,除了可以通过启用Proxy Protocol v2来获取客户端真实源 IP外,NLB现在也支持IP 透传的模式,不需要后端应用做额外配置就可以获取客户端真实源 IP。

参考材料:

https://kubernetes.io/docs/concepts/services-networking/ingress/

https://kubernetes.io/zh/docs/tasks/access-application-cluster/create-external-load-balancer/

https://docs.aws.amazon.com/zh_cn/elasticloadbalancing/latest/network/load-balancer-target-groups.html#client-ip-preservation

https://aws.amazon.com/cn/premiumsupport/knowledge-center/elb-capture-client-ip-addresses/

https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html

本篇作者

梁战雷

AWS解决方案架构师,具有超过13年的运维工作经验,在微服务,容器,devops等云原生领域有丰富的项目落地实施经验,现在主要负责企业级客户的上云推广及支持工作。

崔志阳

首汽约车基础架构部运维工程师,主要负责ISSA层平台及云架构方面的设计与优化。在运维领域有多年的架构设计及运维经验,对devops、服务网格、容器领域、云原生相关技术等有着深入的研究和热情。