Service

Kubernetes Pods 是有生命周期的,它们产生和死亡,一旦死亡就不会复活。ReplicationControllers 能动态创建和销毁Pod (例如,当扩容、缩容或在 rolling updates 时)。尽管每个Pod 有自己的IP,但这些IP可能是会变化的。这导致一个问题:如果一些Pod (称为backend)为Kubernetes群集中的其他Pod (称为frontend)提供功能,那么frontend如何找到,并一直能找到backend呢?

译者注:下面将backend译为后端,frontend译为前端。

可用Services 来解决该问题。

Kubernetes Service 是一个抽象,它定义了一组逻辑Pod 和一个访问它们的策略——有时称为微服务。 Service 所关联的一组Pod (通常)由 Label Selector 决定(详见下文,为什么您可能需要没有选择器的Service )。

例如,假设一个图片处理的后端有三个正在运行的副本。这些副本是可替代的——前端不关心他们使用哪个后端副本。虽然构成后端的实际Pod 可能会改变,但前端客户端不应该也没必要跟踪自己的后端列表(例如:知道后端副本列表的地址等待)。 Service 抽象可实现这种解耦。

对于Kubernetes-native应用程序,Kubernetes提供了一个简单的Endpoint API,只要Service 中的Pod 发生变化,它将被更新。对于non-native应用程序,Kubernetes为Service提供了一个基于虚拟IP的网桥,该网桥可重定向到后端 Pod

定义Service

Kubernetes中的Service 是一个REST对象,类似于Pod 。 像所有的REST对象一样, Service 定义可被POST到apiserver,从而创建一个新的实例。 例如,假设您有一组Pod ,每个Pod 都暴露端口9376,并携带标签"app=MyApp"

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - protocol: TCP
  10. port: 80
  11. targetPort: 9376

使用此文件,即可创建一个名为“my-service”的新Service 对象,该对象会代理那些使用TCP端口9376,并且有"app=MyApp" 标签的Pod,该Service 还会被分配一个IP地址(有时称为“Cluster IP”),它会被服务的代理使用(见下文)。 Service 的选择器将会被持续评估,处理的结果会被POST到一个名为“my-service”的Endpoint 对象。

请注意, Service 可将传入端口port 映射到任意targetPort端口。默认情况下, targetPort 将被设置为与port 字段相同的值。 也许更有趣的是, targetPort 可以是字符串,指向到是后端Pod 端口的名称。拥有该名称的端口的实际端口在每个后端Pod 中可能并不相同。这为Services 提供了很大的灵活性。例如,您可以在后端软件的下一个版本中更改该Pod所暴露的端口,而不会影响客户端的调用。

Kubernetes Service支持TCPUDP 协议。 默认值为TCP

Services without selectors(不带选择器的Service)

Service通常抽象了 Pod 的访问,但也可抽象其他类型的后端。 例如:

  • 您希望在生产环境中使用外部数据库集群,但在测试中,您将使用自己的数据库。
  • 您希望将Service指向另一个 Namespace 的Service或其他集群中的Service。
  • 您正在将工作负载迁移到Kubernetes,部分后端运行在Kubernetes之外。

在以上这些情况下,您可以定义不带选择器的Service:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. ports:
  7. - protocol: TCP
  8. port: 80
  9. targetPort: 9376

因为该Service没有选择器,所以不会创建相应的Endpoint 对象。可手动将Service映射到指定的Endpoint

  1. kind: Endpoints
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. subsets:
  6. - addresses:
  7. - ip: 1.2.3.4
  8. ports:
  9. - 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)。

译者按:loopback、link-local、link-local multicast拓展阅读:https://4sysops.com/archives/ipv6-tutorial-part-6-site-local-addresses-and-link-local-addresses/

访问不带选择器的Service 与访问有选择器Service相同。流量将被路由到用户定义的端点(在本例中为1.2.3.4:9376 )。

