Kubernetes externalTrafficPolicy获取客户端IP

1、先决条件

您必须具有可用的 Kubernetes 1.5 集群才能运行本文档中的示例。这些示例使用一个小的 Nginx Web 服务器,它会显示通过HTTP头接收的请求的一切信息,包括客户端IP。您可以按如下方式创建它:

$ kubectl create deployment source-ip-app --image=idoall/echoserver:1.10
deployment.apps/source-ip-app created

 

2、具有 Type = ClusterIP 服务的客户端IP

如果您在 iptables 模式下运行 kube-proxy,则从群集内发送到 ClusterIP 的数据包永远不会来自 DNAT ,这是Kubernetes 1.2 以来的默认设置。Kube-proxy 通过访问proxyMode endpoint 可以查看公开模式:

$ curl localhost:10249/proxyMode
iptables

 

接下来我们创建一个服务来测试客户端IP:

$ kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080
service/clusterip exposed

$ kubectl get svc clusterip
NAME        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
clusterip   ClusterIP   10.245.180.210   <none>        80/TCP    8s

刚才我们只是部署了一个名为 source-ip-app 的Pod ,通过 kubectl expose 将资源暴露为新的 Kubernetes Service,名称为 clusterip

接下来创建一个 pod 尝试获取 clusterip 测试

$  kubectl run busybox -it --image=busybox --restart=Never --rm
If you don't see a command prompt, try pressing enter.
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether 5e:69:18:64:dd:94 brd ff:ff:ff:ff:ff:ff
    inet 10.244.5.27/24 scope global eth0
       valid_lft forever preferred_lft forever

/ # wget -qO - 10.245.180.210


Hostname: source-ip-app

Pod Information:
    -no pod information available-

Server values:
    server_version=nginx: 1.13.3 - lua: 10008

Request Information:
    client_address=10.244.5.27
    method=GET
    real path=/
    query=
    request_version=1.1
    request_scheme=http
    request_uri=http://10.245.180.210:8080/

Request Headers:
    connection=close
    host=10.245.180.210
    user-agent=Wget

Request Body:
    -no body in request-

/ #

无论何种访问方式,可以发现获取到的 client_address 地址,永远都是客户端所在容器的IP 10.244.5.27
 

3、通过 Type=NodePort 获取客户端IP

从 Kubernetes 1.5 开始, 默认情况下,发送到 Type = NodePort 的服务数据包是来自 DNAT。接下来我们再创建一个类型是 NodePort 的服务,来对应刚才创建的部署 source-ip-app

$ kubectl expose deployment source-ip-app --name=nodeport --port=80 --target-port=8080 --type=NodePort
service/nodeport exposed

 

$ NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
$ NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address}')
$ for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done
    client_address=10.244.3.0
    client_address=10.244.4.0
    client_address=10.244.5.1
    client_address=10.244.0.0
    client_address=10.244.1.0
    client_address=10.244.2.0

通过上面的方法能够看到,我们从不同节点去访问 NodePort ,得到的也并不是真正的客户端IP,而是集群IP

处理流程如下:
* 客户端发送数据包 node2:nodePort
node2 用自己的IP地址替换数据包中的源IP地址(SNAT)
node2 使用 pod IP 替换数据包上的目标 IP
* 数据包路由到 node1,然后路由到 endpoint
* pod的回复被路由回 node2
* pod的回复被发送回客户端

          client
             \ ^
              \ \
               v \
   node 1 <--- node 2
    | ^   SNAT
    | |   --->
    v |
 endpoint

为避免这种情况,Kubernetes 具有保留客户端IP 的功能。设置service.spec.externalTrafficPolicy 为 Local 会将请求代理到本地端点,不将流量转发到其他节点,从而保留原始IP地址。如果没有本地端点,则丢弃发送到节点的数据包,因此您可以在任何数据包处理规则中依赖正确的客户端IP。

