前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂各种场景下的数据路由转发

一文搞懂各种场景下的数据路由转发

原创
作者头像
粲然忧生
修改2024-03-13 16:44:51
9050
修改2024-03-13 16:44:51
举报
文章被收录于专栏:工程师的分享

大家好,笔者最近参与了一个问题的处理,虽然这个问题不是很难,但从数据流的角度上来,也是一个容易遇到的经典问题,所以这里沉淀一下,供大家参考:

可以看到 这里业务流程是这样的:服务端解析客户端上报的数据时,会同时解析客户端的IP信息,用于确认客户端的地域、运营商等信息,方便对数据进行分类和二次分析

但最近发现,某个环境的客户端的IP信息比较集中,具体来说,IP基本上集中在10-30个范围内,考虑到这个是真实的客户环境,用户数应该是一个比较大数量级,所以判定这里存在问题

可以看到,这里IP分布比较集中,既然出现了问题,我们开始数据流的倒查

一、首先看这个客户端的IP在服务端是怎么获取的

(一)通过X-Forward-For获取请求IP

这里以golang代码为例

这里可以看到,在上报数据中获取Header中的X-Forward-For字段,然后取第一个IP地址即可

查询资料可以得知,X-Forward-For基本上是业界的一个标准字段,用来存储HTTP请求过程中IP链路,具体的内容是IP列表,分别用来表示从客户端IP到中间代理IP最后到服务端的IP

每一个代理服务器,都会把与它建联的上一个服务的IP添加到X-Forward-For的里面,并用逗号隔开

(二)X-Forward-For与X-Real-IP的不同

这里拓展一个知识点:当我们想要用X-Forward-For获取客户端IP的时候,很可能会听到另一个字段X-Real-IP也能做到同样的事情,这两个字段有什么区别,先看定义:

1.X-Real-IP: 当一个请求通过反向代理服务器时,代理服务器会将客户端的真实 IP 地址添加到 X-Real-IP 头部中 2.X-Forward-For: 当一个请求通过反向代理服务器时,代理服务器会将与它建联的上一个服务的IP添加到X-Forward-For的里面,并用逗号隔开

可以看到,X-Real-IP是有一个信息,即客户端IP,而X-Forwarded-For 有中间链路的所有IP地址,那么他们在这个场景(获取客户端IP)下是否完全等价呢?也不是,主要有以下几个不同:

1.X-Real-IP 是一个非标准的头部字段;而X-Forwarded-For 是一个实际标准的头部字段,被写入 RFC 7239(Forwarded HTTP Extension)标准之中。 2.当请求通过多个代理服务器时,每个代理服务器都会将自己的 IP 地址添加到 X-Forwarded-For 头部中。这意味着,X-Forwarded-For 头部可以提供更多的信息,包括请求经过的所有代理服务器的 IP 地址、便于信息对齐和排障。

可以看到,在复杂场景中(多种非标准协议请求和多个代理服务器场景下),更推荐使用X-Forwarded-For 作为获取客户端IP的字段


回到问题本身,这里我们可以看到,服务端获取IP的方式应该问题不大,那就需要看看在前面路由转发阶段是否存在问题:

二、再看一下这个客户端的IP在路由转发中是怎么传递

(一)云厂商的路由转发传递

一般来说:路由转发有很多种方式,很多云厂商会提供通用LB的服务,这里只需要可视化的配置即可,比如下图:

以腾讯云为例,官方文档中告知,X-Forwarded-For会在LB中默认传递,并支持日志打印:

但考虑到通用性,笔者更喜欢从根本上剖析这里的问题,而不是一个云厂商UI界面的操作员。

一般来说,很多厂商提供到的LB,底层都是Nginx居多,那就先拿Nginx来看:

(二)Nginx如何传递客户端的IP

这篇文章来看

如果需要获取X-Forwarded-For,则需要在nginx.conf设置以下两个参数

proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_add_x_forwarded_for定义如下:

the “X-Forwarded-For” client request header field with the $remote_addr variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.

可以看到proxy_add_x_forwarded_for基本上等同于:"http_x_forwarded_for, remote_addr"这样的格式,如果刨根问底一些的话,不同在于:

  • 如果请求中不带X-Forwarded-For头,那么取$remote_addr的值;
  • 如果请求中带X-Forwarded-For头,那么在X-Forwarded-For后追加\$remote_addr,即: \$X-Forwarded-For, $remote_addr

看起来,很合理,就两行配置,但是这么配置有没有问题呢?