ExternalName Service一种特殊的Service:它不带选择器,也不定义任何端口或端点。相反,它可以作为将别名返回到外部Service的一种方式。

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. namespace: prod
  6. spec:
  7. type: ExternalName
  8. externalName: my.database.example.com

当查找主机 my-service.prod.svc.CLUSTER 时,集群DNS服务将会返回一条值为my.database.example.comCNAME 记录。访问这样的Service与访问其他Service的工作方式相同,唯一的区别是重定向发生在DNS级别,并且不会发生代理或转发。如果您以后决定将数据库移动到集群中,可启动对应的Pod,添加适当的选择器或Endpoint并更改服务的type

Virtual IPs and service proxies(虚拟IP与Service代理)

Kubernetes集群中的每个Node都运行着一个kube-proxykube-proxy负责为Service实现一个虚拟IP形式,ExternalName 类型的Service除外。在Kubernetes v1.0中,使用userspace模式进行代理。在Kubernetes v1.1中,添加了iptables代理,但不是默认的操作模式。从Kubernetes v1.2开始,默认使用iptables代理。

从Kubernetes v1.0起, Services 是“4层”(TCP / UDP over IP)结构。在Kubernetes v1.1中,添加了Ingress API(beta)来表示“7层”(HTTP)服务。

Proxy-mode: userspace(代理模式:userspace)

在这种模式下,kube-proxy会监视Kubernetes Master对ServiceEndpoints 对象的添加和删除。对于每个Service ,它将在本地Node上打开一个端口(随机选择),记为“代理端口”。与该“代理端口”的任何连接将被代理到Service 的其中一个后端Pod (由 Endpoint 报告)。使用哪个后端Pod 是根据Service的SessionAffinity 决定的。 最后,它安装iptables规则,捕获到ServiceclusterIP:Port 的流量,并将流量重定向到代理端口,代理端口再代理后端Pod

这样,任何绑定Service IP:端口 的流量都被代理到合适的后端,而客户端不需要知道有关Kubernetes或ServicePod 的任何内容。

默认情况下,后端使用算法轮询选择Pod。要想实现基于客户端IP的会话亲和性(Client-IP based session affinity),可将service.spec.sessionAffinity 设置为"ClientIP" (默认为"None" );此时,可通过service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 字段设置最大会话粘性时间(默认值为“10800”)

26-Service - 图1

Proxy-mode: iptables(代理模式:iptables)

在这种模式下,kube-proxy会监视Kubernetes Master对ServiceEndpoints 对象的添加和删除。它将对于每个Service 安装iptables规则,从而捕获到ServiceclusterIP:Port 的流量,并将流量重定向到Service 的其中一个后端Pod。 对于每个Endpoint 对象,它会安装选择后端Pod 的iptables规则。

默认情况下,后端使用随机算法的选择Pod。要想实现基于客户端IP的会话亲和性(Client-IP based session affinity),可将service.spec.sessionAffinity 设为"ClientIP" (默认为"None" );此时,可通过service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 字段设置最大会话粘性时间(默认值为“10800”)。

与用户userspace proxy模式一样,最终,任何绑定Service IP:端口 的流量都被代理到合适的后端,而客户端无需有关Kubernetes或ServicesPods 的任何内容。这种模式应该比userspace proxy模式更快更可靠。但是,与userspace proxy模式不同,如果最初选择的Pod不响应,iptables代理不能自动重试请求另一个Pod,因此它依赖 readiness probes

26-Service - 图2

译者按:

  1. 简而言之,就是userspace方式,使用kubeproxy来转发;而iptables模式中,kube-proxy只生成相应的iptables规则,转发由iptables实现。
  2. 拓展阅读:http://blog.csdn.net/liyingke112/article/details/76022267

Multi-Port Services(多端口Service)

某些Service 可能需要暴露多个端口。对于这种情况,Kubernetes支持Service 对象上定义多个端口。当使用多个端口时,您必须提供所有端口名称,防止Endpoint产生歧义。 例如:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - name: http
  10. protocol: TCP
  11. port: 80
  12. targetPort: 9376
  13. - name: https
  14. protocol: TCP
  15. port: 443
  16. targetPort: 9377

