Kubernetes之Service
概述Service是Kubernetes中最核心的概念,正是因为对此概念的支持,Kubernetes在某种角度下可以被看成是一种微服务平台。Kubernetes中的pod并不稳定,比如由ReplicaSet、Deployment、DaemonSet等副本控制器创建的pod,其副本数量、pod名称、pod所运行的节点、pod的IP地址等,会随着集群规模、节点状态、用户缩放等因素动态变化。Serv..
概述
Service是Kubernetes中最核心的概念,正是因为对此概念的支持,Kubernetes在某种角度下可以被看成是一种微服务平台。Kubernetes中的pod并不稳定,比如由ReplicaSet、Deployment、DaemonSet等副本控制器创建的pod,其副本数量、pod名称、pod所运行的节点、pod的IP地址等,会随着集群规模、节点状态、用户缩放等因素动态变化。Service是一组逻辑pod的抽象,为一组pod提供统一入口,用户只需与service打交道,service提供DNS解析名称,负责追踪pod动态变化并更新转发表,通过负载均衡算法最终将流量转发到后端的pod。
原理
本节定义一个service示例并说明其工作原理。假设已经通过Deployment副本控制器创建了3个pod,每个pod包含"app=Myapp"标签,每个pod暴露端口9376。只所以假设已经有3个pod实例是为了方便说明service工作原理,推荐的做法是先创建service后创建pod。以下是service声明:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
保存到文件中并运行如下命令创建实例:
kubectl create -f Myapp.yaml
工作过程如下:
- 为实例分配置集群虚拟IP。如果在声明时明确指定集群虚拟IP,则分配指定IP,如未指定则自动分配。
- 根据实例名称、分配的集群虚拟IP、端口号创建DNS条目。
- 根据标签选择器聚合符合条件的节点,并创建相应endpoint,endpoint包含所有符合条件pod的ip地址与端口号。
- kube-proxy运行在集群中每一个节点上,并持续监控集群中service、endpoint变更,根据监控结果设置转发规则,将一个集群虚拟IP、端口与一个或者多个pod的IP、端口映射起来。
- 当在集群内部通过服务名称访问创建的service时,首先由DNS将服务名称转换成集群虚拟IP与端口号,kube-proxy根据转发规则对service的流量计算负载均衡、转发到位于后端的pod。
无标签选择器service
当与service对应的后端位于集群外部时,因为集群中没有相关的pod实例,因此这种情况下就不需要标签选择器。有标签选择器时系统自动查询pod并创建相应的endpoint,无标签选择器时需要用户手动创建endpoint,定义如下service:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
为其手动创建endpoint:
kind: Endpoints
apiVersion: v1
metadata:
name: my-service
subsets:
- addresses:
- ip: 1.2.3.4
ports:
- port: 9376
除需要手动创建endpoint外,无标签选择器与有标签选择器的servcie工作过程完全相同。
集群虚拟IP与kube-proxy
什么是虚拟IP?一般情况下,一个IP地址都会被分配给一个二层网络设备,网络设备可以是物理的、也可以是虚拟的,但总有设备对IP地址对应。而kubernetes中的集群IP,只是三层网络上的一个地址,没有设备与其对应,因此集群IP又是虚拟IP。
kube-proxy是kubernetes核心组件,运行在集群中每一个节点上,负责监控集群中service、endpoint变更,维护各个节点上的转发规则,是实现servcie功能的核心部件。在1.8及以后的版本中,kube-proxy有以下三种工作模式,但不同版本kubernetes能支持的工作模式不同,注意查证。
用户空间模式
图1
上图要点:
- kube-proxy通过访问apiServer持续监控集群中service变更
- 当发现有新service时,kube-proxy随机开启local网络的端口号进行监听。同时向节点的iptables中添加转发条目,将service的流量转发到自己监听的端口上。
- kube-proxy通过访问apiServer持续监控集群中endpoint变更,并将service及其可用pod列表保存起来。
- 当在节点中访问服务时,流量首先到达iptables,iptables根据设置好的规则将流量转发给kube-proxy中相应的端口,kube-proxy再根据其维护的servcie与可用pod的对应关系,通过负载均衡计算以后转发给后端pod。
iptables模式
图2
上图要点:与图1相比,kube-proxy的角色发生了变化。它在监控集群中的service与endpoint时,不会在local网络上打开端口并设置iptables先将流量转发给自己,由自己分发给pod。而是设置iptable将流量直接转发给pod,转发给pod的工作由kube-proxy转移到iptables中,也就是转移到内核空间。
iptables模式安全,可靠、效率高,但因为受内核的限制不够灵活。用户空间模式没有iptables模式安全,可靠、效率高,但因为它工作在用户空间,因此比较灵活,比如当某个pod没有应答时,它可以自动重试其它可用pod。iptables模式对于无应答的pod不会重试其它pod,而且问题pod一直存在,当pod的readness诊断失败后,pod才会被系统从可用列表中删除。
ipvs模式
iptables模式与ipvs模式本质相同,实现细节不同。前者首先定义规则,表示规则的数据保存在内核中,即通常说的“四表五链”。然后内核根据"四表五链"中的数据创建相应函数并挂载到内核中合适的点上。在ipvs模式中,kube-proxy根据其对servcie、endpoint的监控结果,调用内核netlink接口创建ipvs rule,不同于iptables的"四表五链",ipvs rule的数据组织更加紧凑、高效。因此相对于iptabels模式,ipvs模式更节省资源,对servcie、endpoint的变更同步速度更快。另外ipvs支持更多种类的负载均衡算法:
- rr: round-robin
- lc: least connection
- dh: destination hashing
- sh: source hashing
- sed: shortest expected delay
- nq: never queue
多端口service
很多service需要向外暴露不只一个端口,kubernetes支持在一个服务中声明多个端口,但必需为每个端口指定名称,避免在生成endpoint时产生歧义,示例如下:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
注意端口号名称有限制,必需只能包含小写字母、数字、中划线,且不能以中划线结尾。如123-abc、web合法, 123_abc、-web非法。
服务发现
所谓服务发现,就是在pod知道服务名称的前提下,如何得到其访问IP地址与端口号。Kubernetes支持两种类型的服务发现:环境变量与DNS。
环境变量方式
环境变量方式要求service先于使用者pod创建。在创建pod时,kubernetes将系统中所有激活服务的访问IP地址、端口号以环境变量的形式自动注册到pod所创建的容器中。不同服务的环境变量用名称区分,例如:
{SVCNAME}_SERVICE_HOST and {SVCNAME}_SERVICE_PORT
如果服务有多个端口则端口的环境变量名称为 {SVCNAME}_SERVICE_{PORTNAME}_PORT。
例如"redis-master"服务端口号为6379,集群虚拟IP地址为10.0.0.11,则其相关环境变量如下:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
DNS方式
DNS是kubernetes的可选装插件,如果在集群中已经安装插件并打开此项功能,则自动为集群中的service添加名称解析条目,而且集群中所有pod默认可以使用服务名称寻址到服务。
假如在名称空间"my-ns"下有名为"my-service"的服务,则DNS插件自动生成"my-service.my-ns"条目。位于"my-ns"名称空间下的pod可直接使用"my-service"名称寻址,位于不同名称空间下的pod寻址时则用"my-service.my-ns"。
DNS插件支持对命名端口的查询,假如"my-service"服务中包含命名端口"http",协议为tcp,则寻址"_http._tcp.my-service.my-ns"则会返回http对应的端口号,这就是对命名端口的支持。
很明显DNS方式比环境变量方式合理、灵活的多。
无头服务
在定义service时,如果.spec.clusterIP被指定为固定值则为服务分配指定的IP,如果.spec.clusterIP字段没有出现在配置中,则自动分配集群虚拟IP。但如果.spect.clusterIP的值被指定为"None",此时创建的服务就被称为无头服务,其行为与普通服务有很大区别。首先不为服务分配集群虚拟IP,自然也就不能在DNS插件中添加服务相关条目。运行在各节点上的kube-proxy不为其添加转发规则,自然也就无法利用kube-proxy的转发、负载均衡功能。
虽然不向DNS插件添加服务相关条目,但可能添加其它条目,取决于service是合包含标签选择器。
包含标签选择器
此种情况下,系统仍然根据标签选择器创建endpoint,并根据endpoint向DNS插件中添加条目。比如命名空间为"my-ns",服务名称为"my-headless",endpoing指向的pod名称为pod1、pod2,则向DNS插件中添加的条目类似于"pod1.my-headless.my-ns"与"pod1.my-headless.my-ns",此时DNS中的条目直接指向pod。在StatefulSet类型资源中,使用无头服务为其中的pod提供名称解析服务,只所以可行,其实是因为StatefulSet能保证其管理的pod有序,名称地址等特征保持不变。
不包含标签选择器
CNAME records for ExternalName-type services.
A records for any Endpoints that share a name with the service, for all other types.
Service类型
本文以上示例都以默认服务类型为前提,实际上kubernetes暴露服务IP的类型有四种,分别如下:
- ClusterIP:默认类型,为服务分配集群虚拟IP,此时集群内部的pod可以通过服务名称寻址到服务的集群虚拟IP地址,集群外无效。
- NodePort:在每个节点上为服务分配静态端口号,注意此端口号占用的是节点网络,此时如果在集群外部访问任何一个节点的IP地址加指定的端口号,kube-proxy会将流量转发到服务的集群虚拟IP,再由虚拟IP寻址到POD。
- LoadBalancer:通过云服务供应商提供的load balancer向外部暴露服务,由指定的load balancer负责对NodePort与ClusterIP服务的路由。
- ExternalName:比较特殊,只是简单的将服务名称映射成指定名称,如foo.bar.example.com,kube-dns1.7有以后版本支持此特性。
接下来介绍除ClusterIP类型以外的其它三种类型
NodePort类型
NodePort类型服务会占用节点网络与端口号,其目的是为外部网络访问集群内部服务提供一种手段。首先将.spec.type值设置为NodePort,在创建服务时系统在各个节点上自动分配相同的port,范围由--service-node-port-range,默认30000-32767。服务占用的NodePort号可通过.spec.ports[*].nodePort查询。当访问IP地址加NodePort端口号时,节点将流量转发到集群虚拟IP加端口号,最终访问到后端pod。
如果存在多个节点网络,默认为所以节点网络打开NodePort。如果想限定使用的节点网络,可以为kube-proxy设置--nodeport-addresses,其值为地址块,如--nodeport-addresses=127.0.0.0/8,则只会为匹配地址块的节点网络地址打开端口。
设置.spec.ports[*].nodePort为特定值,指明使用指定的端口号而非随机分配,其值必需位于--service-node-port-range规定的范围内。此时由用户自行解决端口号冲突问题。
集群内部pod仍可通过“服务名=>>集群虚拟IP:端口号=>>kube-proxy转发到pod”的形式访问服务。
集群外部访问服务路径为“节点IP:NodePort=>>集群虚拟IP:端口号=>>kube-proxy转发到pod”。集群外部不能使用服务名。
LoadBalancer类型
此种类型在公有云服务供应商平台上会用到,典型配置如下:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155
.spec.status.loadBalancer指定服务商提供的负载均衡器地址,.spec.type设置成LoadBalancer,.spec.loadBalancerIP指定占用的由服务商负载均衡器提供的IP地址,当访问此IP地址时,流量被直接转发到后端pod。这种方式的实现机制与配置方式与具体的供应商有关。
ExternalName类型
典型配置如下:
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
当在集群访问my-service时,kube-dns插件返回的结果会是my.database.example.com,而my.database.example.com应该是在集群外其它DNS服务器中可用的域名,使用者需要通过外部DNS将此域名再转换成IP地址。另外一种形式,直接将服务名称转换成集群外可用的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
kube-dns插件自动将服务名转换成80.11.12.10这个IP地址。
总结
kube-proxy运行在集群中的每个节点上,并为每个服务设置转发条目,即使在这个节点上从来不会访问这个服务,效率很低,这种方式简单通用,但不适合于大规模集群服务。对于大规模集群,应该使用供应商或者自定义Loadbalancer。
参考:https://kubernetes.io/docs/concepts/services-networking/service/#
更多推荐
所有评论(0)