这里就不得不提,X-Forwarded-For伪造的问题了:

因为X-Forwarded-For只是http的请求的一个头,如果客户端请求时就带上一个伪造的X-Forwarded-For(使用curl -H 'X-Forwarded-For: 8.8.8.8' http://www.dianduidian.com一条命令就能实现),这时Nginx如果使用上边的配置的话由于X-Forwarded-For不为空,所以Nginx只会在现在值的基础上追加,这样后端服务在拿到头后根据约定取最左边ip话就会拿到一个伪造的IP,会有安全风险。

那这里nginx是怎么解决的呢?

在TCP的场景下,TCP必须经过3次握手,客户端的IP是无法伪造的,所以最外层的Nginx代理一定要取$remote_addr的值,对应配置

proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;

所以解决方案如下:

为了保证后端服务能获取到真实的用户IP,无论中间有多少层代理,必须保证最外层代理能获取到真实的用户IP,这是整个信任连的基础;最外层(直接暴露给用户)的代理一定不能信任请求带过来的X-Forwarded-For,而应取TCP建连时的IP(即$remote_addr),同时必须保证中间层的代理无法被用户直接访问到,否则就不是一个完整的信任链,就有伪造的可能。

  • 最外层Nginx:

proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;

  • 其它中间层Nginx:

proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

可以通过下面的命令进行生效和日志查看

service nginx restart cat /path/server/nginx/logs/access.log 或者cat /var/log/nginx/nginx_access.log 或者cat /var/log/nginx/access.log

(三)其他组件如何获取客户端真实IP

这里大概介绍了IIS 6、IIS 7、Apache服务的获取方式,大家可以参考下

(四)K8S环境下如何传递客户端的IP

好了,我们知道了Nginx本身的原理了,现在再把问题更贴近一下现实情况,目前很多的服务都是基于K8S环境进行部署,那么在K8S环境下,客户端的传递有什么不同?

首先,K8S的集群如何外网访问呢?

1.K8S的集群如何外网访问
  • HostNetwork方式: 通过在 Pod 的配置中使用 hostNetwork: true,Pod 将使用宿主机的网络命名空间,这意味着 Pod 可以直接访问宿主机的网络接口和端口,就像它们是在宿主机上运行的一样。

优点:粗暴直接,跟裸机部署很像,便于新手理解,不用配置/掌握K8S的网络知识就可以运行

缺点:1.Pod 的网络流量与宿主机的网络流量无法区分;2.Pod重启后可能在另一个宿主机,需要保证每台宿主机的网络环境完全一致

总结:适合单集群,单宿主机的情况,基本上适合单机调试和新手上手的情况

  • HostPort方式 hostPort 允许 Pod 在宿主机上绑定一个特定的端口,使得 Pod 的服务可以通过宿主机的 IP 地址和指定的端口访问。这意味着 Pod 的服务可以直接通过宿主机的网络接口访问

优点:粗暴直接,跟裸机部署很像,便于新手理解,不用配置/掌握K8S的网络知识就可以运

缺点:1.Pod重启后可能在另一个宿主机,需要保证每台宿主机的网络环境完全一致

总结:比HostNetwork好在可以区分宿主机还是POD资深的网络流量,但依然会有POD重启的问题

  • LoadBalancer方式 在 k8s 中创建 service 时,需要指定 type 类型,可以分别指定 ClusterIP、NodePort、LoadBalancer 三种,LoadBalancer大部分情况下只适用于支持外部负载均衡器的云提供商(AWS、阿里云、华为云等)使用。本地自己安装的 k8s 集群,默认是不支持 LoadBalancer 的,需要自己安装一个组件来支持。这里不做讨论。 如果想使用这种方式自己搭建的话可以参考:案例2
  • NodePort方式 NodePort在K8S里是一个广泛应用的服务暴露方式,NodePort 服务类型会在每个节点上打开一个端口,并将该端口上的流量路由到服务。这使得服务可以通过 <NodeIP>:<NodePort> 从集群外部访问。

优点:因为会在每个节点自动做相同的设置,对pod重启十分友好,体现出高可用

缺点:学习一下配置方式

总结:选他,选他,选他!

kubectl proxy 在笔者的认知中,不算其中的任何方式,因为它更多的是用来本机调试的,所以没有罗列

2.K8S集群的Ingress

这时,很多同学会说,外网访问少了一种,Ingress也是一种解决方案,其实,这是很多网上的一种不严谨的观点,因为,ingress的规则需要Ingress Controller来执行转发,而Ingress Controller并不能直接被外网访问,,一般会通过 Kubernetes 集群内的Service(NodePort或者LoadBalancer)来进行外网访问,所以严格的说,Ingress方案是NodePort或者LoadBalancer的一种实现方式

当然,Ingress是个很好路由转发方案,当外网数据通过Service传递过来了之后,就可以通过Ingress Controller去执行ingress的规则。

所以一般工程场景下,外网访问的组合模式为:外网请求 --- Service(NodePort)--- Ingress Controller(规则为Ingress的yaml)--- Service(ClusterIP)--- POD(后台服务)

3.K8S集群如何使用ingress配置路由规则

我们先理清Ingress和Ingress controller的关系

Ingress是自kubernetes1.1版本后引入的资源类型。必须要部署 Ingress controller 才能创建Ingress资源,Ingress controller是以一种插件的形式提供。Ingress controller 是部署在Kubernetes之上的Docker容器。它的Docker镜像包含一个像nginx或HAProxy的负载均衡器和一个控制器守护进程。控制器守护程序从Kubernetes接收所需的Ingress配置。它会生成一个nginx或HAProxy配置文件,并重新启动负载平衡器进程以使更改生效。换句话说,Ingress controller是由Kubernetes管理的负载均衡器。Ingress controller是由Kubernetes管理的负载均衡器

可以看到,Ingress是用来配置规则的,Ingress controller是用来执行规则的

最简化的 Ingress 配置如下。

注意:如果没有配置 Ingress controller 就将其 POST 到 API server 不会有任何用处。

配置说明

  • 1-4 行 :跟 Kubernetes 的其他配置一样,ingress 的配置也需要 apiVersion,kind 和 metadata 字段。配置文件的详细说明请查看 部署应用,配置容器 和使用资源。
  • 5-7 行 : Ingress spec 中包含配置一个 loadbalancer 或 proxy server 的所有信息。最重要的是,它包含了一个匹配所有入站请求的规则列表。目前 ingress 只支持 http 规则。
  • 8-9 行 :每条 http 规则包含以下信息:一个 host 配置项(比如 for.bar.com,在这个例子中默认是 *),path 列表(比如:/testpath),每个 path 都关联一个 backend(比如 test:80)。在 loadbalancer 将流量转发到 backend 之前,所有的入站请求都要先匹配 host 和 path。
  • 10-12 行 :正如 services doc 中描述的那样,backend 是一个 service:port 的组合。Ingress 的流量被转发到它所匹配的 backend。
  • 全局参数:为了简单起见,Ingress 示例中没有全局参数,请参阅资源完整定义的 API 参考。 在所有请求都不能跟 spec 中的 path 匹配的情况下,请求被发送到 Ingress controller 的默认后端,可以指定全局缺省 backend。

具体参考案例3

这里可以看到与Nginx类似的内容,那怎么把这些配置运行起来呢——需要配置合适 Ingress controller ,作为规则的分发器,业界的主要有以下几种:

  • NGINX Ingress Controller:基于 NGINX 的 Ingress Controller,它使用 NGINX 作为代理来处理 Ingress 资源中定义的路由规则
  • Traefik:一个开源的 Ingress Controller,基于 Envoy,提供了 API 网关功能1。 HAProxy Ingress Controller:基于 HAProxy 的 Ingress Controller,专为 HAProxy 设计
  • Istio Ingress:基于 Istio 的 Ingress Controller,提供了服务网格的功能
  • Kong Ingress Controller:驱动 Kong Gateway 的 Ingress Controller
  • Contour:基于 Envoy 的 Ingress Controller
  • Emissary-Ingress:基于 Envoy 的 Ingress Controller,也被称为 Emissary API Gateway
  • EnRoute:基于 Envoy 的 API 网关,可以作为 Ingress Controller 运行
  • Easegress IngressController:基于 Easegress 的 API 网关,可以作为 Ingress Controller 运行
  • F5 BIG-IP Container Ingress Services for Kubernetes:允许使用 Ingress 配置 F5 BIG-IP 虚拟服务器1

看到这里不要觉得头大,事实上,绝大部分都是使用NGINX Ingress Controller作为Ingress Controller,一方面,因为NGINX强大的功能和友好的功能配置,另一方面,NGINX本身也是一个业界流行的路由分发解决方案,最后,NGINX Ingress Controller也是Kubernetes 当前支持并维护,所以无脑选它没问题了

当然,喜欢玩微服务和Golang的小伙伴,也可以用Traefik,可以直接UI配置,虽然功能没有NGINX强大,但友好程度十分突出

NGINX Ingress Controller配置

配置这个的话,有一个很好的文档可以给大家参考:[Ingress-Nginx Controller

配置](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/)

简单来说两句话:

ConfigMap: 使用K8S里面的 Configmap 在 NGINX 中设置全局配置。

Annotations: 如果您想要对特定 Ingress 规则的进行配置,使用Annotations。

举个例子:

如果有两个后端服务:myServiceA, myServiceB,且端口均为80的话

需要将外网的数据转发过来,配置如下:

代码语言:yaml
复制
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-myservicea
spec:
  rules:
  - host: myservicea.foo.org
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myservicea
            port:
              number: 80
  ingressClassName: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-myserviceb
spec:
  rules:
  - host: myserviceb.foo.org
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myserviceb
            port:
              number: 80
  ingressClassName: nginx

备注:Kubernetes version >= 1.19.x

其中IngressClassName:nginx,表示指定了使用 Nginx Ingress Controller 来处理这个 Ingress 资源,具体内容很像Nginx本身很像,就不解释了

4.K8S的集群如何使用Ingress配置路由规则时如何传递真实的客户端IP

嗯,过了这么久,终于要回到正题,Ingress-Nginx Controller在路由转发的时候,会不会传递客户端IP呢?

答案是,默认不会!

需要配置,那问题来了,怎么配置?还记得我们需要的字段是哪个吗?X-Forwarded-For!那Ingress-Nginx Controller方案里面怎么配置呢?

回到之前的那句话: 使用K8S里面的 Configmap 在 NGINX 中设置全局配置。

所以需要在Configmap里面配置,这里进行了详细的介绍,笔者简单说下,需要配置以下三个参数:

  • use-forwarded-headers:设置为True时,会将X-Forwarded-*传递过来 配置生效了之后,Nginx的配置结果如下:

注:左边为开启use-forwarded-headers后ingress nginx主配置文件,右边为开启前

  • forwarded-for-header:可以自定义X-Forwarded-For的值
  • compute-full-forwarded-for:使用追加的方式,将请求经过的代理服务器IP全部用逗号,追加

注:左边是未开启compute-full-forwarded-for配置的ingress nginx主配置文件,右边是开启了

当然,这里涉及到一个小技巧,如何查看Ingress-Nginx Controller里面的Nginx配置:

5.K8S的集群如何查看Ingress-Nginx Controller里面的Nginx真实配置

(1)首先进入到Ingress-Nginx Controller的pod中(对,它是一个pod),然后查看线程:

------> kubectl exec -it Ingress-Nginx Controller-XXX /bin/sh ------> ps aux|grep nginx root 352 0.0 0.0 2468624 924 ?? S 10:43上午 0:00.08 nginx: worker processundefinedroot 232 0.0 0.0 2459408 532 ?? S 10:43上午 0:00.02 nginx: master process /usr/local/opt/nginx/bin/ nginx -g daemon off;undefinedroot 2345 0.0 0.0 2432772 640 s000 S+ 1:01下午 0:00.00 grep nginx

上图中,就可以看到nginx的路径为:/usr/local/opt/nginx/bin/nginx

(2)使用nginx的 -t 参数进行配置检查,即可知道实际调用的配置文件路径及是否调用有效,如果有效cat一下就好。

------>/usr/local/opt/nginx/bin/nginx -t nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful ------> cat /usr/local/opt/nginx/bin/nginx

好的,经过这么长时间的探索,我们终于知道了这里怎么设置了,那回到一开始的问题,是不是设置的不对呢?这个问题发生的环境正是使用的K8S的Ingress来配置,且使用的是Ingress-Nginx Controller来做转发,看下这里的值是多少:

破案了,原来真的是这里,

这里再拓展一个小技巧:

6.K8S的集群如何自定义部分Nginx Ingress的日志并查看

需要在configMap 的data字段增加/修改log-format-upstream

当然实际的工程中,可能有多个Ingress服务,需要单独用个文件来配置,避免把全集群都修改了,一般来说,Helm直接下载的组件和其他第三方的组件都会在Nginx原始的yaml文件里面写这样的内容:

这里的value.yaml则是可以使用自定义的yaml限定范围进行覆盖(不要用原始的value.yaml),确保问题可控

具体来说,对于Ingress自定义的configmap,可以绑定对应class,也就是具体使用哪个ingress nginx controller来执行,比如下图就是nginx-1

然后在nginx-1的value.yaml上配置新的日志:

当然了,如果了类似CLB的服务也可以参考这里

三、真实工程环境下的复杂情况

其实,也到这里,问题也找到了,基本的概念也清晰,是不是就可以上手了,其实不是,真实的工程环境会比较复杂,具体来说;

(一)做好上游服务字段的对接

回到之前的Nginx方案,里面提到最外层Nginx配置为:

proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;

实际业务场景中,不一定完全用Nginx来做转发,前面还可能有硬件负载均衡、防火墙,CND等等数据流,CDN厂商有自己的专用获取IP的字段,防火墙也做了一些信息的转换,所以在自己所负责的链路的最前方要跟上游的团队对接好这个字段才行

这里有个比较有意思的问题:负载均衡、防火墙、CDN、服务端真实服务这几个的顺序是什么样的?有知道的同学可以留言一下

一个比较经典的案例如下:案例1

(二)做好底层服务字段的确认

实际上,很多云厂商都有自己的获取IP的标准模式,以腾讯云的TKE为例,这篇文章就提到了如何在 TKE 中获取客户端真实源 IP:

当然,如果部署的是混合云,那问题可能会更加的复杂这篇文章给出了基于腾讯云的混合云的获取IP的方案

(三)从头防篡改X-Forwarded-For的方案

上流数据准确,底层数据准确了,那还有一个问题没有解决,客户端IP在发起时就恶意篡改了,这里就可以用Nginx就尝试用real-ip模块来解决这个问题,对应ingress的configmap配置为enable-real-ip

简单来说,可以在Nginx Ingress的ConfigMap中添加以下

代码语言:yaml
复制
proxy-real-ip-cidr: "10.0.0.0/8"
set-real-ip-from: "IP1"
real-ip-header: "X-Forwarded-For"

这个配置实现了以下几个功能:

  • proxy-real-ip-cidr配置了Nginx Ingress要识别的IP地址段。在本例中,它识别了以10.0.0.0开头的IP地址。
  • set-real-ip-from配置了Nginx Ingress要接受的代理IP地址。在本例中,它接受所有地址。
  • real-ip-header配置了要传递给后端Pod的真实IP地址头。在本例中,它为“X-Forwarded-For”。

这样,即使有伪造也是这样的情况:

X-Forwarded-For: 客户端伪造 IP 地址, IP0(client), IP1(proxy), IP2(proxy)

那么,我们只需启用 realip 模块的 real_ip_recursive 递归模式,将从右往左逐步剔除 IP1 信任代理,最后会获取到真实的客户端 IP 地址。

当然这么做的前提是能拿到最开始的代理服务器的IP

最佳实践在这里

最后:

###拓展的点:

####拓展点1

前面都做了,数据仍然不准,怎么办,如果接入了CDN服务,大概率不能使用X-Forwarded-For等字段来获取,可以跟CDN服务商约定一个字段来获取

####拓展点2.

Service 的 externalTrafficPolicy 字段为 Local,能不能也可以获取到真实的IP? 可以,通过获取Request.RemoteAddr就行了,但那样就意味着,只能将请求转发到Service自身的所在Node上的pod了,这样就可能导致负载不均衡,工程只有在特定情况才使用,具体参考这篇文章

####拓展点3.

golang的Gin框架里面也有一些具体的获取实现,可以用吗?可以,但要冒风险,Gin的代码兼容性有时也做的不好,参考GIn的问题

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、首先看这个客户端的IP在服务端是怎么获取的
    • (一)通过X-Forward-For获取请求IP
      • (二)X-Forward-For与X-Real-IP的不同
      • 二、再看一下这个客户端的IP在路由转发中是怎么传递
        • (一)云厂商的路由转发传递
          • (二)Nginx如何传递客户端的IP
            • (三)其他组件如何获取客户端真实IP
              • (四)K8S环境下如何传递客户端的IP
                • 1.K8S的集群如何外网访问
                • 2.K8S集群的Ingress
                • 3.K8S集群如何使用ingress配置路由规则
                • 4.K8S的集群如何使用Ingress配置路由规则时如何传递真实的客户端IP
                • 5.K8S的集群如何查看Ingress-Nginx Controller里面的Nginx真实配置
            • 三、真实工程环境下的复杂情况
              • (一)做好上游服务字段的对接
                • (二)做好底层服务字段的确认
                  • (三)从头防篡改X-Forwarded-For的方案
                  • 最后:
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档