资讯专栏INFORMATION COLUMN

获取真实客户端IP 容器云 UK8S

ernest.wang / 3486人阅读

摘要:服务本身的源码非常简单,只返回客户端地址,如下所示。结果显示用户的访问地址是一台云主机的内网地址,显然不正确。重新部署服务后,再用浏览器访问,可以发现正确获取了浏览器的访问。

获取真实客户端IP

网络编程中如何获得对端IP

如果是HTTP1.1协议,一般的反向代理或者负载均衡设备(如ULB7)支持X-Forwarded-For头部字段,会在用户的请求报文中加入类似X-Forwarded-For:114.248.238.236的头部。Web应用程序只需要解析该头部即可获得用户真实IP。

如果是TCP或UDP自定义协议,可以客户端在协议字段里定义一个大端unsigned字段来保存自身IP,服务端把该字段解析出来然后调用inet_ntoa(3)等函数获得ipv4点分字符串。

如果2中协议不支持填写自身IP,则服务端可以通过socket系统调用getpeername(2)来获取对端地址。下文讨论此方式。

Kubernetes Loadbalancer ULB4碰到的问题

UK8S使用ULB4和ULB7来支持Loadbalancer类型的Service。对于ULB7,由于只支持HTTP协议且默认支持X-Forwarded-For头部,所以Web服务可以很容易获取客户端的真实IP。但对于使用ULB4接入的纯四层协议的服务来说,可能需要使用getpeername(2)来获取客户端真实IP。然而由于目前kube-proxy采用Iptables模式,后端pod内的应用程序的网络库调用getpeername(2)会无法获得正确的IP地址。以下例子可以说明问题。

部署一个简单的webserver,通过Loadbalancer ULB4外网模式接入。

apiVersion: v1
kind: Service
metadata:
  name: ucloud-nginx
  labels:
    app: ucloud-nginx
  annotations:
    service.beta.kubernetes.io/ucloud-load-balancer-type: "outer"
    service.beta.kubernetes.io/ucloud-load-balancer-vserver-method: "source" 
spec:
  type: LoadBalancer
  ports:
    - protocol: "TCP"
      port: 80
      targetPort: 12345
  selector:
    app: ucloud-nginx
---
apiVersion: v1
kind: Pod
metadata:
  name: test-nginx
  labels:
    app: ucloud-nginx
spec:
  containers:
  - name: nginx
    image: uhub.service.ucloud.cn/uk8s/uk8s-helloworld:stable
    ports:
     - containerPort: 12345

部署完毕后,Service状态如下所示,可以通过EIP 117.50.3.206访问该服务。

# kubectl  get svc ucloud-nginx
NAME           TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
ucloud-nginx   LoadBalancer   172.17.179.247   117.50.3.206   80:43832/TCP   112s

服务本身的源码非常简单,只返回客户端地址,如下所示。

