Kubernetes.Service—使用源 IP

简介: Kubernetes.Service—使用源 IP

 使用源 IP

运行在 Kubernetes 集群中的应用程序通过 Service 抽象发现彼此并相互通信,它们也用 Service 与外部世界通信。 本文解释了发送到不同类型 Service 的数据包的源 IP 会发生什么情况,以及如何根据需要切换此行为。

准备开始

术语表

本文使用了下列术语:

NAT网络地址转换Source NAT替换数据包上的源 IP;在本页面中,这通常意味着替换为节点的 IP 地址Destination NAT替换数据包上的目标 IP;在本页面中,这通常意味着替换为 Pod 的 IP 地址VIP一个虚拟 IP 地址,例如分配给 Kubernetes 中每个 Service 的 IP 地址Kube-proxy一个网络守护程序,在每个节点上协调 Service VIP 管理

先决条件

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

    • Killercoda
    • 玩转 Kubernetes

    示例使用一个小型 nginx Web 服务器,服务器通过 HTTP 标头返回它接收到的请求的源 IP。 你可以按如下方式创建它:

    kubectl create deployment source-ip-app --image=registry.k8s.io/echoserver:1.4

    image.gif

    输出为:

    deployment.apps/source-ip-app created

    image.gif

    教程目标

      • 通过多种类型的 Service 暴露一个简单应用
      • 了解每种 Service 类型如何处理源 IP NAT
      • 了解保留源 IP 所涉及的权衡

      Type=ClusterIP类型 Service 的源 IP

      如果你在 iptables 模式(默认)下运行 kube-proxy,则从集群内发送到 ClusterIP 的数据包永远不会进行源 NAT。 你可以通过在运行 kube-proxy 的节点上获取 http://localhost:10249/proxyMode 来查询 kube-proxy 模式。

      kubectl get nodes

      image.gif

      输出类似于:

      NAME                           STATUS     ROLES    AGE     VERSION
      kubernetes-node-6jst   Ready      <none>   2h      v1.13.0
      kubernetes-node-cx31   Ready      <none>   2h      v1.13.0
      kubernetes-node-jj1t   Ready      <none>   2h      v1.13.0

      image.gif

      在其中一个节点上获取代理模式(kube-proxy 监听 10249 端口):

      # 在要查询的节点上的 Shell 中运行
      curl http://localhost:10249/proxyMode

      image.gif

      输出为:

      iptables

      image.gif

      你可以通过在源 IP 应用程序上创建 Service 来测试源 IP 保留:

      kubectl expose deployment source-ip-app --name=clusterip --port=80 --target-port=8080

      image.gif

      输出为:

      service/clusterip exposed

      image.gif

      kubectl get svc clusterip

      image.gif

      输出类似于:

      NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
      clusterip    ClusterIP   10.0.170.92   <none>        80/TCP    51s

      image.gif

      并从同一集群中的 Pod 中访问 ClusterIP:

      kubectl run busybox -it --image=busybox:1.28 --restart=Never --rm

      image.gif

      输出类似于:

      Waiting for pod default/busybox to be running, status is Pending, pod ready: false
      If you don't see a command prompt, try pressing enter.

      image.gif

      然后,你可以在该 Pod 中运行命令:

      # 从 “kubectl run” 的终端中运行
      ip addr

      image.gif

      1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
          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
          inet6 ::1/128 scope host
             valid_lft forever preferred_lft forever
      3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1460 qdisc noqueue
          link/ether 0a:58:0a:f4:03:08 brd ff:ff:ff:ff:ff:ff
          inet 10.244.3.8/24 scope global eth0
             valid_lft forever preferred_lft forever
          inet6 fe80::188a:84ff:feb0:26a5/64 scope link
             valid_lft forever preferred_lft forever

      image.gif

      然后使用 wget 查询本地 Web 服务器:

      # 将 “10.0.170.92” 替换为 Service 中名为 “clusterip” 的 IPv4 地址
      wget -qO - 10.0.170.92

      image.gif

      CLIENT VALUES:
      client_address=10.244.3.8
      command=GET
      ...

      image.gif

      不管客户端 Pod 和服务器 Pod 位于同一节点还是不同节点,client_address 始终是客户端 Pod 的 IP 地址。

      Type=NodePort类型 Service 的源 IP

      默认情况下,发送到 Type=NodePort 的 Service 的数据包会经过源 NAT 处理。你可以通过创建一个 NodePort 的 Service 来测试这点:

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

      image.gif

      输出为:

      service/nodeport exposed

      image.gif

      NODEPORT=$(kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services nodeport)
      NODES=$(kubectl get nodes -o jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }')

      image.gif

      如果你在云供应商上运行,你可能需要为上面报告的 nodes:nodeport 打开防火墙规则。 现在你可以尝试通过上面分配的节点端口从集群外部访问 Service。

      for node in $NODES; do curl -s $node:$NODEPORT | grep -i client_address; done

      image.gif

      输出类似于:

      client_address=10.180.1.1
      client_address=10.240.0.5
      client_address=10.240.0.3

      image.gif

      请注意,这些并不是正确的客户端 IP,它们是集群的内部 IP。这是所发生的事情:

        • 客户端发送数据包到 node2:nodePort
        • node2 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT)
        • node2 将数据包上的目标 IP 替换为 Pod IP
        • 数据包被路由到 node1,然后到端点
        • Pod 的回复被路由回 node2
        • Pod 的回复被发送回给客户端

        用图表示:

        image.gif编辑

        转存失败重新上传取消image.gif编辑

        如图。使用 SNAT 的源 IP(Type=NodePort)

        为避免这种情况,Kubernetes 有一个特性可以保留客户端源 IP。 如果将 service.spec.externalTrafficPolicy 设置为 Local, kube-proxy 只会将代理请求代理到本地端点,而不会将流量转发到其他节点。 这种方法保留了原始源 IP 地址。如果没有本地端点,则发送到该节点的数据包将被丢弃, 因此你可以在任何数据包处理规则中依赖正确的源 IP,你可能会应用一个数据包使其通过该端点。

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

        kubectl patch svc nodeport -p '{"spec":{"externalTrafficPolicy":"Local"}}'

        image.gif

        输出为:

        service/nodeport patched

        image.gif

        现在,重新运行测试:

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

        image.gif

        输出类似于:

        client_address=198.51.100.79

        image.gif

        请注意,你只从运行端点 Pod 的节点得到了回复,这个回复有正确的客户端 IP。

        这是发生的事情:

          • 客户端将数据包发送到没有任何端点的 node2:nodePort
          • 数据包被丢弃
          • 客户端发送数据包到必有端点的 node1:nodePort
          • node1 使用正确的源 IP 地址将数据包路由到端点

          用图表示:

          image.gif编辑

          转存失败重新上传取消image.gif编辑

          如图。源 IP(Type=NodePort)保存客户端源 IP 地址

          Type=LoadBalancer类型 Service 的源 IP

          默认情况下,发送到 Type=LoadBalancer 的 Service 的数据包经过源 NAT处理,因为所有处于 Ready 状态的可调度 Kubernetes 节点对于负载均衡的流量都是符合条件的。 因此,如果数据包到达一个没有端点的节点,系统会将其代理到一个带有端点的节点,用该节点的 IP 替换数据包上的源 IP(如上一节所述)。

          你可以通过负载均衡器上暴露 source-ip-app 进行测试:

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

          image.gif

          输出为:

          service/loadbalancer exposed

          image.gif

          打印 Service 的 IP 地址:

          kubectl get svc loadbalancer

          image.gif

          输出类似于:

          NAME           TYPE           CLUSTER-IP    EXTERNAL-IP       PORT(S)   AGE
          loadbalancer   LoadBalancer   10.0.65.118   203.0.113.140     80/TCP    5m

          image.gif

          接下来,发送请求到 Service 的 的外部 IP(External-IP):

          curl 203.0.113.140

          image.gif

          输出类似于:

          CLIENT VALUES:
          client_address=10.240.0.5
          ...

          image.gif

          然而,如果你在 Google Kubernetes Engine/GCE 上运行, 将相同的 service.spec.externalTrafficPolicy 字段设置为 Local, 故意导致健康检查失败,从而强制没有端点的节点把自己从负载均衡流量的可选节点列表中删除。

          用图表示:

          image.gif编辑

          转存失败重新上传取消image.gif编辑

          你可以通过设置注解进行测试:

          kubectl patch svc loadbalancer -p '{"spec":{"externalTrafficPolicy":"Local"}}'

          image.gif

          你应该能够立即看到 Kubernetes 分配的 service.spec.healthCheckNodePort 字段:

          kubectl get svc loadbalancer -o yaml | grep -i healthCheckNodePort

          image.gif

          输出类似于:

          healthCheckNodePort: 32122

          image.gif

          service.spec.healthCheckNodePort 字段指向每个在 /healthz 路径上提供健康检查的节点的端口。你可以这样测试:

          kubectl get pod -o wide -l app=source-ip-app

          image.gif

          输出类似于:

          NAME                            READY     STATUS    RESTARTS   AGE       IP             NODE
          source-ip-app-826191075-qehz4   1/1       Running   0          20h       10.180.1.136   kubernetes-node-6jst

          image.gif

          使用 curl 获取各个节点上的 /healthz 端点:

          # 在你选择的节点上本地运行
          curl localhost:32122/healthz

          image.gif

          1 Service Endpoints found

          image.gif

          在不同的节点上,你可能会得到不同的结果:

          # 在你选择的节点上本地运行
          curl localhost:32122/healthz

          image.gif

          No Service Endpoints Found

          image.gif

          在控制平面上运行的控制器负责分配云负载均衡器。 同一个控制器还在每个节点上分配指向此端口/路径的 HTTP 健康检查。 等待大约 10 秒,让 2 个没有端点的节点健康检查失败,然后使用 curl 查询负载均衡器的 IPv4 地址:

          curl 203.0.113.140

          image.gif

          输出类似于:

          CLIENT VALUES:
          client_address=198.51.100.79
          ...

          image.gif

          跨平台支持

          只有部分云提供商为 Type=LoadBalancer 的 Service 提供保存源 IP 的支持。 你正在运行的云提供商可能会以几种不同的方式满足对负载均衡器的请求:

            1. 使用终止客户端连接并打开到你的节点/端点的新连接的代理。 在这种情况下,源 IP 将始终是云 LB 的源 IP,而不是客户端的源 IP。
            2. 使用数据包转发器,这样客户端发送到负载均衡器 VIP 的请求最终会到达具有客户端源 IP 的节点,而不是中间代理。

            第一类负载均衡器必须使用负载均衡器和后端之间商定的协议来传达真实的客户端 IP, 例如 HTTP 转发或 X-FORWARDED-FOR 标头,或代理协议。 第二类负载均衡器可以通过创建指向存储在 Service 上的 service.spec.healthCheckNodePort 字段中的端口的 HTTP 健康检查来利用上述功能。

            清理现场

            删除 Service:

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

            image.gif

            删除 Deployment、ReplicaSet 和 Pod:

            kubectl delete deployment source-ip-app

            image.gif



            文章下方有交流学习区!一起学习进步!也可以前往官网,加入官方微信交流群 你的支持和鼓励是我创作的动力❗❗❗ Doker的成长,欢迎大家一起陪伴!!! 我发好文,兄弟们有空请把我的官方旗舰店流量撑起来!!!

            官网:Doker 多克; 官方旗舰店:Doker 多克旗舰店-淘宝网全品优惠

            相关实践学习
            容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
            通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
            云原生实践公开课
            课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
            目录
            相关文章
            |
            4月前
            |
            Kubernetes 负载均衡 网络协议
            |
            4月前
            |
            Kubernetes 负载均衡 算法
            kubernetes—Service详解
            kubernetes—Service详解
            37 0
            |
            7月前
            |
            Kubernetes 网络协议 应用服务中间件
            |
            9月前
            |
            存储 Kubernetes 应用服务中间件
            k8s--Service 环境准备、ClusterIP 使用
            k8s--Service 环境准备、ClusterIP 使用
            |
            10月前
            |
            Kubernetes 负载均衡 网络协议
            Kubernetes Service解析
            我们都知道,在K8S集群中,每个Pod都有自己的私有IP地址,并且这些IP地址不是固定的。这意味着其不依赖IP地址而存在。例如,当我们因某种业务需求,需要对容器进行更新操作,则容器很有可能在随后的启动运行过程中被分配到其他IP地址。此外,在K8S集群外部看不到该Pod。因此,Pod若单独运行于K8S体系中,在实际的业务场景中是不现实的,故我们需要通过其他的策略去解决,那么解决方案是什么? 由此,我们引入了Serivce这个概念以解决上述问题。
            89 0
            |
            12月前
            |
            Kubernetes 负载均衡 算法
            kubernetes中service探讨
            kubernetes中service探讨
            86 0
            |
            12月前
            |
            缓存 Kubernetes 数据库
            【kubernetes】Service: 将外部服务定位为 Service
            【kubernetes】Service: 将外部服务定位为 Service
            78 0
            |
            Kubernetes Linux Docker
            Kubernetes - Service 端口映射暴露配置
            Kubernetes - Service 端口映射暴露配置
            303 0
            Kubernetes - Service 端口映射暴露配置
            |
            Kubernetes 负载均衡 网络协议
            kubernetes Service:让客户端发现pod并与之通信(下)
            kubernetes Service:让客户端发现pod并与之通信
            |
            域名解析 Kubernetes 负载均衡
            kubernetes Service:让客户端发现pod并与之通信(上)
            kubernetes Service:让客户端发现pod并与之通信
            kubernetes Service:让客户端发现pod并与之通信(上)