设置 service.spec.externalTrafficPolicy 字段如下:

$ kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'
service/nodeport patched

现在,重新运行测试:

for node in $NODES; do curl --connect-timeout 1 -s $node:$NODEPORT | grep -i client_address; done
    client_address=10.0.0.100
    client_address=10.0.0.100

这时获取到的正确IP地址,只能是在当前节点的IP地址。
处理流程如下:
* 客户端发送数据包 node2:nodePort,没有任何 endpoint
* 数据包被丢
* 客户端发送数据包 node1:nodePort,存在 endpoint
node1 使用正确的源IP将数据包路由到 endpoint

        client
       ^ /   \
      / /     \
     / v       X
   node 1     node 2
    ^ |
    | |
    | v
 endpoint

 

 

4、通过 Type = LoadBalancer 获取客户端IP

从 Kubernetes 1.5 开始,默认情况下,发送到具有 Type = LoadBalancer 的服务的数据包来源是 DNAT,因为该 Ready 状态中的所有可调度 Kubernetes 节点都能获得负载平衡流量。因此,如果数据包到达没有 endpoint 的 Node,系统会将其代理到具有 endpoint 的 Node,用这个 Node 的IP替换数据包上的源IP(如上一节所述)。

您可以通过负载均衡器公开 source-ip-app 来测试这一点

$ kubectl expose deployment source-ip-app --name=loadbalancer --port=80 --target-port=8080 --type=LoadBalancer
service/loadbalancer exposed

$ kubectl get svc loadbalancer
NAME           TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
loadbalancer   LoadBalancer   10.245.176.219   192.168.11.221   80:30821/TCP   57s

$ curl -s 192.168.11.221 | grep -i client_address
    client_address=10.244.0.0

通过上面的实例可以看到,获取到的 client_address 还是集群内的IP。

通过设置相同的 service.spec.externalTrafficPolicy 字段以 Local 强制没有服务端点的节点通过故意失败的运行状况检查将自己从符合加载平衡流量的节点列表中删除。

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

您可以通过下面的代码来测试:

$ kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'
service/loadbalancer patched

你应该立即看到service.spec.healthCheckNodePortKubernetes 分配的字段

$ kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort
  healthCheckNodePort: 32131

该 service.spec.healthCheckNodePort 字段指向服务于运行状况检查的每个节点上的端口 /healthz。可以通过以下字段测试:

$ kubectl get pod -o wide -l run=source-ip-app
NAME                            READY   STATUS    RESTARTS   AGE   IP             NODE                  NOMINATED NODE   READINESS GATES
source-ip-app                   1/1     Running   0          47m   10.244.4.179   k8s-dev-datanode-02   <none>           <none>
source-ip-app-784589f58-rk95c   1/1     Running   0          47m   10.244.5.26    k8s-dev-datanode-03   <none>           <none>

[root@k8s-dev-datanode-01 ~]# curl localhost:32131
{
    "service": {
        "namespace": "default",
        "name": "loadbalancer"
    },
    "localEndpoints": 0

[root@k8s-dev-datanode-02 ~]# curl localhost:32131
{
    "service": {
        "namespace": "default",
        "name": "loadbalancer"
    },
    "localEndpoints": 1

在 master 服务器上会进行负载均衡分配 ,当执行此操作时,它会分配指向每个节点上的端口/路径的HTTP运行状况检查。对于没有端点的2个节点等待大约10秒钟的健康检查失败,然后会指向正确的IP地址:

$ curl -s 192.168.11.221 | grep -i client_address
    client_address=192.168.11.175

只有在所在节点 datanode-02 上运行,才可以访问

 

 

5、删除服务

$ kubectl delete svc -l app=source-ip-app

删除标签是 app: source-ip-app 的服务

删除 DeploymentReplicaSet 和 Pod

$ kubectl delete deployment source-ip-app

6、参考文章

source-ip

 

发表评论