Kubernetes-核心资源之Service

在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。

Services overview diagram for iptables proxy

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的同一端口,集群外可以通过<NodeIP>:<NodePort>方式访问服务。
  • 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-

作者简介:
季向远,北京神舟航天软件技术有限公司产品经理。本文版权归原作者所有。

K8S中文社区微信公众号

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址