「Kubernetes Objects」- Service(学习笔记)

  CREATED BY JENKINSBOT

问题描述

Pod 会被创建,并且还会消失,这由 ReplicaSets 控制。每个Pod都有自己的IP地址,但是这些IP地址不能视为可靠的。

那么,如果前端的一部分Pod依赖于后端的Pod,那前端的这些Pod如何找出并追踪后端的Pod?

解决方案

Service,服务,用于暴露 Pod 以供访问。

Service 是一个抽象,定义 Pod 的逻辑集合 以及谁可以访问它们的 策略 (有时称为微服务)。 Service 指向的 Pod集 由 Label Selector 确定(由写Service也没有选择器)。

一个场景,后端运行某个镜像的三个副本,前端并不关心访问哪个副本。后端的Pod副本可能会变化,但是前端不应该去关心或者追踪后端的变化。 Service 的抽象提供了这种解耦。

??? 对于 Kubernetes本机应用程序 ,Kubernetes提供了一个简单的Endpoints API,只要服务中的 Pod集 发生变化,它就会更新。 对于 非本机应用程序 ,Kubernetes提供基于虚拟IP的服务桥接,重定向到后端Pod

定义服务(Defining a Service)

Service 也是一个REST对象,像Pod一样。将定义提交给apiserver来创建对象。

假设有个Pod暴露了9376端口,并且标签为app: MyApp,到该Pod集的Service可以这么定义:

# foo.yaml
# 定义一个服务
kind: Service
apiVersion: v1
metadata:
  # 服务名
  name: my-service
spec:
  selector:
    # 流量发往标签为「app: MyApp」的Pod上
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    # 流量发往Pod的这个端口上。
    targetPort: 9376

??? Service 会被分配一个IP地址(有时称为 Cluster IP ),被 服务代理 使用。将连续评估 Service 的选择器,并将结果发布到名为“my-service”的 Endpoints 对象。

注意事项,Service 可以映射一个入口 port 到任何 targetPort 。默认 port 和 targetPort 是相同的。

targetPort 也可以是一个字符串,指向后端 Pod 的端口名(由containers.ports.name定义),分配到该字符串的端口号可以在每个Pod中不同。这非常利于部署和升级 Service 。例如,下次Pod版本升级的时候可以修改端口,而无需打破客户端。

默认使用TCP协议,支持的协议参考 supported protocol 手册。某些服务需要暴露多个端口,可以在 Service 中定义多个端口,并使用不同的协议。

Services without selectors

除了抽象 Pod 以外,还可以抽象其他类型的后端,例如:

	生产环境有外部的数据库集群,但是测试中你想使用自己的数据库
	你想将 Service 指向其他 Namespace 中的 Service (或者其他集群)
	迁移工作负载到 k8s 集群,但是有些后端服务运行在 k8s 之外

对于以上场景,可以这么做:

# service-foo.yaml
# 定义没有选择器的后端服务,因此也不会创建对应的 EndPoints 对象
kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376

# 然后将 Service 手动映射到特定的后端

# endpoints-bar.yaml
kind: Endpoints
apiVersion: v1
metadata:
  # 名称与Service的相同
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

# !!! Endpoints的IP可能不是环回(127.0.0.0/8),链路本地(169.254.0.0/16)或链路本地多播(224.0.0.0/24)。
# !!! 它们不能是其他Kubernetes服务的集群IP,因为kube-proxy组件不支持 VIP 作为目标地址。

到达Service的流量会被发送到Endpoints定义的地址(1.2.3.4:9376)。

ExternalName

是种特殊情况的 Service,它没有 选择器 而是使用DNS名称。 有关详细信息,请参阅本文档后面的 ExternalName 部分。

Endpoint Slices

WIP

虚拟网络地址 / 服务代理

WIP

多个端口的服务(Multi-Port Services)

多端口的Service需要指定端口名称:

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-web 或 web
# !!! 无效:123_abc

选择自己的集群网络地址(Choosing your own IP address)

在服务创建是,可以指定Cluster IP地址。通过设置.spec.clusterIP字段。例如,你有一个想重新使用的DNS条目,或者系统中使用了某个IP地址并且很难配置。用户选择的IP地址必须是在API Server中的service-cluster-ip-range设置的CIDR范围。如果IP地址无效,则API Server返回442状态码。

为什么不使用 轮循 DNS ?

为什么使用诸如IPVS来实现Round-Robin等等,而不使用Round-Robin DNS来实现:

	DNS库一直都不遵照DNS TTL,并且不缓存名称查找的结果。
	许多应用程序执行DNS查找一次,并缓存结果。
	即使应用程序和库进行了适当的重新解析,每个客户端反复重新解析DNS的负载也难以管理。

官方也说了,如果想用循环DNS的人多,人家也会实现这个功能。

