K8S 服务(Service)是K8S实现微服务架构最重要的组件之一,主要作用:1)为Pod提供稳定的访问地址(域名或IP),2)实现负载均衡,3)自动屏蔽后端Endpoints的变化。
假设我们开发了一个用户认证和授权的Restful功能,这个功能以Deployment的形式来部署,replicas是5,即有5个pod可以同时提供认证和授权的功能。如果没有Service,客户端在使用此功能的时候,需要知道这5个Pod的IP地址,然后访问其中的一个IP地址。这里带来了很多问题,1)客户端需要维护这5个IP地址,并自己选择某一个IP地址来使用,增加了客户端的复杂性。2)如果客户端的IP地址发生了变化,客户端并不会自动感知,客户端可能会使用不存在的IP地址。
如果使用K8S的Service,就能完美解决上面的问题。由于Service的域名或IP地址不会发生变化,所以对客户端来说其看到的Service域名是不变的,即便Service对应的Endpoints发生了改变。一个Service的后端有多个Endpoints,所以Service在选择某个Endpoint的时候会通过LB的方式来进行(通过kube-proxy组件来实现LB,具体基于iptables或ipvs)。K8S的Service降低了普通微服务架构中服务注册和发现的开销,很轻量级的实现了微服务架构中对服务的管理。
Service中最重要的配置是ports和selector,其中ports中定义了Service本身的端口和后端要使用的Pod的端口,selector定义了选择哪些Pod来作为该Service的Endpoints。K8S的Service控制器会持续监控后端Pod列表的变化,如果发生改变,则其会实时更新Endpoints列表。
从Service的IP到后端Pod的选择是由运行在每个Node上的kube-proxy来实现的。可以选择iptables模式或者ipvs模式,通过K8S启动参数--proxy-mode来设置。
iptables:默认的方式,基于Linux kernel的iptables规则来实现路由规则的定义和数据包的转发,LB算法是加权random。关于iptables可以参考:
https://www.cnblogs.com/kalixcn/p/17323391.html
https://zhuanlan.zhihu.com/p/633712699
ipvs:IPVS是Linux kernel内置的层四LB,比iptables的性能更高,且支持比较多的LB算法,如rr,lc,dh,sh等。
iptable更灵活,功能强大,但工作在用户态,且LB算法简单。ipvs工作在内核态,性能更好,且支持的LB算法比较多,但相对于iptables来说不够灵活。
通过设置sessionAffinity,可以实现基于客户端IP的会话保持机制,即如果首次将某个客户端来源IP发起的请求转发到了后端的某个Pod上,之后相同的IP客户端的请求也会转发到此Pod上。
一个Service可以绑定多个端口,例如,通过多端口的设置可以很容易区分管理端口和业务端口,只需要后端的Pod能对不同的端口提供相应的服务即可。不同的端口还可以具备不同的协议,如TCP,UDP等。
有两种办法,1)手动定义Endpoints。通过这种方式,service没有selectors配置,即不需要选择后端的Pod,但需要额外定义一个Endpoints资源,在里面手动指定外部服务的IP地址,service就能选择这些IP地址。2)将Service的类型定义为ExternalName。如果Service的type是ExternalName,那么需要指定一个属性externalName,该属性可以设置外部服务的域名。
ClusterIP:K8S默认的类型,会为服务分配一个仅在集群内部使用的IP地址,服务域名到该IP地址的转换通过Core DNS来实现。
NodePort:K8S会将服务的端口映射到每个Node的某一个端口上,这样集群外部的Client就可以通过任何一个Node来访问此服务。NodePort类型下,可以手动通过nodePort属性指定一个外部的端口,如果没有指定,则K8S会自动分配一个30000-32767范围内的端口。另外,还可以通过指定哪些网卡来对外提供服务,那么Node上只能访问对应网卡的IP地址来访问K8S内部服务。
LoadBalancer:将此服务映射到一个已经存在的LB上,这样在集群外部也能访问服务,且已经存在的LB可以自动选择某个node。通常在Public Cloud上使用,也可以基于MetalLB在非公有云上使用。如果没有此类型,客户端就不知道该选个哪个Node来访问内部服务,相当于LB的功能需要客户端来实现。有了此类型,LB的工作就交给了外部的某个LB,LB会将请求转发到某个Node上。
ExternalName:将外部的服务映射到K8S里面,参见上面的说明。
NodePort:参见上面的说明。
LoadBalancer:参见上面的说明。
Ingress:使用Ingress资源,在L7上进行路由。
环境变量:K8S将集群中所有有效的服务,通过环境变量的方式注入到每个Pod中,在Pod中就可以通过环境变量来获取服务的IP地址,端口等。
DNS:推荐的方式。在Pod中直接使用服务的DNS域名。
客户端如果不想使用K8S提供的服务的LB机制,或者客户端想自己选择某个后端Pod,那么此时可以使用Headless Service。如果客户端访问服务域名,其得到的将是后端Pod列表(如果服务设置了Label Selector),而不是服务的Cluster IP。
端点分片:如果K8S集群的Node很多,且服务关联的Pod也很多,那么K8S在更新服务的Endpoints列表时将带来很大开销,为此,K8S提供了端点分片的功能(EndpointSlice),即K8S不是在所有的Node上更新Endpoints,而是在需要的Node上更新。例如,Deployment的滚动升级,我们可以只更新部分Node,等在这些Node上的功能被测试完成后,再更新所有Node。这需要通过创建EndpointSlice来实现。
服务拓扑
一个服务的Endpoints可以包含分布在不同地理位置的Pod,客户端对服务的不同请求也可能来自不同的地理位置。可以设置规则,让服务根据这些规则来返回Endpoint,只让满足规则的Endpoints来响应客户端请求。这些规则通过topologyKeys来指定,例如,可以按照hostname,zone,region等规则。
该功能的目的是可以通过七层路由机制来指定不同请求的URL进入到不同的服务。例如:对http://www.mysite.com/api的访问会进入到提供API的服务,对http://www.mysite.com/doc的访问会进入到提供doc服务。有两个重要的组件,Ingress和Ingress控制器。
Ingress:K8S的一种资源类型,在里面定义Ingress规则,即什么样的URL转发到什么样的服务上。
Ingress控制器:K8S没有实现,需要手动通过Deployment来指定,可以是Nginx或者HAproxy等,只要具备反向代理的功能即可。K8S会把Ingress中定义的规则转换成Ingress控制器中的配置来实现七层路由。通常,在部署Ingress控制器的时候,还需要向外暴露一个端口,外部client通过此端口来访问Ingress控制器。这个端口可以在部署Ingress控制器的时候通过hostPort来指定。注意:这里是hostPort,不是前面的nodePort,hostPort只存在于当前Ingress控制器的node上,而nodePort存在于每个node上。
Ingress只能处理HTTP或HTTPS的七层路由,不能处理TCP等四层路由,对于Restful API有较多应用场景。