Choosing your own IP address(选择自己的IP地址)

Service 创建时,可指定自己的Cluster IP。可通过spec.clusterIP 字段设置Cluster IP。 例如,如果您想替换一条现有DNS条目,或者有一个配置了固定IP、很难重新配置的遗留系统。用户选择的IP地址必须是合法的IP地址,并且该IP在service-cluster-ip-range CIDR范围内,该范围由API Server的标识指定。 如果IP不合法,apiserver将返回一个422 HTTP状态码,表示该值不合法。

Why not use round-robin DNS?(为什么不使用轮询DNS?)

为什么要使用虚拟IP,而非标准的round-robin DNS呢?有几个原因:

  • 长久以来,DNS库都未能实现DNS TTL并缓存域名查询结果。
  • 许多应用只执行一次DNS查询,并缓存结果。
  • 即使应用程序和DNS库都进行了适当的重新解析,客户端重新解析DNS造成的负载难以管理。

我们试图阻止用户吃力不讨好的事情。也就是说,如果有足够的人要求该功能,我们也可实现该方案作为替代方案。

Discovering services(服务发现)

Kubernetes主要支持两种方式找到 Service :环境变量和DNS。

Environment variables(环境变量)

PodNode 上运行时,kubelet会为每个活动的Service 添加一组环境变量。它支持 Docker links compatible 变量(请参阅 makeLinkVariables )和更简单的{SVCNAME}_SERVICE_HOST 以及 {SVCNAME}_SERVICE_PORT 变量,其中服务名称是大写的,中划线将会转换为下划线。

例如,暴露TCP端口6379,并已分配 Cluster IP地址10.0.0.11 的服务 redis-master ,将会产生以下环境变量:

  1. REDIS_MASTER_SERVICE_HOST=10.0.0.11
  2. REDIS_MASTER_SERVICE_PORT=6379
  3. REDIS_MASTER_PORT=tcp://10.0.0.11:6379
  4. REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
  5. REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
  6. REDIS_MASTER_PORT_6379_TCP_PORT=6379
  7. REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

这意味着有顺序的需求 —— Pod 想要访问的任何Service 必须在该Pod 之前创建,否则环境变量将不会被赋值。DNS没有这个限制。

DNS

DNS server是一个可选的(虽然强烈推荐) cluster add-on 。DNS server监视创建新Services 的Kubernetes API,并为每个Service创建一组DNS记录。如果在整个集群中启用了DNS,那么所有的Pod 都应该能够自动进行Service 名称解析。

例如,如果在Kubernetes my-ns 这个 Namespace 中,有一个名为my-service 的Service,就会创建my-service.my-ns 的DNS记录。 存在于"my-ns" 这个Namespace中的Pod 可通过my-service 这个名称查找来找到这个Service。存在于其他NamespacesPod 将必须使用my-service.my-ns 。使用服务名称查询的结果是Cluster IP。

Kubernetes还支持命名端口的DNS SRV(Service)记录。如果my-service.my-ns 这个 Service 有一个名为http 、协议为TCP 的端口,则可以使用 _http._tcp.my-service.my-ns 的DNS SRV查询来发现http 这个端口的端口号 。

Kubernetes DNS Server是访问ExternalName 类型Service的唯一方法。 DNS Pods and Services 提供了更多信息。

Headless Service

有时你不需要或不想使用负载均衡,以及一个单独的Service IP。在这种情况下,您可以通过将Cluster IP( spec.clusterIP )指定None 来创建Headless Service。

此选项允许开发人员通过允许由他们实现自己的服务发现方式,从而减少与Kubernetes系统的耦合。应用程序仍可使用自注册的模式,并且可轻松地将此适配器用于其他的服务发现系统。

对于这类Services ,不会分配Cluster IP,kube-proxy也不会处理它们,并且平台也不会对它们进行负载均衡或代理。如何自动配置DNS,取决于Service是否定义了选择器。