发现服务(Discovering services)

两种查找服务的方式:(1)环境变量;(2)DNS;

环境变量

在Node上运行Pod时,kubelet会为活动的Server添加一系列的环境变量。它支持 Dokcer link 兼容的环境变量 , {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_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

隐含要求:被 Pod 访问的服务要先创建,否则服务的环境变量不会写入到 Pod 中(使用 DNS 就没有这个限制)。

DNS

通过安装提供 DNS 服务的集群插件。

集群感知的 DNS Server 会观察 K8s API 的服务创建,然后为每个服务生成一条DNS记录。如果在整个群集中启用了DNS,则所有Pod应该能够自动对服务进行名称解析。

例如,服务”my-service”,命名空间”my-ns”,则DNS为”my-service.my-ns”。同命名空间的Pod可以直接使用”my-service”访问,不同命名空间的服务可以使用”my-service.my-ns”进行访问。解析的结果是集群的IP地址。

除此之外还支持 DNS SRV 记录。例如,”my-service.my-ns” 的 Service 有个名为 ”http“ 的端口,使用”tcp“协议,则可以进行 DNS SRV 查询 ”_http._tcp.my-service.my-ns“ 来发现端口号。

Kubernetes DNS 是访问 ExternalName 类型服务的唯一方式。更多信息参考 DNS Pods and Services 中的说明。

Headless Services

有时候不需要负载均衡或者服务网络地址,可以将 .spec.clusterIP 设置为 ”None“ 来创建无头服务。

此选项允许开发人员”通过允许他们自由地以自己的方式进行发现“来减少与Kubernetes系统的耦合。 应用程序仍然可以使用自注册模式,并且可以轻松地在此 API 上构建适用于其他发现系统的适配器。

对于此类服务,未分配 Cluster IP,kube-proxy 不处理这些服务,并且平台没有为它们执行负载均衡或代理。如何自动配置 DNS 取决于服务是否已定义选择器:

已定义 Selector 的服务

已经定义选择器的无头服务, Endpoints Controller 会在 API 中创建 Endpoints 记录,并修改 DNS 配置来返回一个 A 记录,该记录直接指向 Service 后端的 Pod 实例

没有 Cluster IP 参数(None),因此 kube-proxy 也不会管理该 Serivce 对象,因此也不会有什么 ipvs、port、targetPort 等等参数。

未定义 Selector 的服务

如果没有定义选择器, Endpoints Controller 不会创建 Endpoints 记录。但是 DNS 系统会查找并配置:
1)ExternalName 类型服务的 CNAME 记录
2)为所有其他类型的,与服务共享名称的任何 Endpoints 的记录

Publishing Services (ServiceTypes)

有时我们想将服务暴露出来供外部访问,可以使用 ServiceTypes 来指定服务类型以暴露服务。

类型的行为如下:
1)ClusterIP – 在集群内部的IP地址上暴露服务,只能由集群内部访问。这是默认的服务类型
2)NodePort – 在每个Node的IP地址上暴露一个端口(NodePort)。会自动创建一个ClusterIP服务,NodePort的服务会路由到该服务上。可以使用NodeIP:NodePort来访问该服务。
3)LoadBalancer – 使用云商的负载均衡器在外部公开服务。 将自动创建”外部负载均衡器将路由到的“NodePortClusterIP服务。
4)ExternalName – 通过返回带有其值的CNAME记录,将服务映射到externalName字段的内容(例如foo.bar.example.com)。 没有设置任何类型的代理。 这需要版本1.7或更高版本的kube-dns

NodePort

从–service-node-port-range标志指定的范围(30000-32767)分配一个端口,每个节点都会代理该端口到服务。在服务的.spec.ports[*].nodePort字段查看端口。

如果要指定用于代理端口的特定IP地址,可以将kube-proxy中的–nodeport-addresses标志设置为特定的IP块(自Kubernetes v1.10起支持)。 以逗号分隔的IP块列表(例如10.0.0.0/8,1.2.3.4/32)用于过滤此节点的本地地址。 例如,如果您使用标志–nodeport-addresses=127.0.0.0/8来启动kube-proxy,则kube-proxy将仅为NodePort服务选择环回接口。 –nodeport-addresses默认为空([]),这意味着选择所有可用的接口,并符合当前的NodePort行为。

如果需要特定的端口号,可以在nodePort字段中指定一个值,系统将为您分配该端口,否则API事务将失败(即您需要自己处理可能的端口冲突)。 您指定的值必须在节点端口的配置范围内。

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

请注意,此服务将同时显示为<NodeIP>:spec.ports[*].nodePort.spec.clusterIP:spec.ports[*].port。(如果设置了kube-proxy中的–nodeport-addresses标志,则会过滤NodeIP。)

