Service是Kubernetes中的关键对象,Kubernetes将运行在一组pod上的应用程序公开为网络服务的抽象方法。为应用程序实现自动化的服务发现,为Pods提供了自己的IP地址和一组Pod的单个DNS名称,并且可以在它们之间进行负载均衡。

Service的由来

Kubernetes Pod是有生命周期的,如果使用Deployment来部署,Pod都是动态生成和删除的。也就是说,每个Pod的很多状态都是动态的,例如IP。这样会导致一个问题:如果某些Pod(后端)为集群内的其他Pod(前端)提供功能,前端如何找出并跟踪要连接的后端IP地址 ,以便前端可以接受请求工作并负载到后端?因此,Service诞生了。它就是来满足这类场景需求的Kubernetes对象,帮助前端和后端实现解耦。

Service虚拟IP模式

Kubernetes集群中的每个节点都运行一个kube-proxy。 kube-proxy负责为ExternalName以外的类型的Service实现虚拟IP。实现虚拟IP有以下三种模式:

  • User space proxy mode:>= Kubernetes v1.0
  • iptables proxy mode:>= Kubernetes v1.1 Kubernetes v1.2为默认模式
  • IPVS proxy mode:>= Kubernetes v1.8

想了解实现原理,见这里

配置Service

创建一个Service示例:

my-service.yaml

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service # 指定类型为Service
metadata:
name: my-service # Service名
spec:
selector:
app: MyApp # Pod选择器标签,用以匹配此标签的Pod
ports:
- protocol: TCP
port: 80 # 服务前端端口
targetPort: 9376 # 服务后端Pod端口

Kubernetes为该Service分配一个IP地址(有时称为Cluster IP),由Servcie代理使用。Servcie选择器的控制器不断扫描与其selector匹配的Pod,然后对名为“my-service”的Endpoint对象进行更新。Servcie的默认协议是TCP; 你还可以使用任何其他支持的协议。由于许多Servcie需要公开多个端口,因此Kubernetes支持Service对象上的多个端口定义。 每个端口定义的协议也可不同。

配置不带selectors选择器的Service

如下场景可能会用到:

  • Service配置使用外部数据库集群
  • 将服务指向其他命名空间或其他群集上的服务。
  • 将业务迁移至Kubernetes。 为了评估及验证,你只在Kubernetes中运行一部分后端,另一部分通过Service指向你原来的后端上。

由于此服务没有选择器,因此不会自动创建相应的Endpoint对象。 你可以手动添加Endpoint对象,手动将服务映射到运行它的网络地址和端口:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376

注意:端点IP不能是:loopback(IPv4为127.0.0.0/8,IPv6为::1/128),或本地链路(IPv4为169.254.0.0/16和224.0.0.0/24,IPv6为fe80::/64)。端点IP地址也不能是其他Kubernetes服务的群集IP,因为kube-proxy不支持虚拟IP作为目标。

ExternalName服务是Service的一个特例,它没有选择器而是使用DNS名称。 有关详细信息,请参阅ExternalName部分。

由上我们可以理解,在不配置selectors的情况下,Service类似一个负载均衡器,我们可以灵活的用它来自定义集成k8s内部或外部的后端应用服务。

配置多端口Service

某些服务,你想公开多个端口,Service支持这类配置需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
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

注意:端口名称只能包含小写字母、数字字符和-,且以字母、数字字符开头和结尾。

可设置.spec.clusterIP字段指定自己的群集IP地址。 例如,你已经拥有要重用的现有DNS记录,或者为保留旧的特定IP地址配置。配置的IP地址必须是为API服务器配置的service-cluster-ip-range CIDR范围内的有效IPv4或IPv6地址。 如果使用无效的clusterIP地址值创建Servcie,API服务器将返回422的HTTP错误返回码。

服务发现

Kubernetes支持2种主要的服务发现模式 - 环境变量和DNS

环境变量方式

当Pod在节点上运行时,kubelet为每个活动服务添加一组环境变量。 它支持Docker链接兼容变量(请参阅makeLinkVariables)和更简单的{SVCNAME} _SERVICE_HOST和{SVCNAME} _SERVICE_PORT变量,其中服务名称为大写,短横线转换为下划线。

例如,为暴露TCP端口6379并已分配群集IP地址为10.0.0.11的名为redis-master的service生成以下环境变量:

1
2
3
4
5
6
7
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

如果你有一个需要访问服务的Pod,并且正在使用环境变量方法将端口和Cluster IP发布到客户端Pod,则必须在客户端Pod存在之前创建Service。否则,这些客户端Pod将不会更新其环境变量。如果仅使用DNS来发现服务的Cluster IP,则无需担心此问题。

DNS方式

Kubernetes集群是通过插件方式配置DNS服务,插件详见

DNS服务器(如CoreDNS)会监视Kubernetes API以获取新服务,并为每个服务创建一组DNS记录。 所有Pod应自动通过此DNS解析名称服务。

例如,如果在Kubernetes命名空间“my-ns”中有一个名为“my-service”的服务,则DNS服务会创建“my-service.my-ns”的DNS记录。 “my-ns”命名空间中的Pod应该能够通过简单地为my-service执行名称查找来找到它(“my-service.my-ns”也可以工作)。其他命名空间中的Pod必须将名称限定为my-service.my-ns。 这些名称将解析为Service分配的Cluster IP。

Kubernetes还支持命名端口的DNS SRV(服务)记录。 如果“my-service.my-ns”1服务具有名为“http”且协议设置为TCP的端口,则可以对_http._tcp.my-service.my-ns执行DNS SRV查询以发现端口号 “http”,及其IP地址。

Kubernetes DNS服务器是访问ExternalName服务的唯一方法。

Headless Services

Headless Services即无头服务,有时你不需要负载均衡和Cluster IP。 在这种情况下,你可以通过设置群集IP(.spec.clusterIP)为“None”来创建无头服务。

无头服务未分配Cluster IP,所以kube-proxy不会处理这些服务,且Kubernetes没有为它们分配负载平衡或代理。 如何自动配置DNS取决于服务是否已定义selector选择器。

  • 配置selector:Endpoint控制器会在API中创建Endpoints记录,并且修改DNS配置对应的A记录地址,访问这个地址可直达Service后端的Pod上
  • 不配置selector:Endpoint控制器不会创建Endpoints记录,但DNS系统仍会查寻和配置ExternalName类型的CNAME记录、以及与Service共享一个名称的任何Endpoints

对于配置的selector的无头服务,Servie的名称会被DNS轮循解释到后端的Pod上,且每个Pod在DNS上都有对应的记录,且每个PDd的DNS域名是有规律且不变的(pod_name-id.service_name,其中id编号是从0开始递增的数字),这种特性特别适合我们在Kubernetes中创建有状态的服务(例如rabbitmq集群)。

发布Service

Service可能发布时可选择多种类型,按场景和业务需求来选择。配置ServiceTypes来指定你想要发布的Service类型,默认是ClusterIP。ServiceTypes支持的类型如下:

  • ClusterIP:通过集群的内部IP暴露服务,服务只能够在集群内部可以访问,这也是默认的ServiceType。
  • NodePort:通过每个k8s节点上的IP和静态端口(NodePort)暴露服务。NodePort服务会路由到ClusterIP的服务,这个ClusterIP服务会自动创建。通过请求 :,可以从集群的外部访问一个NodePort服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP服务。
  • ExternalName:通过返回CNAME值,将服务映射到externalName的字段内容,例如,foo.bar.example.com。不会创建任何代理,需要Kubernetes 1.7或更高版本的kube-dns支持。