package main
import (
    "fmt"
    "io"
    "log"
    "net/http"
    "net/http/httputil"
)
func main() {
    log.Println("Server hello-world")
    http.HandleFunc("/", AppRouter)
    http.ListenAndServe(":12345", nil)
}
func AppRouter(w http.ResponseWriter, r *http.Request) {
    dump, _ := httputil.DumpRequest(r, false)
    log.Printf("%q
", dump)
    io.WriteString(w, fmt.Sprintf("Guest come from %v
", r.RemoteAddr))
    return
}

在外网通过浏览器访问该服务,如下所示。

结果显示用户的访问IP地址是一台云主机的内网IP地址,显然不正确。

原因解释

Loadbalancer创建成功后,ULB4的VServer将UK8S集群中的每个Node云主机节点作为自身的RS节点,RS端口为Service申明的Port值(注意不是NodePort)。ULB4将访问流量转发到其中一个RS后,RS根据本机上kube-proxy生成的iptables规则将流量DNAT到后端Pod中,如下所示。

图中ULB4先将流量转发到Node1中,Node1中根据iptables DNAT规则,将流量转发给Node2中的Pod。 需要注意的是,Node1将IP包转发到Node2前,对这个包有一次SNAT操作。准确地说,是一次MASQUERADE操作,规则如下。

-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE

这条规则将源地址改成Node1的本机地址,即10.9.31.26。当然,这个 SNAT 操作只针对本Service转发出来的包,否则普通的IP包也受到影响了,而判定IP包是否由本Service转发出来的依据是改包上是有有个"0x4000"标志,这个标志则是在DNAT操作前打上去的。

由于IP请求包的源地址被修改,Pod内的程序网络库通过getpeername(2)调用获取到的对端地址是Node1的IP地址而不是客户端真实的地址。

为什么需要对流出的包做SNAT操作呢?

原因比较简单。参考下图,当Node1上的Pod处理完请求后,需要发送响应包,如果没有SNAT操作,Pod收到的请求包源地址就是client的IP地址,这时候Pod会直接将响应包发给client的IP地址,但对于client程序来说,它明明没有往PodIP发送请求包,却收到来自Pod的IP包,这个包很可能会被client丢弃。而有了SNAT,Pod会将响应包发给Node1,Node1再根据DNAT规则产生的conntrack记录,将响应包通过返回给client。

       client
         ^
          
          v 
           ulb4
             ^
              
              v 
  node 1 <--- node 2    
   | ^   SNAT
   | |   --->
   v |
endpoint
如何获取源IP?

对于Pod需要明确知道客户端来源地址的情况,我们需要显示地将Service的spec.externalTrafficPolicy设置成Local,如下修改。

apiVersion: v1
kind: Service
metadata:
  name: ucloud-nginx
  labels:
    app: ucloud-nginx
  annotations:
    service.beta.kubernetes.io/ucloud-load-balancer-type: "outer"
    service.beta.kubernetes.io/ucloud-load-balancer-vserver-method: "source"
spec:
  type: LoadBalancer
  ports:
    - protocol: "TCP"
      port: 80
      targetPort: 12345
  selector:
    app: ucloud-nginx
  externalTrafficPolicy: Local

重新部署服务后,再用浏览器访问,可以发现Pod正确获取了浏览器的访问IP。

而这个机制的原理也很简单,当设置了externalTrafficPolicy为Local时,Node上的iptables规则会设置只将IP包转发到在本机上运行的Pod,如果本机上无对应Pod在运行,此包将被DROP。如下图,这样Pod可以直接使用client的源地址进行回包而不需要SNAT操作。

     client
       ^
        
        v  
       ulb4
      ^ /   
     / /       VServer健康检查失败
    / v       X
  node 1     node 2
   ^ |
   | |
   | v
endpoint

对于其他未运行Service对应Pod的Node节点来说,ULB VServer对其健康检查探测会因为iptables的DROP规则而失败,这样来自用户的请求永远不会被发往这些节点上,可以确保这些请求都能被正确响应。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/126937.html

相关文章

  • 容器 UK8S】服务发现:ULB属性修改的处理方法和获取真实户端IP

    摘要:原因解释创建成功后,的将集群中的每个云主机节点作为自身的节点,端口为申明的值注意不是。如何获取源对于需要明确知道客户端来源地址的情况,我们需要显示地将的设置成如下修改。重新部署服务后,再用浏览器访问,可以发现正确获取了浏览器的访问。ULB属性修改的处理方法如没有实际需要,请避免修改ULB名称及注释根据cloudprovider插件使用提醒,由UK8S cloudprovider创建的ULB不...

    Tecode 评论0 收藏0
  • 容器UK8S】新手指导

    摘要:详细请见产品价格产品概念使用须知名词解释漏洞修复记录集群节点配置推荐模式选择产品价格操作指南集群创建需要注意的几点分别是使用必读讲解使用需要赋予的权限模式切换的切换等。UK8S概览UK8S是一项基于Kubernetes的容器管理服务,你可以在UK8S上部署、管理、扩展你的容器化应用,而无需关心Kubernetes集群自身的搭建及维护等运维类工作。了解使用UK8S为了让您更快上手使用,享受UK...

    Tecode 评论0 收藏0
  • 网络插件 升级 容器 UK8S

    摘要:提供在线升级的功能,插件升级不会影响现有的网络。升级功能开启后,即可看到插件版本信息,点击升级即可进行升级。年月日修复固定意外释放导致不可用的问题修复抢占文件锁超时导致释放失败的问题插件开启后,将默认使用其管理。 网络插件 升级本篇目录1. 网络插件升级2. 网络插件更新纪要UK8S 提供的 CNI (Container Network Interface)基于 UCloud VPC 网络实...

    ernest.wang 评论0 收藏724
  • 容器 UK8S】应用商店:安装应用和管理应用

    摘要:更新商店信息查询应用安装商店应用安装商店应用执行了安装商店应用的命令后,我们看到了系统返回给我们了安装的详细信息。安装应用安装商店应用按照前文helm工具已经安装完成,接下来通过helm客户端在kubernetes集群中创建一个应用,执行安装前最好先进行应用商店的同步,以获得最新的应用信息。#更新商店信息 helm repo update #查询tomcat应用 helm search to...

    Tecode 评论0 收藏0
  • 容器 UK8S】集群常见问题:UK8S创建Pod失败,使用kubectl describe po

    摘要:集群常见问题单个集群最多能添加多少个节点当前单个集群对应节点数量可查看集群节点配置推荐。创建失败,使用发现报错为,是啥原因在创建等资源时,都需要扮演云账户的身份调用来完成相关操作。集群内可以解析,但无法联通外网拉取数据失败。集群常见问题单个集群最多能添加多少个节点?A:当前单个UK8S集群对应节点数量可查看集群节点配置推荐。UK8S完全兼容原生Kubernetes API吗?A:完全兼容。U...

    Tecode 评论0 收藏0
  • 容器 UK8S】服务发现:通过ULB暴露Kubernetes Dashboard和Ingress

    摘要:通过暴露是社区的一个开源项目,你可以通过来部署更新应用排查应用故障以及管理集群资源。执行以下命令安装,使用的镜像已经去掉了的证书限制。不支持的版本范围。通过ULB暴露Kubernetes DashboardDashboard是Kubernetes社区的一个Web开源项目,你可以通过Dashboard来部署更新应用、排查应用故障以及管理Kubernetes集群资源。另外,Dashboard还提...

    Tecode 评论0 收藏0

发表评论

0条评论

ernest.wang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<