LoadBalancer

在支持提供外部负载均衡器的云商上,将type设置为LoadBalancer将会为你的Service规定一个负载均衡器。负载均衡器的实际创建是异步发生的,有关已配置的均衡器的信息将发布在Service的.status.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

来自外部负载均衡器的流量被直接重定向到Pod,虽然具体如何运作取决于云提供商。

某些云商允许指定loadBalancerIP字段,在这些情况下,在这些情况下,将使用用户指定的loadBalancerIP创建负载均衡器。如果未指定loadBalancerIP字段,则会将临时IP分配给loadBalancer。如果指定了loadBalancerIP,但云提供程序不支持该功能,则该字段将被忽略。

TODO 负载均衡类型的Service服务

ExternalName

该类型的服务将服务映射到DNS名,而不是一个选择器,可以使用spec.externalName指定参数:

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

# ExternalName 接受 IPv4 地址字符串,但是被解释为由数字组成的 DNS 名称,而不是网络地址。
# 类似于IPv4地址的ExternalNames不会被CoreDNS或ingress-nginx解析,因为ExternalName旨在指定规范的DNS名称。
# 要对IP地址进行硬编码,请考虑无头服务。

当查找 my-service.prod.svc.cluster.local 域名时,DNS 服务将会返回一个 CNAME,值为 my.database.example.com。访问该服务时,与访问其他服务没有区别,关键是这发生在DNS层面,而不时代理或者转发。之后,如果你想将外部服务迁移到k8s中,你只需要创建对应的Pod,添加选择器或者Endpoints,然后修改type字段。

External IP

Service | Kubernetes/External IPs
Learn how to use Kubernetes External IP service type | by Mohamad Fadhil | The Startup | Medium

如果有 外部IP 路由到一个或多个群集节点,则可以在这些 External IP 上公开 Kubernetes 服务。在 Service 的 Port 上使用 External IP (作为目标IP)进入群集的流量将路由到其中一个服务 Endpoints。externalIPs 不由 Kubernetes 管理,是集群管理员的责任。

在 Service.spec 中,任何服务类型都能指定 externalIPs 属性。

如下示例,通过 80.11.12.10:80(externalIP:port)来访问 my-service 服务:

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

在内网中,如果是测试环境:
1)将 externalIPs 设置为 Worker 的 IP-Address,则能通过 IP-Address:port 直接访问服务,但存在不确定性,还会影响节点状态,所以不建议使用;
2)或者使用同网段的其他地址,但是需要管理员在某个 Worker 上绑定该地址(externalIPs 由管理员负责管理) ,还会影响到网络插件检测地址,需要额外调整,不建议使用;

TODO !!! Kubernetes Service ExernalIP 的使用方法,能否使用同网段内网地址;
当使用 ExternalIP 方式时,externalIPs 应为公网地址(或在私网内不同网段地址)。

缺点

使用VIP的用户空间的代理将在中小规模上工作,但不会扩展到具有数千个服务的非常大的集群。 有关详细信息,请参阅 门户网站的原始设计提案

使用用户空间代理会模糊访问服务的数据包的源IP,这使得某些类型的防火墙变得不可能。iptables代理器,不会掩盖集群内的源IP,但它仍会影响通过负载均衡器或节点端口的客户端。

“type”字段设计为嵌套功能 – 每个级别都添加到前一个级别。 并非所有云提供商都严格要求这样做(例如,Google Compute Engine不需要分配NodePort来使LoadBalancer正常工作,但AWS会这样做)但当前的API需要它。

未来的工作

在未来,我们设想代理策略可以比简单的循环平衡更加细微,例如主选或分片。 我们还设想一些服务将具有“真正的”负载均衡器,在这种情况下,VIP将简单地在那里传输数据包。

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

我们打算为Service提供更灵活的Ingress模式,包括当前的ClusterIP,NodePort和LoadBalancer模式等等

有关虚拟IP的更多细节

对于许多只想使用服务的人来说,之前的信息应该足够了。 然而,幕后有很多可能值得理解的事情

避免碰撞

Kubernetes的主要哲学之一是用户不应该暴露于可能导致他们的行为失败的情况,而不是他们自己的过错。在这种情况下,我们正在寻找网络端口 – 如果该选择可能与另一个用户发生冲突,则用户不必选择端口号。这是隔离失败。

为了允许用户为其服务选择端口号,我们必须确保没有两个服务可以冲突。我们通过为每个服务分配自己的IP地址来做到这一点。

为了确保每个服务都接收到唯一的IP,内部分配器在创建每个服务之前以原子方式更新etcd中的全局分配映射。映射对象必须存在于注册表中以获取IP的服务,否则创建将失败,并显示一条消息,指示无法分配IP。后台控制器负责创建该映射(从内存锁定中使用的旧版Kubernetes迁移)以及由于管理员干预而检查无效分配,并清除已分配但当前没有服务使用的任何IP。

