Kubernetes:Service剖析

2021-03-30  本文已影响0人  wyatt_plus

一. 简介

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

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

关于本文的项目的代码,都放于链接: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

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

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上的端口):

总的来说,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 版本,代理模式的实现方式也不尽相同,前后共有三种模式:

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 为负载均衡算法提供了更多选项,例如

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 具有如下的特点:

通过 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/

上一篇下一篇

猜你喜欢

热点阅读