With selectors(有选择器)

对于定义了选择器的Headless Services,Endpoint Controller会在API中创建Endpoint 记录,并修改DNS配置以返回Pods A记录(地址),A记录直接指向Service后端的Pod。

Without selectors(无选择器)

对于没有选择器的Headless Service,Endpoint Controller不会创建Endpoint 记录。但是,DNS系统会寻找并配置:

  • ExternalName 类型Service的CNAME记录。
  • 所有与Service共享名称的Endpoints 的记录,以及其他类型。

Publishing services - service types(暴露Service——服务类型)

对于应用程序的某些部分(例如前端),您可能希望将服务暴露到一个外部(集群外部)IP中。

Kubernetes ServiceTypes 允许您指定所需的Service类型。 默认是ClusterIP

Type 值及其行为如下:

  • ClusterIP :通过集群内部的IP暴露Service,Service只能从集群内部访问。这是默认的ServiceType
  • NodePort :在每个Node的IP的静态端口上暴露该Service。NodePort Service会路由到 ClusterIP Service。NodePort类型的集群外的请求可通过 <NodeIP>:<NodePort> 与访问一个NodePort 类型的Service。
  • LoadBalancer :使用云提供商提供的负载均衡器外部暴露服务。负载均衡器将会路由NodePortClusterIP 类型的Service。
  • ExternalName :通过返回带有其值的CNAME 记录,将Service映射到externalName 字段的内容(例如foo.bar.example.com )。没有任何代理设置。这需要kube-dns 1.7或更高版本。

Type NodePort(NodePort类型)

如果将type 字段设为NodePort ,则Kubernetes Master将会从给定的配置范围(默认30000-32767)分配一个端口,并且每个Node都会将该端口(每个Node上的同一端口)代理到Service 。该端口将在Servicespec.ports[*].nodePort 字段中报告。

如果您想要一个特定的端口号,可在nodePort 字段的值,系统会为您分配该端口,否则API事务将会失败(即:您需要自己关心可能的端口冲突)。您指定的值必须在Node端口的配置范围内(默认30000-32767)。

这使开发人员可自由设置自己的负载均衡器,配置Kubernetes不完全支持的环境,甚至直接暴露一个或多个Node的IP。

请注意,此Service将在 <NodeIP>:spec.ports[*].nodePortspec.clusterIp:spec.ports[*].port 可见。

Type LoadBalancer(LoadBalancer类型)

External IPs(外部IP)

如果外部IP路由到一个或多个集群Node上,则可在这些externalIPs 上暴露Kubernetes Service。使用外部IP:端口(作为目标IP)进入集群的流量,将被路由到其中一个Service Endpoint。externalIPs 不由Kubernetes管理,这属于集群管理员的职责。

在ServiceSpec中,ServiceTypes 的Service都可指定externalIPs 。 如下示例中,my-service 可通过80.11.12.10:80访问(externalIP:port)

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: my-service
  5. spec:
  6. selector:
  7. app: MyApp
  8. ports:
  9. - name: http
  10. protocol: TCP
  11. port: 80
  12. targetPort: 9376
  13. externalIPs:
  14. - 80.11.12.10

Shortcomings(缺点)

使用VIP的userspace proxy可在中小规模的情况下工作,但无法扩容到有数千个Service的非常大的集群。有关详细信息,请参阅 the original design proposal for portals(门户网站的原始设计方案)

使用userspace proxy会隐藏访问Service 的数据包的源IP。这使得某些防火墙无法实现。iptables proxier不会隐藏群集内的源IP,但它仍会影响到使用负载均衡器或node-port的客户端。

译者按:但它仍会影响到使用负载均衡器或node-port的客户端是什么意思?

Type 字段支持嵌套——每个级别添加之前级别的功能。不会严格要求所有云提供商这么玩(例如Google Compute Engine不需要分配NodePort 来使LoadBalancer 工作,但AWS),但当前API是强制要求的。

