前段时间,笔者撰写了《一文搞懂各种场景下的数据路由转发》,获得很多读者的喜欢,但同时,也有读者留言说,能不能针对云厂商的配置撰写一篇实践文章,说起来,上篇文章主要从最基础的原理上来进行实践,因为很多厂商的LB服务都是基于Nginx,所以就没有专门撰写使用配置上的内容,不过最近笔者发现,其实使用云厂商的LB服务,并不是那么的“易如反掌”,事实上,还是有挺多坑的,那么此文就针对这些云厂商的服务沉淀一些文章,供大家参考
目前腾讯云、阿里云等大型云厂商基本上都是通过提供CLB服务进行路由转发,什么是CLB?
通过云厂商的官方文档定义:
负载均衡(Cloud Load Balancer,CLB)是对多台后端服务器进行流量分发的服务。负载均衡可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。
负载均衡服务通过设置虚拟服务地址(VIP),将位于同一地域的多台后端服务器资源虚拟成一个高性能、高可用的应用服务池。根据应用指定的方式,将来自客户端的网络请求分发到服务器池中。
一个提供服务的负载均衡组通常由以下部分组成:
负载均衡实例:一个 CLB 实例是一个运行的负载均衡服务,用于流量分发。
监听器:用来检查客户端请求并将请求转发给后端服务器。
后端服务器:后端的一组服务器实例,用于接收前端的请求。
来自负载均衡外的访问请求,通过负载均衡实例并根据相关的策略和转发规则分发到后端服务器进行处理。
CLB一般提供两种转发服务即四层和七层转发服务
两者应用的场景不大相同,具体来说:
参考:https://help.aliyun.com/zh/slb/classic-load-balancer/user-guide/listener-overview
同时两者的技术原理上也有一些不同:
比如在四层转发的情况下,转发服务更像是一个路由器,只修改报文目标地址等内容,不获取报文内容,然后根据算法进行转发,实际上还是原来的TCP连接,同时转发主要是修改目标IP和端口
而在七层转发服务中:转发服务更像是一个代理服务器,因为七层转发服务可以根据URL、域名、浏览器类型等进行转发,所以需要获取报文内容,所以只能重新建新的TCP连接
这里可以看出两者的区别:
1.因为七层转发服务需要建立两次TCP连接,而四层转发服务只需要建立一次TCP连接,所以数据路由能力(转发能努力)同等资源下四层转发服务性能更好
2.因为七层转发服务可以提供URL、域名、浏览器类型能力,所以七层转发服务功能更好
3.因为七层功能强,消耗大,所以对硬件设备要求更高,这也意味着价格更贵
首先看下腾讯云CLB服务介绍:
负载均衡 CLB 提供四层(TCP 协议/UDP 协议/TCP SSL 协议)和七层(HTTP 协议/HTTPS 协议)负载均衡服务。您可以通过负载均衡 CLB 将业务流量分发到多个后端服务器上,消除单点故障并保障业务可用性。负载均衡 CLB 自身采用集群部署,可实现会话同步,消除服务器单点,提升系统冗余,保证服务稳定,可在同一个地域部署多个机房,实现同城容灾。
可以看到,腾讯云的CLB服务主要提供四层协议和七层协议,那么我们还是以获取客户端IP为例,看下数据路由转发的情况
CLB 的四层(TCP/UDP/TCP SSL)和七层(HTTP/HTTPS)服务均支持直接在后端 CVM 上获取客户端真实 IP,无需进行额外配置。
其中
四层负载均衡(TGW),在后端 CVM 上获取的源 IP 即为客户端 IP。 七层负载均衡(STGW),在 CLB 与后端服务之间使用短连接时,在后端 CVM 上获取的源 IP 即为客户端 IP;在 CLB 与后端服务之间使用长连接时,CLB 不再透传源 IP,您可以通过 X-Forwarded-For 或 remote_addr 字段来直接获取客户端 IP。七层负载均衡的访问日志请参见 配置访问日志到 CLS。
可以看到两者获取客户端IP的方式略有不同,具体来说,我们以Nginx服务来承接CLB传过来的数据为例:
参考:https://cloud.tencent.com/document/product/214/3728 Nginx 配置方案
1.Nginx 作为服务器时,获取客户端真实 IP 使用http_realip_module,可使用nginx -V命令查看是否已安装 http_realip_module 模块。若 Nginx 未安装此模块,需要重新编译 Nginx 增加 --with-http_realip_module。
yum -y install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel
wget http://nginx.org/download/nginx-1.17.0.tar.gz
tar zxvf nginx-1.17.0.tar.gz
cd nginx-1.17.0
./configure --prefix=/path/server/nginx --with-http_stub_status_module --without-http-cache --with-http_ssl_module --with-http_realip_module
make
make install
vi /etc/nginx/nginx.conf
修改如下部分的配置字段和信息:
说明 其中 xx.xx.xx.xx 需要修改为上一级代理服务器 IP 或者 IP 段。
fastcgi connect_timeout 300;
fastcgi send_timeout 300;
fastcgi read_timeout 300;
fastcgi buffer_size 64k;
fastcgi buffers 4 64k;
fastcgi busy_buffers_size 128k;
fastcgi temp_file_write_size 128k;
# 修改如下部分的配置字段和信息
# 若代理的回源IP比较分散,或不清楚代理回源IP时,可以写成0.0.0.0/0,代表所有请求都从XFF中获取源IP
set_real_ip_from xx.xx.xx.xx;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
service nginx restart
cat /path/server/nginx/logs/access.log
腾讯云容器服务(Tencent Kubernetes Engine ,TKE),以下简称 TKE,可以理解为腾讯云版K8S 从TKE官方文档中看,在 TKE 中,默认的外部负载均衡器为 腾讯云负载均衡 作为服务流量的访问首入口,而在获取客户端IP的方式上,有四种方式
即在 Service 资源中配置字段 Service.spec.externalTrafficPolicy。该字段表示服务是否希望将外部流量路由到节点本地或集群范围的 Pods。有两个选项值:Cluster(默认)和 Local 方式
Cluster:表示隐藏客户端源 IP,LoadBalancer 和 NodePort 类型服务流量可能会被转发到其他节点的 Pods。
Local:表示保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型的服务流量转发到其他节点的 Pods
所以,只需要将service的yaml文件修改为Local即可
apiVersion: v1
kind: Service
metadata:
name: example-Service
spec:
selector:
app: example-Service
ports:
- port: 8765
targetPort: 9376
externalTrafficPolicy: Local
type: LoadBalancer
这种方式的优劣式如下:
使用 TKE 原生支持的 CLB 直通 Pod 的转发功能(CLB 透传转发,并绕过 Kubernetes Service 流量转发),后端 Pods 收到的请求的源 IP 即为客户端真实源 IP
这里可能很多小伙伴会有疑问,还可以这么操作吗?
我们回顾一下K8S的service的type类型,有三种:
ClustrerIP,NodePort,LoadBalancer三种
其中NodePort类型为外部可访问的类型,ClustrerIP类型为集群内部可访问类型
而LoadBalancer类型大部分情况下只适用于支持外部负载均衡器的云提供商
那么在腾讯云这里就是可以这种类型:
原生 LoadBalancer 模式 Service 可自动创建负载均衡 CLB,并通过集群的 Nodeport 转发至集群内,再通过 iptable 或 ipvs 进行二次转发。
事实上,上面是大多数选择的模式,开发者无需知道这里的细节
但在以下场景中更推荐使用直连 Pod 模式 Service:
这里获取IP的场景正好适合,不过这种使用还有有一些限制的,具体来说:
具体配置参考后面的连接:https://cloud.tencent.com/document/product/457/41897
这种方式的优劣式如下
在七层(HTTP/HTTPS)服务转发场景下,可以通过获取 HTTP Header 中 X-Forwarded-For 和 X-Real-IP 字段的值来获取客户端真实源 IP。TKE 中有两种场景使用方式,原理介绍图如下所示:
可以看到,这里和CLB获取的方式差不多,也是CLB传过来的
该方式优缺点分析如下:
通过 TOA 内核模块加载获取真实源 IP
该方式优缺点分析如下:
优点:对于 TCP 传输方式,在内核层面且仅对 TCP 连接的首包进行改造,几乎没有性能损耗。
缺点:需要在集群工作节点上加载 TOA 内核模块,且需在服务端通过函数调用获取携带的源 IP 及端口信息,配置使用较复杂。
对于 UDP 传输方式,会对每个数据包改造添加 option 数据(源 IP 和源端口),带来网络传输通道性能损耗。
TOA 内核模块原理和加载方式请参见 https://cloud.tencent.com/document/product/608/73564
首先看下阿里云CLB服务介绍:
CLB通过设置虚拟服务地址,将添加的同一地域的多台云服务器虚拟成一个高性能和高可用的后端服务池,并根据转发规则,将来自客户端的请求分发给后端服务器池中的云服务器。 CLB默认检查云服务器池中的云服务器的健康状态,自动隔离异常状态的云服务器,消除了单台云服务器的单点故障,提高了应用的整体服务能力。此外,CLB还具备抗DDoS攻击的能力,增强了应用服务的防护能力。 本文介绍阿里云传统型负载均衡CLB提供的功能和功能概述,CLB支持4层和7层负载均衡,并提供健康检查、会话保持、域名转发等功能,保证后端服务的高可用。
可以看到,阿里云的CLB服务也主要提供四层协议和七层协议,那么我们还是以获取客户端IP为例,看下数据路由转发的情况
传统型负载均衡CLB四层监听支持后端服务器获取客户端真实IP地址。通常情况下,无需操作,后端服务器即可获取客户端真实IP地址。但当客户端使用IPv6地址访问IPv4服务时,需要在CLB监听与后端服务器同时开启Proxy Protocol,后端服务器才可获取客户端真实IP地址。
可以看到,阿里云中针对四层的获取客户端真实IP地址,有两个方案,一个直接获取,另一个是通过Proxy Protocol,具体的实现方式,官方文档提供的比较详细,这里只截取部分主要内容:
直接获取主要方式:
通过Proxy Protocol获取主要方式:
七层负载均衡(HTTP或HTTPS协议)支持在HTTP头部的X-Forwarded-For字段保留客户端真实IP信息,服务器进行相应配置后即可获取到客户端真实IP地址。
可以看到,阿里云中针对七层的获取客户端真实IP地址,主要是通过X-Forwarded-For获取,这里与腾讯云的获取字段来说少了一个在短链接场景下的remote_addr,不过由于本身TCP的重新建联,所以只保障一个字段也问题不大
X-Forwarded-For字段格式如下:
名次解释:
TKE:腾讯云容器服务(Tencent Kubernetes Engine ,TKE),可以理解为腾讯云版K8S。 STGW: 腾讯云的七层转发服务(Secure Tencent Gateway,STGW) TGW:腾讯云的四层转发服务(Tencent Gateway,TGW)
我们假设一个项目部署在腾讯云的TKE服务上,项目基于成本的考虑,同时购买了STGW和TGW的服务
于是乎,在岁月静好的一天,研发工程师小A接手一个获取客户端真实IP的需求,这是小A基于严谨的思考,翻开了TKE的文档:https://cloud.tencent.com/document/product/457/48949
发现里面居然有现成的答案:
作为研发工程师,小A觉得1,2、4的都涉及到部署层的改造,目前项目已经稳定,尽量在业务层获取,所以选择了第3种方案
考虑到X-Forwarded-For存在伪造的可能性,就使用了X-Real-IP来进行获取
一切完美,提交上线!
然后发现:
上线一段时间之后,客户反馈,部分获取到的客户端ip,是内网服务IP!!!
研发工程师小A陷入到了沉思,发现这个事情需要好好查查了
经过与运维工程师对齐,发现服务用了两种的CLB——STGW和TGW
并且研发工程师小A也理清了数据传输路线为:
CLB--TKE(ingress)--Service--Pod
于是,开始反查,首先看下查看一下TKE的ingress的配置:
这里X-Real-IP是通过remote_addr获取的
这时,研发工程师小A查阅了CLB的文档发现
腾讯云的四层均衡服务,在后端 CVM 上获取的源 IP 即为客户端 IP(实际获取Nginx可以通过$remote_addr获取) 腾讯云的七层转发服务(STGW),在 CLB 与后端服务之间使用短连接时,在后端 CVM 上获取的源 IP 即为客户端 IP;在 CLB 与后端服务之间使用长连接时,CLB 不再透传源 IP,可以通过 X-Forwarded-For 或 remote_addr 字段来直接获取客户端 IP
看来CLB的确会将remote_addr的值进行赋值,那打印一下日志看下:
$remote_addr的值在STGW和TGW两种CLB服务下结果是不同的
在TGW服务中,是正常,在STGW服务中,变成了内网IP
同时在日志中,还发现,X-Forwarded-For的结果是正常
难道出现了BUG?
研发工程师小A找到了CLB的客服发现,原来还有另一个知识点:
underlay VS Overlay
Underlay网络正如其名,是Overlay网络的底层物理基础,在Underlay网络中,互联的设备可以是各类型交换机、路由器、负载均衡设备、防火墙等,但网络的各个设备之间必须通过路由协议来确保之间IP的连通性。为了摆脱Underlay网络的种种限制,现在多采用网络虚拟化技术在Underlay网络之上创建虚拟的Overlay网络。
绝大部分腾讯云的服务都是部署在Overlay网络的,而小A负责的项目是用在Underlay网络的,所以$remote_addr在大量的数据交换层面下丢了数据,而X-Forwarded-For累加IP,所以X-Forwarded-For是可以的!
替换成获取X-Forwarded-For,同时考虑到可能被客户端篡改,并不是直接拿第一个值,而是从后往前拿第一个外网IP
一切完美,提交上线!
问题解决!
我们假设一个项目部署在腾讯云的自研服务上,项目基于成本的考虑,同时购买了STGW和TGW的服务,同时使用了自研的Nginx来承接CLB的数据进行二次转发
于是乎,在岁月静好的一天,研发工程师小A(对的,又是他)接手一个获取客户端真实IP的需求,这是小A基于上一个项目经验,这次直接拿X-Forwarded-For
一切完美,提交上线!
然后发现:
上线一段时间之后,客户反馈,部分获取到的客户端ip,是内网服务IP!!
这一次,研发工程师小A没有陷入沉思直接找了与运维工程师对齐,
发现服务果然用了两种的CLB——STGW和TGW
并且研发工程师小A也理清了数据传输路线为:
CLB--ServiceA--NGINX--ServiceB--Pod
然后打印NGINX的日志,发现:X-Forwarded-For的信息在STGW和TGW的表现不同
在TGW服务中,是内部IP,在STGW服务中,是正常的
研发工程师小A表示:跟上一次的问题完全相反!!!
这是,研发工程师小A突然想到,是不是第一个ServiceA的问题!
service 是一个抽象概念,其实就是转发规则,具体的转发实际是由kubeproxy 监听并转发的。
打开配置,发现:
externalTrafficPolicy设置成了Cluster
从资料来看
Kube-proxy在做转发的时候,会做一次SNAT (source network address translation),所以源IP变成了节点的IP地址。
而用loacl则可以完整保留客户端IP,当然,代价是这一次的ServiceA的转发只能到所在Node上
那就需要每个Node上都要有个Nginx
研发工程师小A跟运维工程师对齐后发现,Nginx是一个daemonset,符号标准
那就把externalTrafficPolicy改成local
一切完美,提交上线!
问题解决
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。