• Kubernetes教程(五)---Service 的几种访问方式


    来自:指月 https://www.lixueduan.com

    原文:https://www.lixueduan.com/posts/kubernetes/05-service-access/

    本文主要介绍了 Service 的几种访问方式,包括ClusterIP、NodePort、LoadBalancer、ExternalName等。


    如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

    扫描下方二维码或搜索公众号【探索云原生】即可订阅


    1. 概述

    所谓 Service,其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。

    Service 的四种访问方式如下:

    • 1)ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。
    • 2)NodePort:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。
    • 3)LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。
    • 4)ExternalName:通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。

    其中 ClusterIP 为默认方式,只能集群内部访问。NodePort、LoadBalancer 则是向外暴露服务的同时将流量路由到 ClusterIP服务。ExternalName 则是CNAME方式进行服务映射。

    2. 详解

    2.1 ClusterIP

    ClusterIP也是 Service 的默认访问方式。

    根据是否生成 ClusterIP 又可分为普通 Service 和 Headless Service 两类:

    • 普通 Service:通过为 Kubernetes 的 Service 分配一个集群内部可访问的固定虚拟IP(Cluster IP),实现集群内的访问,为最常见的方式。
    • Headless Service:该服务不会分配 Cluster IP,也不通过 kube-proxy 做反向代理和负载均衡。而是通过 DNS 提供稳定的网络络 ID 来访问,DNS 会将Headless Service 的后端(endpoints)直接解析为 PodIP 列表,主要供 StatefulSet 使用。

    2.2 NodePort

    NodePort 也是比较常见的一种访问方式。

    YAML 定义如下:

    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      type: NodePort
      ports:
      - port: 8080
        targetPort: 80
        protocol: TCP
        name: http
        #nodePort: 31703
      - port: 443
        protocol: TCP
        name: https
        #nodePort: 31704
      selector:
        app: nginx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这个 Service 的定义里,我们声明它的类型是,type=NodePort。然后,我在 ports 字段里声明了 Service 的 8080 端口代理 Pod 的 80 端口,Service 的 443 端口代理 Pod 的 443 端口。

    如果你不显式地声明 nodePort 字段,Kubernetes 就会为你分配随机的可用端口来设置代理。这个端口的范围默认是 30000-32767,你可以通过 kube-apiserver 的–service-node-port-range 参数来修改它。

    查看一下自动分配的 NodePort

    [root@ks ~]# kubectl get svc my-nginx
    NAME       TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE
    my-nginx   NodePort   10.96.107.207   <none>        8080:31589/TCP,443:30134/TCP   19m
    
    • 1
    • 2
    • 3

    可以看到,虽然是 NodePort 类型,但还是默认创建了 ClusterIP。

    • 可以通过 : 来访问
      • 这里就是 10.96.107.207:8080
    • 或者通过:方式来访问
      • 这里就是内网IP 192.168.2.141:31589,或者公网IP 123.57.236.125:31589 都可以

    NodePort 模式也就非常容易理解,kube-proxy 要做的,就是在每台宿主机上生成这样一条 iptables 规则

    -A KUBE-NODEPORTS -p tcp -m comment --comment "default/my-nginx: nodePort" -m tcp --dport 8080 -j KUBE-SVC-67RL4FN6JRUPOJYM
    
    • 1

    KUBE-SVC-67RL4FN6JRUPOJYM其实就是一组随机模式的 iptables 规则。所以接下来的流程,就跟 ClusterIP 模式完全一样了

    要注意的是,在 NodePort 方式下,Kubernetes 会在 IP 包离开宿主机发往目的 Pod 时,对这个 IP 包做一次 SNAT 操作,如下所示:

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

    这里的原理其实很简单,如下所示:

               client
                 \ ^
                  \ \
                   v \
       node 1 <--- node 2
        | ^   SNAT
        | |   --->
        v |
     endpoint
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当一个外部的 client 通过 node 2 的地址访问一个 Service 的时候,node 2 上的负载均衡规则,就可能把这个 IP 包转发给一个在 node 1 上的 Pod。这里没有任何问题。

    而当 node 1 上的这个 Pod 处理完请求之后,它就会按照这个 IP 包的源地址发出回复。

    可是,如果没有做 SNAT 操作的话,这时候,被转发来的 IP 包的源地址就是 client 的 IP 地址。所以此时,Pod 就会直接将回复发给client。对于 client 来说,它的请求明明发给了 node 2,收到的回复却来自 node 1,这个 client 很可能会报错。

    所以,在上图中,当 IP 包离开 node 2 之后,它的源 IP 地址就会被 SNAT 改成 node 2 的 CNI 网桥地址或者 node 2 自己的地址。这样,Pod 在处理完成之后就会先回复给 node 2(而不是 client),然后再由 node 2 发送给 client。

    当然,这也就意味着这个 Pod 只知道该 IP 包来自于 node 2,而不是外部的 client。对于 Pod 需要明确知道所有请求来源的场景来说,这是不可以的。

    所以这时候,你就可以将 Service 的 spec.externalTrafficPolicy 字段设置为 local,这就保证了所有 Pod 通过 Service 收到请求之后,一定可以看到真正的、外部 client 的源地址。

    而这个机制的实现原理也非常简单:这时候,一台宿主机上的 iptables 规则,会设置为只将 IP 包转发给运行在这台宿主机上的 Pod。所以这时候,Pod 就可以直接使用源地址将回复包发出,不需要事先进行 SNAT 了。这个流程,如下所示:

           client
           ^ /   \
          / /     \
         / v       X
       node 1     node 2
        ^ |
        | |
        | v
     endpoint
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当然,这也就意味着如果在一台宿主机上,没有任何一个被代理的 Pod 存在,比如上图中的 node 2,那么你使用 node 2 的 IP 地址访问这个 Service,就是无效的。此时,你的请求会直接被 DROP 掉。

    2.3 LoadBalancer

    从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service。

    ---
    kind: Service
    apiVersion: v1
    metadata:
      name: example-service
    spec:
      ports:
      - port: 8765
        targetPort: 9376
      selector:
        app: example
      type: LoadBalancer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接。

    所以,在上述 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端

    2.4 ExternalName

    而第三种方式,是 Kubernetes 在 1.7 之后支持的一个新特性,叫作 ExternalName。举个例子:

    kind: Service
    apiVersion: v1
    metadata:
      name: my-service
    spec:
      type: ExternalName
      externalName: my.database.example.com
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在上述 Service 的 YAML 文件中,我指定了一个 externalName=my.database.example.com 的字段。而且你应该会注意到,这个 YAML 文件里不需要指定 selector。

    这时候,当你通过 Service 的 DNS 名字访问它的时候,比如访问:my-service.default.svc.cluster.local。那么,Kubernetes 为你返回的就是my.database.example.com。

    所以说,ExternalName 类型的 Service,其实是在 kube-dns 里为你添加了一条 CNAME 记录

    这时,访问 my-service.default.svc.cluster.local 就和访问 my.database.example.com 这个域名是一个效果了。

    此外,Kubernetes 的 Service 还允许你为 Service 分配公有 IP 地址,比如下面这个例子:

    kind: Service
    apiVersion: v1
    metadata:
      name: my-service
    spec:
      selector:
        app: MyApp
      ports:
      - name: http
        protocol: TCP
        port: 80
        targetPort: 9376
      externalIPs:
      - 80.11.12.10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上述 Service 中,我为它指定的 externalIPs=80.11.12.10,那么此时,你就可以通过访问 80.11.12.10:80 访问到被代理的 Pod 了。

    3. 故障诊断

    在理解了 Kubernetes Service 机制的工作原理之后,很多与 Service 相关的问题,其实都可以通过分析 Service 在宿主机上对应的 iptables 规则(或者 IPVS 配置)得到解决。

    1. Service 无法通过 DNS 访问

    你需要区分到底是 Service 本身的配置问题,还是集群的 DNS 出了问题

    一个行之有效的方法,就是检查 Kubernetes 自己的 Master 节点的 Service DNS 是否正常:

    # 在一个Pod里执行
    $ nslookup kubernetes.default
    Server:    10.0.0.10
    Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
    
    Name:      kubernetes.default
    Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果上面访问 kubernetes.default 返回的值都有问题,那你就需要检查 kube-dns 的运行状态和日志了。否则的话,你应该去检查自己的 Service 定义是不是有问题。

    2. Service 无法通过 ClusterIP 访问

    此时首先应该检查的是这个 Service 是否有 Endpoints:

    $ kubectl get endpoints hostnames
    NAME        ENDPOINTS
    hostnames   10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
    
    • 1
    • 2
    • 3

    而如果 Endpoints 正常,那么你就需要确认 kube-proxy 是否在正确运行。在我们通过 kubeadm 部署的集群里,你应该看到 kube-proxy 输出的日志如下所示:

    $ kubectl logs <kube-proxy-pod-name>
    
    I1027 22:14:53.995134    5063 server.go:200] Running in resource-only container "/kube-proxy"
    I1027 22:14:53.998163    5063 server.go:247] Using iptables Proxier.
    I1027 22:14:53.999055    5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
    I1027 22:14:54.038140    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
    I1027 22:14:54.038164    5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
    I1027 22:14:54.038209    5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
    I1027 22:14:54.038238    5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
    I1027 22:14:54.040048    5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
    I1027 22:14:54.040154    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
    I1027 22:14:54.040223    5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果 kube-proxy 一切正常,你就应该仔细查看宿主机上的 iptables 了

    一个 iptables 模式的 Service 对应的规则,它们包括:

    • 1)KUBE-SERVICES 或者 KUBE-NODEPORTS 规则对应的 Service 的入口链,这个规则应该与 VIP 和 Service 端口一一对应;
    • 2)KUBE-SEP-(hash) 规则对应的 DNAT 链,这些规则应该与 Endpoints 一一对应;
    • 3)KUBE-SVC-(hash) 规则对应的负载均衡链,这些规则的数目应该与 Endpoints 数目一致;
    • 4)如果是 NodePort 模式的话,还有 POSTROUTING 处的 SNAT 链。

    4. 小结

    所谓 Service,其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。

    • ClusterIP:集群内部IP,也是默认方法方式。
    • NodePort:通过节点IP+静态端口访问,NodePort 服务会将流量路由到 ClusterIP 服务。
    • LoadBalancer:使用云厂商提供的负载均衡向外暴露服务,可以将流量路由到 NodePort 服务或者ClusterIP 服务。
    • ExternalName:通过返回 CNAME 值的方式将服务映射到指定的域名。

    如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

    扫描下方二维码或搜索公众号【探索云原生】即可订阅


    5. 参考

    https://kubernetes.io/docs/concepts/services-networking/service/

    https://draveness.me/kubernetes-service/

    深入剖析Kubernetes

  • 相关阅读:
    基于微信小程序的懒人美食帮外卖订餐设计与实现(亮点:多角色的订餐系统、最贴近现实的小程序)
    GO 工程下载依赖操作流程(go mod)
    websocket(三)——前端JS封装
    快速上手几个Linux命令
    Prometheus配置监控ip、端口连通,get、post接口连通和状态码
    React学习--- 组件
    Linux进程管理2
    「Java 数据结构」:环形链表和约瑟夫问题。
    Python数据挖掘实用案例——自动售货机销售数据分析与应用
    langchain主要模块(四):Memory
  • 原文地址:https://blog.csdn.net/java_1996/article/details/133234501