Kubernetes获取用户真实IP
为什么获取客户端真实源IP
当需要能感知到服务请求来源去满足一些业务需求时,就需要后端服务能准确获取到请求客户端的真实源 IP
- 对服务请求的来源有做审计的需求,如异地登陆告警
- 针对安全攻击或安全事件溯源需求,如APT攻击、DDoS攻击等
- 业务场景数据分析需求,如业务请求区域统计
- 其他需要获取客户端地址的需求
集群内的客户端连接到服务的时候,是支持服务的Pod可以获取到客户端的 IP 地址的,Kubernetes依靠kube-proxy组件实现Service的通信与负载均衡,在这个过程中由于使用了SNAT对源地址进行了转换,因此数据包的源IP地址会发生变化,导致Pod中的服务拿不到真实的客户端IP地址信息
获取客户端真实源IP方法
通过Service资源配置选项保留客户端源IP
比如下面nginx服务应用
[root@k8s01 ~]# vim nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.18
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
[root@k8s01 ~]# kubectl apply -f nginx.yaml
deployment.apps/nginx created
service/nginx created
查看服务状态
[root@k8s01 ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6dfc66cc6c-7h5f4 1/1 Running 0 46s 10.244.247.68 k8s03.op.local <none> <none>
nginx-6dfc66cc6c-xkzp5 1/1 Running 0 46s 10.244.197.2 k8s02.op.local <none> <none>
[root@k8s01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.96.98.9 <none> 80:30080/TCP 52s
看到Pod被分配到了2个不同的节点,我们通过master节点的NodePort端口来访问下服务,这个时候我们查看nginx的Pod日志可以看到其中获取到的clientIP是10.244.230.192 ,并不是我们期望的真正的浏览器端访问的IP地址
[root@k8s01 ~]# kubectl logs nginx-6dfc66cc6c-7h5f4 -f
...
10.244.230.192 - - [23/Jun/2022:13:50:29 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
10.244.230.192 - - [23/Jun/2022:13:50:34 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
要保留客户端IP,可在Service资源中配置字段Service.spec.externalTrafficPolicy,此字段表示服务是否希望将外部流量路由到节点本地或集群范围的端点,有两个选项值:Cluster(默认)和Local方式
Cluster 表示隐藏了客户端源 IP, LoadBalancer和NodePort类型服务流量可能会被转发到其他节点的Pods; Local表示保留客户端源IP并避免LoadBalancer和NodePort类型的服务流量转发到其他节点的Pods,如果本地没有本地Pod存在,则连接将挂起,比如我们这里设置上该字段更新,这个时候我们去通过master节点的NodePort访问应用是访问不到的,因为master节点上并没有对应的Pod运行,所以需要确保负载均衡器将连接转发给至少具有一个Pod的节点
[root@k8s01 ~]# vim nginx.yaml
...
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080
externalTrafficPolicy: Local
访问测试
[root@k8s01 ~]# kubectl logs nginx-6dfc66cc6c-7h5f4 -f
223.166.104.62 - - [23/Jun/2022:14:08:29 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
223.166.104.62 - - [23/Jun/2022:14:08:43 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
223.166.104.62 - - [23/Jun/2022:14:08:49 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
需要注意的是使用这个参数有一个缺点,通常情况下请求都是均匀分布在所有Pod上的,但是使用了这个配置的话,情况就有可能不一样了。比如有两个节点上运行了3个Pod,假如节点A运行一个Pod,节点B运行两个Pod,如果负载均衡器在两个节点间均衡分布连接,则节点 A上的Pod将接收到所有请求的50%,但节点B上的两个Pod每个就只接收 25%。由于增加了externalTrafficPolicy: Local 这个配置后,接收请求的节点和目标Pod 都在一个节点上,所以没有额外的网络跳转(不执行 SNAT),所以就可以拿到正确的客户端 IP
优点:只需要修改 Kubernets Service资源配置即可
缺点:会存在潜在的Pods(Endpoints)流量负载不均衡风险
通过LB直通Pod转发模式获取真实源IP
生产环境,通常会有多个节点同时接收客户端的流量,如果仅使用Local模式将会导致服务可访问性变低。引入LB的目的是为了利用其探活的特点,仅将流量转发到存在服务Pod的节点上,后端Pods收到的请求的源IP即是客户端真实源IP,此方式无论是在四层还是七层服务的转发场景下都适用
通过LB-Ingress获取真实源IP
如果每一个服务都占用一个LB,成本很高,同时配置不够灵活,每次新增服务时都需要去LB增加新的端口映射,还有一种方案是LB将80、443的流量导给Ingress Controller,然后将流量转发到Service,接着达到Pod中的服务。此时需要LB能做TCP层的透传,或者HTTP层的带真实IP转发,将Ingress Controller的externalTrafficPolicy设置为Local模式,而Service可以不必设置为Local模式。如果想要提高可访问性,同样可以配置反亲和性,保证在每个后端节点上都有Ingress Controller,如果DaemonSet部署的话,将开启use-forwarded-headers(https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#forwarded-for-header)
在七层(HTTP/HTTPS)服务转发场景下,可以通过获取Http Header中X-Forwarded-For和X-Real-IP字段的值来获取客户端真实源IP
[root@k8s01 ingress-nginx]# vim cm.yaml
...
data:
#把真实IP地址传给后端
compute-full-forwarded-for: "true"
forwarded-for-header: "X-Forwarded-For"
use-forwarded-headers: "true"
...
通过域名访问服务即可获取真实IP
[root@k8s01 ~]# kubectl logs nginx-6dfc66cc6c-7h5f4 -f
223.166.104.62 - - [23/Jun/2022:14:08:29 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
223.166.104.62 - - [23/Jun/2022:14:08:43 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
223.166.104.62 - - [23/Jun/2022:14:08:49 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
总结
三种获取真实源IP的方式
- 直接通过NortPort访问获取真实IP
受制于Local模式可能会导致服务不可访问,需要保证对外提供入口的节点上必须具有服务的负载
- 通过LB->Service访问获取真实IP
利用LB的探活能力,能够提高服务的可访问性。适用于服务较少或者愿意每个服务一个 LB 的场景
- 通过 LB->Ingress->Service访问获取真实 IP
通过LB将 80、443端口的流量转到Ingress Controller ,再进行服务分发,但Ingress Controller使用Local模式,就要求LB的每个后端节点都有Ingress Controller副本,适用于对外暴露服务数量较多的场景
当然也可以组合使用,对于并不需要获取客户端真实IP的服务,可以继续使用Cluster模式