美文网首页
Kubernetes:Service剖析

Kubernetes:Service剖析

作者: wyatt_plus | 来源:发表于2021-03-30 09:13 被阅读0次

    一. 简介

    Service 是 Kubernetes 里重要的服务对象,而 Kubernetes 之所以需要 Service,一方面是因为 Pod 的 IP 不是固定的,另一方面则是因为一组 Pod 实例之间总会有负载均衡的需求。

    通过创建 Service 可以为一组相同功能的容器应用提供一个统一的入口,并将请求均衡负载发送到后端的各个容器应用上。

    • 通过label selector来实现选中具体哪些容器
    • 均衡负载算法默认是 RR (Round-Robin 轮询调度)
    • 还可以通过设置 service.spec.sessionAffinity=ClientIp 来启用 SessionAffinity 策略
    • Service 只提供4层负载均衡能力(只能基于ip地址和端口进行转发),而没有7层功能(不能通过主机名及域名的方案去进行负载均衡)

    关于本文的项目的代码,都放于链接:GitHub资源

    基础架构图如下:


    Service Architecture

    二. Service类型

    2.1 ClusterIP

    在集群的内部ip上公开服务,这种类型使得只能从集群内访问服务。

    2.1.1 定义

    ClusterIP 是默认的方式。
    对于 ClusterIP 模式的 Service 来说,它的 A 记录的格式是:..svc.cluster.local。当访问这条 A 记录的时候,它解析到的就是该 Service 的 VIP 地址。

    ClusterIP 主要在每个node节点使用 Iptables 或者 IPVS,将发向 ClusterIP 对应端口的数据,转发到kube-proxy 中。然后kube-proxy自己内部实现有负载均衡的方法,并可以查询到这个service下对应pod的地址和端口,进而把数据转发给对应的pod的地址和端口。

    2.1.2 转发流程

    关于 ClusterIP 的转发流程如下:


    Service-ClusterIP

    为了实现图上的功能,需要以下几个组件协调工作:

    • api-server
      用户通过kubectl命令向apiserver发送创建 Service 的命令,apiserver接收到请求后将数据存储到etcd中。
    • kube-proxy
      Kubernetes的每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知Service,pod的变化,并将变化的信息写入本地的iptables规则中。
    • iptables
      使用NAT等技术将virtuallP的流量转至endpoint中。

    2.1.3 案例

    “demo-svc-clusterip.yaml” 文件参考案例如下:

    apiVersion: v1
    kind: Service
    metadata:
      name: demo-svc-clusterip
    spec:
      type: ClusterIP
      selector:
        app: demo-svc-clusterip
      ports:
      - name: http
        port: 80
        targetPort: 80
    

    我们通过spec.type: ClusterIP字段来定义即可。

    2.2 NodePort

    2.2.1 定义

    通过将 Service 的 port 映射到集群内每个节点的相同一个端口,实现通过 nodeIP:nodePort从集群外访问服务,这属于 ClusterIP 的超集。

    2.2.2 Port

    Service中主要涉及三种Port(这里的port表示service暴露在clusterIP上的端口):

    • ClusterIP
      Port 是提供给集群内部访问 Kubernetes 服务的入口。
    • targetPort
      containerPort,targetPort 是 pod 上的端口,从 port 和 nodePort 上到来的数据最终经过kube-proxy 流入到后端 pod 的 targetPort 上进入容器。
    • nodePort
      nodeIP:nodePort 是提供给从集群外部访问 Kubernetes 服务的入口。

    总的来说,port 和 nodePort 都是 Service 的端口,前者暴露给从集群内访问服务,后者暴露给从集群外访问服务。从这两个端口到来的数据都需要经过反向代理 kube-proxy 流入后端具体 pod 的 targetPort ,从而进入到 pod 上的容器内。

    2.2.3 案例

    “demo-svc-nodeport.yaml” 案例代码如下:

    apiVersion: v1 
    kind: Service 
    metadata:
      name: demo-svc-nodeport
    spec:
      type: NodePort 
      selector:
        app: demo-svc-nodeport
      ports:
      - name: http 
        port: 80 
        targetPort: 80
        protocol: TCP
      - nodePort: 443
        protocol: TCP
        name: https
    

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

    我们也可以不显式地声明 nodePort 字段,Kubernetes 就会分配随机的可用端口来设置代理。这个端口的范围默认是 30000-32767,可以通过 kube-apiserver–service-node-port-range 参数来修改它。
    当我们创建完毕后,可以通过如下访问格式:

    <任何一台宿主机的IP地址>:80
    

    2.3 LoadBalancer

    2.3.1 定义

    在公有云提供的 Kubernetes 服务里,都使用了一个叫作 CloudProvider 的转接层,来跟公有云本身的 API 进行对接。所以,在 LoadBalancer 类型的 Service 被提交后,Kubernetes 就会调用 CloudProvider 在公有云上创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。

    2.3.2 案例

    ”demo-svc-loadbalancer.yaml” 案例如下:

    kind: Service
    apiVersion: v1
    metadata:
      name: demo-svc-loadbalancer
    spec:
      ports:
      - port: 8080
        targetPort: 8080
      selector:
        app: demo-svc-loadbalancer
      type: LoadBalancer
    

    2.4 ExternalName

    2.4.1 定义

    通过返回具有该名称的 CNAME 记录,使用任意名称(在规范中指定)公开服务,并且不使用代理。

    2.4.2 案例

    kind: Service
    apiVersion: v1
    metadata:
      name: demo-svc-externalname
    spec:
      type: ExternalName
      externalName: demo-svc-externalname.wyatt.plus
    

    在上述 Service 的 YAML 文件中,我指定了一个 externalName=demo-svc-externalname.wyatt.plus 的字段。
    当通过 Service 的 DNS 名字访问它的时候,比如访问:demo-svc-externalname.default.svc.cluster.local。那么,Kubernetes 返回的就是demo-svc-externalname.wyatt.plus

    2.4.3 CNAME

    所以说,ExternalName 类型的 Service 其实是在 kube-dns 里添加了一条 CNAME 记录。当访问 demo-svc-externalname.default.svc.cluster.local 就和访问 demo-svc-externalname.wyatt.plus 这个域名效果一样。

    2.4.4 externalIPs

    在 ExternalName 模式下,Kubernetes 的 Service 还允许为 Service 分配公有 IP 地址。
    “demo-svc-externalips.yaml” 案例如下:

    kind: Service
    apiVersion: v1
    metadata:
      name: demo-svc-externalips
    spec:
      selector:
        app: demo-svc-externalips
      ports:
      - name: http
        protocol: TCP
        port: 80
        targetPort: 80
      externalIPs:
      - 192.11.11.11
    

    在上述 Service 中,为它指定的 externalIPs=192.11.11.11,就可以通过访问 192.11.11.11:80 访问到被代理的 Pod 了。

    三. Service代理

    在Kubernetes集群中,为每个节点运行了一个kube-proxykube-proxy 负责为 Service 实现一种 virtual ip 的形式,而这个过程称之为Service代理模式。
    不同的 Kubernetes 版本,代理模式的实现方式也不尽相同,前后共有三种模式:

    • userspace(已过期):Kubernetes v1.0 版本使用的是这种代理模式
    • Iptables:从 Kubernetes v1.2 开始使用 Iptables
    • IPVS:Kubernetes v1.14 开始默认使用 IPVS 代理

    3.1 Iptables

    3.1.1 原理

    kube-proxy 通过iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。

    3.1.2 架构图

    Iptables architecture

    3.1.3 优缺点

    当宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。
    所以说,基于 Iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。

    3.2 IPVS

    3.2.1 原理

    IPVS 模式的工作原理,其实跟 Iptables 模式类似。当我们创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址。
    而接下来,kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。

    3.2.2 架构图

    IPVS architecture

    3.2.3 负载均衡

    IPVS 使用哈希表作为底层数据结构并在内核空间中工作,这意味着 IPVS 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。
    此外,IPVS 为负载均衡算法提供了更多选项,例如

    • rr:轮询调度
    • 1c:最小连接数
    • dh:目标哈希
    • sh:源哈希
    • sed:最短期望延迟
    • nq:不排队调度

    3.2.4 优缺点

    而相比于 Iptables,IPVS 在内核中的实现其实也是基于 NetfilterNAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。
    但是,IPVS 并不需要在宿主机上为每个 Pod 设置 Iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。所以,“将重要操作放入内核态”是提高性能的重要手段。

    注意: IPVS 需要节点上的 IPVS内核模块 支持,如果未安装,则 kube-proxy 将回退到 Iptables 代理模式。

    四. 拓展

    4.1 Endpoints

    在 Kubernetes 中,selector 选中的 Pod,就称为 Service 的 Endpoints,可以使用 kubectl get ep 命令看到它们。
    需要注意的是,只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。

    4.2 Headless Service

    Headless Service 也是一种 ClusterIP ,只不过是一种特殊的情况。
    有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP) 的值为 None 来创建 Headless Service
    这类 Service 具有如下的特点:

    • 不会分配 Cluster IP
    • kube-proxy 不会处理它们
    • 平台也不会为它们进行负载均衡和路由

    通过 Headless Service的方式,可以解决 hostnameportname的变化问题,也就是通过它去进行绑定。例如,我们之前提到的 StatefulSet 这种有状态应用。

    五. 总结

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

    当然,我们发现 Service 和 DNS 机制 不具备强多租户能力。比如,在多租户情况下,每个租户应该拥有一套独立的 Service 规则(Service 只应该看到和代理同一个租户下的 Pod)。再比如 DNS,在多租户情况下,每个租户应该拥有自己的 kube-dnskube-dns 只应该为同一个租户下的 Service 和 Pod 创建 DNS Entry)。

    欢迎收藏个人博客: Wyatt's Blog ,非常感谢~

    Reference

    https://kubernetes.io/zh/docs/concepts/services-networking/service/
    https://www.cnblogs.com/binghe001/p/13166641.html
    https://draveness.me/kubernetes-service/
    https://www.cnblogs.com/baoshu/p/13233014.html
    https://time.geekbang.org/column/article/68964?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
    https://draveness.me/kubernetes-service/

    相关文章

      网友评论

          本文标题:Kubernetes:Service剖析

          本文链接:https://www.haomeiwen.com/subject/nwswhltx.html