在Kubernetes中,Pods是有生命周期的。它们被创建、被终止,但不能被复活。在Kubernetes中通过ReplicationControllers动态的创建和删除Pod。然后,每一个Pod都拥有自己的IP地址,但是这些IP地址随着时间会发生变化。这会导致一个问题:如果在Kubernetes集群中,前端的Pod需要调用后端的Pod的功能,那么这些前端的Pod如何发现和跟踪后端的Pod?
在Kubernetes中,Service是一个抽象的概念,它定义了Pod逻辑集合和访问这些Pod的策略。Service通过Label Selector选择Pod。例如,在后端运行着有3个副本的Pod,这些副本是可互相替换的,前端不需要关注使用那个副本。Service抽象就用来实现此解耦的能力的。对于 Kubernetes-native的应用,当Service中的Pod发生变化时,Kubernetes通过了Endpoints API类进行更新。对于non-native applications, Kubernetes 提供了virtual-IP-based桥,通过它重定向后端的Pod。在本文中,描述如何定义Service、发布Service和发现Serivce的整个过程。
1、虚拟IP和服务代理
在Kubernetes的每一个Node中,都运行着一个kube-proxy,kube-proxy负责为服务(ExternalName除外)实现虚拟IP的格式。在Kubernetes v1.0中,服务是一个4层(IP之上的TCP/UDP)结构,纯粹在userspace实现代理;在Kubernetes v1.1,增加了Ingress API,它表达了7层(HTTP)服务;也增加了iptables代理,此代理是Kubernetes v1.2后的默认代理模式;在Kubernetes v1.8.0-beta.0, 增加了ipvs代理。
在iptables模式中,kube-proxy通过创建iptables规则,将访问Service虚拟IP的请求重定向到Endpoints上,iptables代码模式方式利用linux的iptables nat转发进行实现。kube-proxy监控Kubernetes master中的Service和Endpoints对象,并进行添加和移除,以更新iptables规则。
- 对于每一个Service,它将会安装iptable规则,此规则获取流量至Service‘s clusterIP和端口,并将这些流量传递给Service后端集。
- 对于每一个Endpoints对象,它将安装iptable规则,用于选择后端的Pod。
显然,iptable不需要在userspace和kernelspace之间进行转换,它比userspace更快更可靠。不像userspace代理器,如果一个Pod被提供服务,iptable代理器不能自动的使用另外一个Pod。需要注意下图中:clusterIP被显示为ServiceIP。
2、定义服务
在Kubernetes中,服务是一个REST对象,类似于Pod。想其他所有的REST对象一样,服务定义能够被传递给apiserver来创建一个新的实例,例如,这里有一组Pod,对外暴露的端口为9367,标签为app:MyApp:
kind:Service apiVersion:v1 metadata: name:my-service spec: selector: app:MyApp ports: - protocol:TCP port:80 #暴露在cluster Ip上的端口,供集群内部使用 targetPort:9376 #pod上的端口
在此配置文件中,创建了一个名为“my-service”的服务对象,在每一个标签带有“app=MyApp”的Pod上,它的targetPort为9376。此服务将被指派一个IP地址(有时也称为“cluster IP”),服务选择器将被持续的评估,评估的结果将被传递给名称也为“my-service”的Endpoints对象。
需要注意的是,服务能够映射一个输入端口至任意的targetPort。在默认情况下,targetPort将被设置成与port的值一样。targetPort可以是一个字符串,可以引用后端Pod中的port名称。基于后端Pod的不同,实际的port值也会不同。这就为部署服务提供大量的灵活性。Kubernetes服务支持TCP和UDP协议,默认为TCP协议。
2.1 无选择器的服务
Service一般被用来代理访问Pod,但也能够代理后端的其他类型,例如:
- 在生产环境中使用外部的数据库,但在测试环境中使用集群内的数据;
- 服务将需要被另外的命名空间或者另外的集群上的服务调用;
- 正在迁移应用至Kubernetes,并且一些后端在Kubernetes外运行。
在上述的这些场景中,可以定义无选择器的Service:
kind:Service apiVersion:v1 metadata: name:my-service spec: ports: - protocol:TCP port:80 #Cluster IP上的端口为80,此端口仅可以在集群内访问 targetPort:9376 #Pod上的端口
因为此Service没有选择器,则不会创建对应的Endpoints对象,您可以手工将服务映射至指定的endpoints中:
kind:Endpoints apiVersion:v1 metadata: name:my-service subsets: - addresses: - ip:1.2.3.4 ports: - port:9376
注意:Endpoint IP不可以是loopback(127.0.0.0/8), link-local (169.254.0.0/16), 或者link-local multicast (224.0.0.0/24)。 访问无选择器的Service与访问有选择器的Service是一样。流量将通过用户定义的endpoints进行路由。
2.2 ExternalName服务
ExternalName Service是Service的一个特例,它没有选择器,也没有定义任何端口或Endpoints。它的作用是返回集群外Service的外部别名。
kind:Service apiVersion:v1 metadata: name:my-service namespace:prod spec: type:ExternalName #服务类型为外部服务 externalName:my.database.example.com #外部服务
当查找my-service.prod.svc.CLUSTER时,集群DNS服务将会返回一条CNAME记录,此记录的值为my.database.example.com。当然后续也可以将此数据库迁移到集群中,这样就可以通过Pod启动,并为其添加合适的选择器或者Endpoints,并修改服务类型。
2.3 headless服务
在有些场景下,服务可能不需要作为负载均衡代理,而仅仅需要一个单一的cluster ip。这时就可以通过设置“spec.clusterIP”的值为None,来创建一个”headless“类型的服务。此类型的服务允许开发者减少对Kubernetes系统的依赖,开发者可以通过自己的方式实现对服务的自动发现。应用也能够使用其他的服务发现系统,进行服务的自注册和适配器,实现服务的自动发现。对于这样的服务:
- Kubernetes未指派cluster IP;
- kube-proxy将不处理这些服务;
- 因此也就没有负载均衡和代理。
- 但会依赖服务是否拥有选择器进行DNS的配置。
对于定义了选择器的headless service,Endpoints控制器在API中创建Endpoints记录,并通过修改DNS的配置信息返回一条记录,此记录指向服务后端的Pod。对于没有定义选择器的headless service,Endpoints控制器不会创建Endpoints记录,然而,DNS系统将会进行寻址和配置。
- CNAME记录:
ExternalName类型的
服务 - Endpoints记录:任意与service共享一个名称的Endpoints。
2.4、多端口服务
在实际的应用场景中,有一些服务需要暴露多个端口。在Kubernetes中,支持在Service对象上定义多个端口。当使用多个端口时,则需要为每个端口设置一个名称。例如,下面名称为my-service的服务YAML配置文件,它暴露了一个http的端口和一个https的端口。
kind:Service apiVersion:v1 metadata: name:my-service spec: selector: app:MyApp ports: - name:http #名称为http的端口 protocol:TCP port:80 targetPort:9376 - name:https #名称为https的端口 protocol:TCP #端口协议为TCP port:443 #ClusterIP端口号为443 targetPort:9377 #Pod上的端口为9377
2.5 代理外部的服务
另外,在一些场景下,Kubernetes中的容器化应用需要调用集群外的应用。Service也可以代理任意其它的后端应用,比如运行在Kubernetes集群外部的Oracle、MySQL和Redis等。在Kubernetes中,通过定义同名的Service和EndPoints来实现对于外部应用的代理,以实现其能够被集群内部的应用调用。
下面是接入外部Oralce端点YAML文件:
apiVersion: v1 kind: Endpoints metadata: name: oracle-service subsets: - addresses: - ip: 192.168.8.159 ports: - port: 1521 protocol: TCP
下面是代理外部Oralce的服务YAML文件:
apiVersion: v1 kind: Service metadata: name: oracle-service spec: ports: - port: 1521 targetPort: 1521 protocol: TCP
3、发现服务
在Kubernetes中,支持两种服务发现的模式:
- 环境变量
- DNS
3.1 环境变量
当一个Pod运行在Node上时,kubelet将为每一个活动的Service添加环境变量,环境变量有两类:
- DockerLink 环境变量:相当于Docker的–link参数实现容器连接时设置的环境变量。
- Kubernetes Service环境变量:Kubernetes为Service设置的环境变量形式,{SVCNAME}_SERVICE_HOST 和{SVCNAME}_SERVICE_PORT变量,环境变量的名称为大写字母和下划线。
例如:存在一个名称为“redies-master”的Service(它的cluster ip地址为10.0.0.11,端口号为6379,协议为TCP),它的环境变量如下:
#Kubernetes Service环境变量: REDIS_MASTER_SERVICE_HOST=10.0.0.11 REDIS_MASTER_SERVICE_PORT=6379 #Docker Link环境变量: 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
在这里,可以看到环境变量中记录了”redies-master”服务的IP地址和端口,以及协议信息。因此,Pod中的应用就可以通过环境变量来发现此服务。但是,环境变量方式存在如下的限制:
1)环境变量只能在相同的命名空间中使用;
2)另外,Service必须在Pod创建之前被创建,否则Service变量不会被设置到Pod中;
3)DNS服务发现机制则没有这些限制。
3.2 DNS
DNS服务发现是基于Cluster DNS的,DNS服务器会对新服务进行监控,并为每一个服务创建DNS记录,用于域名解析。在集群中,如果启用DNS,则所有的Pod都可以自动通过名称解析服务。
例如,如果在“my-ns”命名空间下拥有一个名为“my-serivce”的服务,则会有一个名为“my-service.my-ns”的DNS记录被创建。
- 在“my-ns”命名空间下,Pod将能够通过名称“my-service”来发现此服务。
- 在其它命名空间,Pod必须通过“my-serivce.my-ns”来发现此服务,此名称选址的结果即为cluster IP。
Kubernetes也支持端口的DNS SRV(serivce)记录。如果 “my-service.my-ns”服务拥有一个TCP协议名称为”http”的端口,就能够通过”_http._tcp.my-service.my-ns”名称来发现”http”端口的值。Kubernetes DNS服务器是发现ExternalName类型服务的唯一途径。
4、发布服务-服务类型
对于某些应用(例如:前端)的一部分功能,您可能需要暴露一个使用外部IP地址的Sevice。通过Kubernetes的ServiceType,能够指定所使用的service类型。默认情况下使用ClusterIP。Kubernetes的服务类型如下:
- ClusterIP (default) – 将服务暴露在集群内部的IP,此类型仅支持在集群内服务。
- NodePort – 将服务暴露在所选定每一个Node的同一端口,集群外可以通过:方式访问服务。
- LoadBalancer – 在当前的集群中创建一个外部的负载均衡,并为服务(service)指派一个固定的、外部的。
- ExternalName – 使用一个随意的名称(在规格中指定)来暴露服务,并会返回一个带有名称的CNAME记录。此类型不使用代理,这种类型只在kube-dns v1.7上才支持。
4.1 主机端口(NodePort)类型
如果Service的type为“NodePort”,则Kubernetes master将会在每一个Node为此Service暴露一个对外的端口(默认:30000-32767)。外部网络将能够通过[NodeIP]:[NodePort]对服务进行访问。也可以通过nodePort指定此端口,但此端口的值必须在“30000-32767”范围内,手动指定的话需要注意端口存在冲突的可能性。此类型使开发者能够自由的设置自己的负载均衡,即也可以采用Kubernetes未支持的负载均衡技术。
kind:Service apiVersion:v1 metadata: name:my-service spec: type:NodePort #指定Service类型为NodePort selector: app:MyApp ports: - protocol:TCP port:80 targetPort:9376
4.2 负载均衡(LoadBalancer)类型
负载均衡服务是类型为LoadBalance的服务,它建立在NodePord类型服务的基础上。Kubernetes会分配给LoadBalance服务一个内部的虚拟IP,并且暴露NodePort。通过LoadBalanceIP进来的请求,将会被转发给NodePort。
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 #指定Service的类型为LoadBalancer status: loadBalancer: ingress: - ip:146.148.47.155
来自于外部负载均衡的流量将被直接引到后端的Pod中。
4.3 外部IP
如果这里有一些外部IP,通过它们能够路由至一个或者多个集群的Node,Kubernetes服务将可以被暴露在这些externalIPs上。通过外部IP(作为目标IP),ingress导入到集群流量的将被路由到其中的一个服务endpoint上。externalIPs由集群管理员进行管理。所有的服务类型都可以指定externalIPs,在下面的“my-service”服务中,客户端口可以通过“80.11.12.10:80”外部端口来访“my-service”服务。
kind:Service apiVersion:v1 metadata: name:my-service spec: selector: app:MyApp ports: - name:http protocol:TCP port:80 targetPort:9376 externalIPs: #定义外部ip地址 - 80.11.12.10
6、服务的相关kubectl命令
在此部分列示了与服务相关的kubectl命令,服务即可以通过YAML,也可以直接通过命令创建。命令的详细信息可以参考:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands。
1)kubectl create service clusterip
此命令用于创建一个ClusterIP类型的服务,示例如下:
$ kubectl create service clusterip my-cs --tcp=5678:8080
2)kubectl create service externalname
此命令用于创建一个ExternalName类型的服务,示例如下:
$ kubectl create service externalname my-ns --external-name bar.com
ExternalName类型的服务引用外部DNS地址而不是仅POD,这将允许应用程序可以引用其他平台上、其他集群和本地的服务。
3)kubectl create service loadbalancer
此命令用于创建一个LoadBalancer类型的服务,示例如下:
$ kubectl create service loadbalancer my-lbs --tcp=5678:8080
4)kubectl create service nodeport
此命令用于创建一个NodePort类型的服务,示例如下:
$ kubectl create service nodeport my-ns --tcp=5678:8080
5)kubectl expose
此命令用于为资源暴露服务,这些资源包括pod (po), service (svc), replicationcontroller (rc), deployment (deploy), replicaset (rs)。下面是为nginx部署暴露服务的示例:
$ kubectl expose deployment nginx --port=80 --target-port=8000
只有当其拥有的选择器可转换为服务支持的选择器时,即当选择器只包含matchLabels组件时,部署或副本集才会作为服务公开。注意,如果没有通过–port指定端口,并且被暴露的资源具有多个端口,则服务将会使用这些端口。此外,如果没有指定标签,新服务将会使用来自资源的标签。
参考资料
1.《Services》地址:https://kubernetes.io/docs/concepts/services-networking/service/
2.《kubectl-commands service》地址:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-service-em-
作者简介:
季向远,北京神舟航天软件技术有限公司产品经理。本文版权归原作者所有。