IP与VIP

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

我们支持三种代理模式 – userspace,iptables和ipv,它们的运行方式略有不同

Userspace
作为示例,考虑上述镜像应用程序。 创建后端服务时,Kubernetes主服务器会分配一个虚拟IP地址,例如10.0.0.1。 假设服务端口为1234,则群集中的所有kube-proxy实例都会观察到该服务。 当代理看到新服务时,它会打开一个新的随机端口,建立从VIP到这个新端口的iptables重定向,并开始接受它上面的连接。

当客户端连接到VIP时,iptables规则启动,并将数据包重定向到服务代理自己的端口。 服务代理选择后端,并开始代理从客户端到后端的流量。

这意味着服务所有者可以选择他们想要的任何端口而不会发生冲突。 客户端可以简单地连接到IP和端口,而无需知道他们实际访问的是哪些Pod。

Iptables
再次,考虑上述镜像应用程序。 创建后端服务时,Kubernetes主服务器会分配一个虚拟IP地址,例如10.0.0.1。 假设服务端口为1234,则群集中的所有kube-proxy实例都会观察到该服务。 当代理看到新服务时,它会安装一系列iptables规则,这些规则从VIP重定向到每服务规则。 每服务规则链接到每端点规则,该规则将(目标NAT)重定向到后端。

当客户端连接到VIP时,iptables规则启动。选择后端(基于会话亲和性或随机),并将数据包重定向到后端。 与用户空间代理不同,数据包永远不会复制到用户空间,因此不必运行kube-proxy以使VIP工作,并且不会更改客户端IP。

当流量通过节点端口或通过负载均衡器进入时,执行相同的基本流程,但在这些情况下,客户端IP确实会被更改

Ipvs
Iptables操作在大规模集群中显着放缓,例如10,000个服务。 IPVS旨在实现负载均衡并基于内核中的哈希表。 因此,我们可以从基于IPVS的kube-proxy实现大量服务的性能一致性。 同时,基于IPVS的kube-proxy具有更复杂的负载均衡算法(最少的conns,局部性,加权,持久性)

API对象

Service是Kubernetes REST API中的顶级对象。更多API对象的细节可以参考 Service API object 手册。

支持的协议

TCP

Kubernetes v1.0 TCP

UDP

Kubernetes v1.0 UDP 可以为大多数服务使用,对于type=LoadBalancer类型的服务,是否支持要看云商的提供的设施。

HTTP

Kubernetes v1.1 HTTP 如果您的云提供商支持它,您可以使用LoadBalancer模式下的服务来设置外部HTTP/HTTPS反向代理,转发到服务的端点。

PROXY

Kubernetes v1.1 PROXY 如果您的云提供商支持它(例如,AWS),您可以使用LoadBalancer模式下的服务来配置Kubernetes之外的负载均衡器,它将转发带有PROXY协议前缀的连接。负载均衡器将发送一系列描述传入连接的初始八位字节,类似于此示例 PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n,然后是来自客户的数据

SCTP

Kubernetes v1.12 SCTP Kubernetes支持SCTP作为Service,Endpoint,NetworkPolicy,Pod定义中的协议值,作为alpha功能。 要启用此功能,集群管理员需要在apiserver上启用SCTPSupport功能门,例如“–feature-gates=SCTPSupport=true,…”。 启用功能后,用户可以将Service, Endpoint, NetworkPolicy,Pod的protocol字段设置为SCTP。 Kubernetes相应地为SCTP关联设置网络,就像它对TCP连接一样。

支持多宿主SCTP关联
多宿主SCTP关联的支持要求CNI插件可以支持将多个接口和IP地址分配给Pod。

用于多宿主SCTP关联的NAT在相应的内核模块中需要特殊逻辑。

Service with type=LoadBalancer
仅当云商的负载均衡器实现支持SCTP作为协议时,才能创建类型为LoadBalancer和协议SCTP的服务。 否则,将拒绝Service的创建请求。 当前的云负载均衡器提供程序集(Azure,AWS,CloudStack,GCE,OpenStack)不支持SCTP。

Windows
基于Windows的节点不支持SCTP。

用户空间的kube-proxy
当kube-proxy处于用户空间模式时,它不支持管理SCTP关联。

相关链接

关于Service网络:Understanding kubernetes networking: services

配置 Pod 权重:Can Kubernetes Service control traffic percentage to a given pod? – Stack Overflow

参考文献

kubernetes/CONCEPTS/Services
in json schema for service port is a number, but containerPort is a string #2123
Get a Shell to a Running Container
kube-proxy
Kubernetes API v1.18/Service v1 core