侧边栏壁纸
博主头像
Wyatt博主等级

Done is better than perfect!

  • 累计撰写 103 篇文章
  • 累计创建 31 个标签
  • 累计收到 7 条评论

Kubernetes:Service剖析

Wyatt
2021-03-30 / 0 评论 / 1 点赞 / 862 阅读 / 7,501 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2021-03-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

一. 简介

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 ,非常感谢~

个人博客: Wyatt's Blog个人公众号:Wyatt的成长之路
微信号公众号

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/

1

评论区