Future work(未来的工作)

将来,代理策略可能会比简单的round-robin均衡策略更加的细致,例如Master选举或分片。Services 也将会拥有“真正的”负载均衡器,在这种情况下,VIP只会将数据包传输到负载均衡器上。

我们打算改进对L7(HTTP) Service 的支持。

我们打算为Service 提供更灵活的入口模式,包括当前的ClusterIPNodePortLoadBalancer 模式等。

The gory details of virtual IPs(虚拟IP血腥的细节)

对于那些只想使用Service 的人来说,之前的信息应该已经足够。不过,幕后还有很多值得我们理解的事情。

Avoiding collisions(避免冲突)

Kubernetes的主要哲学之一是,用户不应被暴露在那些“可能会导致他们操作失败,但又不是他们的过错”的场景。在这种情况下,我们来检查网络端口——用户不应该必须选择端口,如果该选择可能与其他用户发生冲突。 那就是“隔离失败”。

译者按:笔者理解的“隔离失败”,指的是尽管两个用户之间是隔离的,但仍可能发生失败。

为了允许用户为其Service 选择端口,我们必须确保两个Service 不会相冲突。我们通过为每个Service 分配自己的IP地址的方式来实现。

为了确保每个Service接收到唯一的IP,内部分配器将在创建每个Service之前,以原子方式更新etcd中的全局分配映射表。映射表对象必须存在于服务的注册表中,从而获取IP,否则创建将失败,并显示无法分配IP的消息。后台Controller负责创建该映射表(老版本使用的是内存锁),并检查由于管理员干预而造成的无效分配,并清理那些已经分配但尚无Service使用的IP。

IPs and VIPs

与实际路由到固定目的地的Pod IP地址不同, Service IP实际上不由单个主机来应答。相反,我们使用iptables (Linux中的数据包处理逻辑)定义虚拟IP,从而根据需要透明地进行重定向。当客户端连接到VIP时,其流量将自动传输到适当的端点。Service的环境变量和DNS实际上是根据Service 的VIP和端口填写的。

我们支持两种代理模式——userspace和iptables,运行方式略有不同。

Userspace

作为示例,考虑上面提到的图像处理应用。创建后端Service ,Kubernetes Master分配虚拟IP地址,例如10.0.0.1 。 假设Service 端口为1234, Service 由集群中的所有kube-proxy 实例观察。 当kube-proxy看到一个新的Service ,它会随机打开一个新的端口,建立从VIP到这个新端口的iptables重定向,然后开始接受连接。

当客户端连接到VIP时,iptables规则开始起作用,并将数据包重定向到Service proxy 的端口。Service proxy 选择一个后端节点,并开始代理从客户端到后端的流量。

这意味着Service 所有者可选择他们想要的任何端口,没有冲突的风险。客户端可简单地连接到IP和端口,而无需知道它们正在访问哪个Pod

Iptables

再次考虑上述图像处理应用。创建后端Service ,Kubernetes Master分配虚拟IP地址,例如10.0.0.1 。 假设Service 端口为1234, Service 由集群中的所有kube-proxy 实例观察。当代理看到一个新的Service 时,它会安装一系列从VIP重定向到per-Service 的iptables规则。 per-Service 的规则链接到per-Endpoint 规则,per-Endpoint 规则会重定向(目标NAT)到后端。

当客户端连接到VIP时,iptables规则就会起作用。选择一个后端(基于会话亲和性或随机),并将数据包重定向到后端。与userspace proxy不同,数据包不会复制到userspace,因此无需运行kube-proxy才能使VIP工作,客户端IP也不会被更改。

当流量通过node-port或负载均衡器进入时,同样的基本流程就会执行,尽管在这种情况下,客户端IP会被改变。

API Object

Service是Kubernetes REST API中的顶级资源。 有关API对象的更多详细信息,请访问: Service API object

For More Information

阅读 Connecting a Front End to a Back End Using a Service

原文

https://kubernetes.io/docs/concepts/services-networking/service/