通常,为了访问kubernetes集群中的pod,我们会使用service的形式。以下,就不同的 service 类型来探讨对source ip的影响。

kubernetes的官网对此也有说明:
https://kubernetes.io/docs/tutorials/services/source-ip/

在此简单总结一下(网络插件使用calico, 其他插件可能有不同,在此不做讨论):

ClusterIP

在创建service 的时候,如果不声明type 字段,则默认为 ClusterIP. 其原理就是利用iptables 对发送到clusterip 的报文进行转发。

[root@walker-1 hehe]# kubectl describe svc nginx
Name:              nginx
Namespace:         default
Labels:            app=nginx
Selector:          app=nginx
Type:              ClusterIP
IP:                10.96.9.42
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.187.212:80
Session Affinity:  None

如果是ClusterIP, 一般用于集群内pod之间的访问。
1. 如果client和server在同一个node上,那么从server观察到的source ip 为client的真实IP。
2. 如果client和server分别处于node A, node B上,那么从server段观察到source ip的就是 node B的IP(pod外出的流量会被做SNAT)。

NodePort

NodePort 可以认为是构建在 ClusterIP 之上的。如果指定service 类型为NodePort,那么在每个 node 上都会监听同一个端口,并通过iptables对其进行转发。

请求有两种情况:

  • pod不在所请求的node上
toplogy:
          client
             \ ^
              \ \
               v \
   node 1 <--- node 2
    | ^   SNAT
    | |   --->
    v |
 endpoint
  1. 客户端将请求发送到node 2
  2. node 2 根据iptables 中的转发策略,将报文转发至pod所在节点 node 1
  3. node 1 根据本地路由将报文发送给 pod
  4. node 1 将pod的响应报文做SNAT后,发给node 2
  5. node 2 回复给client

(此时pod所见的source ip为node 2的IP)

  • pod在所请求的node上
toplogy:
   client
    | ^   
    | |SNAT 
    v |
   node 1 
    | ^   
    | |   
    v |
  endpoint
  1. 客户端请求node1:nodeport
  2. node 1 根据iptables,通过SNAT 将原地址改为node1 ip, 通过DNAT 将目的地址改为pod ip
  3. node 1 将pod响应报文通过SNAT 返回给客户端

(此时pod所见的source ip 为node 1的IP)

这就很烦了,怎么都获取不到正确的用户ip。好在NodePort模式还提供了{"spec":{"externalTrafficPolicy":"Local"}} 参数

加上以后效果是这样的:

toplogy:
        client
       ^ /   \
      / /     \
     / v       X
   node 1     node 2
    ^ |
    | |
    | v
  endpoint
  1. 客户端请求node1:nodeport
  2. node 1 根据iptables, 通过DNAT将目的地址改为pod ip
  3. node 1 将pod响应报文通过SNAT返回给客户端

(此时pod所见的source ip为client ip)

注:发往node2的报文会被丢弃,因为报文不会再做SNAT,来将client ip替换为node2 ip了。

LoadBalancer

默认也会做SNAT,替换客户端IP。

However, if you’re running on Google Kubernetes Engine/GCE, setting the same service.spec.externalTrafficPolicy field to Local forces nodes without Service endpoints to remove themselves from the list of nodes eligible for loadbalanced traffic by deliberately failing health checks.

Visually:

                      client
                        |
                      lb VIP
                     / ^
                    v /
health check --->   node 1   node 2 <--- health check
        200  <---   ^ |             ---> 500
                    | V
                 endpoint

总结

要让 pod 能正常获取客户端ip大致有如下几种方式:
1. 可以使用 NodePort + {"spec":{"externalTrafficPolicy":"Local"}} 的配置来实现。
2. 还有个解决思路就是利用 INGERSS。INGRESS 本质上是监听物理机端口,然后直接将客户端请求转发至service。可以在INGRESS 请求转发阶段将客户端IP 带到请求头中。
3. pod直接使用 HOST 网络模式。

第三种方式最便捷,但容易造成端口冲突。安全问题也有待考量,因此不推荐。
第二种方式最灵活,即使pod分布在不同node上也可以通过统一入口访问,官方INGRESS是由nginx实现的,这样一来花样就多了(甚至可以做流控,认证功能)。推荐使用。
第一种方式折中吧,因为还没试过当service 的多个endpoint 分布在不同节点上的情况。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