Kubernetes - Service

Table of Contents

Service 是一组 Pod 上的应用程序服务暴露的抽象方式。在 Kubernetes 中,你无需要修改应用程序即可使用服务发现机制(不入侵应用)。 Kubernetes 为每一个 Pod 提供单独的 IP 并且为一组 Pod 提供单独的 DNS 名称,并且在 Pod 之间进行负载均衡。

服务发现一般分为几种方式:

  1. 应用程序自己写代码实现服务的注册和发现,比如说启动时写入到 etcd 中,然后 watch 变更
  2. 在框架层面实现,比如 Dubbo
  3. 在平台层面实现,比如 Kubernetes,Service Mesh 等

前两种都是需要入侵业务的,而且一般很难跨技术栈。

1. 动机

Kubernetes 的 Pod 不是一个持久的实体。使用 Deployment 运行应用时,它会动态的创建和销毁 Pods。

虽然每一个 Pod 都有自己的 IP 地址,但是 Deployment 中的不同的时间段的 Pod 集合可能不同(有很多原因导致 Pod 重启)。

这就产生了一个问题:如果一些 Pods (称他们为“后端”)为另外集群中的另外一些 Pod(称他们为“前端”) 提供了功能, 前端是如何找到需要连接的后端 Pods 的 IP 地址呢?

答案是 Services

2. Service 资源

Service 是一种抽象,定义了 Pod 的逻辑集合和访问策略(有时称之为微服务)。Service 和目标 Pods 的关联通常由 selector w来关联。

前端不用关心 Pod 的 IP 地址到底是什么,也不用关心 Pod 是不是之前的 Pod。Service 为它们提供了统一的出口。 前端只需要关心 Service 的地址就行了,而 Service 的地址是不会变的。

Service 的抽象使这种解耦成为了可能。

2.1. 云原生的服务发现

在你的应用程序中你可以使用 Kubernetes APIs 做服务发现,可以向 API Server 查询 Endpoints,每当在 Service 中的 Pod 集合发生变更时, 这些更新就会耕者更新。

对于非原生应用,Kubernetes 提供了防止网络端口的方法或者在应用程序和后端 Pod 之间的负载均衡器。

3. 定义 Service

Service 和 Pod 一样,是一个 REST 对象,和其它的对象一样,你可以 POST 一个 Service 到 API server 来创建一个实例。 Service 的名字必须是一个有效的 DNS 标签。

假定你有一组 Pods,每个 Pod 监听 9376 的 TCP 端口,并且标签为 app=MyApp

apiVersion: v1
kind: Service
metadata:
  name: my-service
  spec:
    selector:
      app: MyApp
      ports:
        - protocol: TCP
          port: 80
          targetPort: 9376

以上创建了一个叫做 my-service 的 service 对象。Kubernetes 为 Service 分配一个 IP 地址(有时称为"集群IP"),该地址为服务代理 所用(见下面的虚拟 IP 和服务代理)。

Service 控制器负责持续扫描与选择器相匹配的 Pod,然后更新 my-service 的 Endpoints 。

*注意:* Service 可以把任何 =port= 映射到 =targetPort= 。默认为了方便, =targetPort= 通常和 =port= 相同。

3.1. 没有选择器的 Services

Service 最常见的是抽象对 Pod 集合的访问,但是他们也可以抽象其它类型的后端,比如:

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

这种情况下,你可以定义一个 Service 不指定 Pod 选择器,如下:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  spec:
    ports:
      - protocol: TCP
        port: 80
        targetPort: 9376

因为 Service 没有选择器,对应的 Endpoint 对象不会被创建。你可以手动将 Service 映射到其运行所在网络的地址和端口,手动添加 Endpoint 对象:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
  subsets:
    - addresses:
        - ip: 192.0.2.42
          ports:
            - port: 9376

Endpoints 对象必须是一个有效的 DNS 子域名。

注意:

Endpoint IPs 不可以 为:回环地址(IPv4 的127.0.0.0/8,IPv6 的 ::1/128),或者本地链接(IPv4 的 169.254.0.0/16 和 224.0.0.0/24 IPv6 的 fe80::/64)。

Endpoint IP 地址不能是 Kubernetes Services 集群 IPs,因为 kube-proxy 不支持虚拟 IPs 作为目标。

没有选择器的情况下访问 Service 和有选择器的访问一样。

ExternalName Service 是 Service 的特例,它没有选择器,而是使用 DNS 名称代替。

3.2. EndpointSlices

特性状态: Kubernetes v1.17 [beta]

EndpointSlices 是一种 API 资源,可以为 Endpoints 提供更具扩展性的替代方案。尽管从概念上来讲与 Endpoints 非常相似, 但 EndpointSlices 允许跨多个网络资源分布式网络 endpoints。默认情况下,EndpointSlice 到达 1000 个 endpoints 之后就会被认为是已满。 这时将创建其他的 EndpointSlice,以存储任何其他 endpoints。

EndpointSlices 提供了其他属性和功能,具体见:https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/

3.3. Application Protocol

特性状态: Kubernetes v1.20 [stable]

appProtocol 字段提供了为每个服务端口指定应用程序协议的方法。该字段的值由相应的 Endpoints 和 EndpointSlice 对象镜像(mirror)。

该字段遵循标准的 Kubernetes 标签语法。值应该是 IANA 标准 service 名称 或者类似 mycompany.com/my-custom-protocol 的域名前缀。

4. 虚拟 IPs 和服务代理

Kubernetes 集群中的每一个结点都运行一个 kube-proxykube-proxy 的责任是实现除了 ExternalName 之外的 Services 的虚拟 IP。

4.1. 为什么不使用 DNS 轮询?

经常会有人问,为什么 Kubernetes 要依赖于代理将流量转发到后端,而不是使用其他方式,比如说:是否可以配置具有多个 A 值的 DNS 记录, 并依靠轮询名称进行解析呢?

使用服务代理有以下几个原因:

  • DNS 实现的历史悠久都不遵守记录的 TTLs,并且在查找结果过期后进行缓存。
  • 某些应用仅执行一次 DNS 查找,并无限期缓存结果。
  • 即使应用程序和程序库做了适当的重新解析,DNS 的低或者 0 TTLs 可能会给 DNS 带来高负载,从而使 DNS 变的难以管理。

4.2. 使用空间代理模式(User space proxy mode)

在此模式下,Kube-proxy 监听 Kubernetes master 以添加或者删除 Service 和 Endpoint 对象。 对于每一个 Service ,它都会在本地节点上打开一个端口(随机选择)。与此代理端口的连接将代理到 Service 后端 Pod 之一。 在决定选择哪一个后端 Pod 时,kube-proxy 会考虑 Service 的 SessionAffinity 设置。

最后,user-space 代理安装 iptables 规则,以捕获到 Service 的 clusterIPport 的流量。规则将流量重定向到代理后端 Pod 的 代理端口。

默认情况下,user-proxy 模式下的 kube-proxy 通过轮询算法选择后端。

Sorry, your browser does not support SVG.

Figure 1: from https://kubernetes.io/

4.3. iptables 代理模式

在这种模式下,kube-proxy 监听 Kubernetes 控制面板用于添加和删除 Service 和 Endpoint 对象。对于每一个 Services,它安装 iptables 规则,用来捕获到 Service 的 clusterIPport 的流量,然后将该流量重定向到 Services 的后端集合之一。 对于每一个 Endpoint 对象,都会安装 iptables 规则来选择后端 Pod。

默认情况下,iptables 模式下的 kube-proxy 会随机选择一个后端。

使用 iptables 处理流有较低的系统开销,因为流量由 Linux netfilter 处理,而无需在用户空间和内核空间之间切换。这种方法也可能更可靠。

如果 kube-proxy 在 iptables 模式下运行,并且所选择的第一个 Pod 没有响应,则连接失败。这和 userspace 模式不同在于: 在这种情况下,kube-proxy 会检测与第一个 Pod 的连接失败,并会自动使用其它后端 Pod 重试。

你可以使用 Pod 的可用检测(readiness)探针来检测后端 Pods 是不是正常运行,确保 kube-proxy 只看到经过测试后正常的后端。 这样做可以避免流量通过 kube-proxy 发送到已知故障的 Pod。

Sorry, your browser does not support SVG.

Figure 2: from https://kubernetes.io/

4.4. IPVS 代理模式

Kubernetes v1.11 之后支持的特性, stable

ipvs 模式中,kube-proxy 监听 Kubernetes Services 和 Endpoints,调用 netlink 接口创建对应的 IPVS 规则并定期与 Kubernetes 中的对象保持同步。这种控制循环可确保 IPVS 状态与所期望状态匹配。当访问 Service 时,IPVS 直接转发流量到一个后端 Pod 中。

IPVS 代理模式基于 netfilter hook 函数与 iptables 模式类似,但是它使用哈希表作为基础数据结构,并在内核空间中工作。 这意味着 kube-proxy 在 IPVS 模式重定向流量延迟比 iptables 模式下的 kube-proxy 还要低,在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高网络流量吞吐量。

IPVS 为后端 Pods 流量的负载均衡提供了更多的选项:

  • rr 轮询
  • lc 最小连接(打开连接的最小数量)
  • dh 目标 hashing
  • sh 源 hashing
  • sed 最短预期延迟
  • nq 永不排队(never queue)

注意: 使用 IPVS 模式的 kube-proxy,必须在启动 kube-proxy 之前使 IPVS Linux 在节点上可用。

当 kube-proxy 在 IPVS 代理模式下启动,它会验证 IPVS 内核模块是否可用。如果 IPVS 内核模块没有被检测到,它会以 iptables 代理 模式运行。

Sorry, your browser does not support SVG.

Figure 3: from https://kubernetes.io/

在这些代理模式中,Service 的 IP 流量限制:在客户端不知道 Kubernetes 或者 Service 或者 Pod 的任何信息的情况下,将 Port 代理到合适的后端。

如果你想确保每次都将来自特定的客户端的连接都传递给相同的 Pod,你可以通过设置客户端基于 IP 地址的会话亲和性 service.spec.sessionAffinity 设置为 "ClientIP" (默认情况下是 "None")。你也可以适当的设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 来控制最大会话粘性时间。(默认是 10800,3小时)。

5. 多端口 Services

对于一些 Services,你需要暴露多个端口。Kubernetes 允许你在 Service 对象定义是配置多个端口。 当一个 Service 包含多个端口时,你必须要设置所有端口的名字以消除歧义。比如:

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

注意: 与 Kubernetes 名称的规范一样,端口的名字也只能包含小写字母、数字和 - ,端口名字还必须以字母数字开头和结尾。

比如, 123-abcweb 是有效的,而 123_abc-web 不是。

6. 选择自己的 IP 地址

你可以在 Service 的创建请求中指定你自己的集群 IP 地址。只需要设置 .spec.clusterIP 字段。 比如,你已经有一个想要重复使用的 DNS 条目,或者为特定的 IP 地址配置切难以重新配置的旧版系统。

你选择的 IP 地址必须是一个有效的 IPv4 和 IPv6 地址,且在 API server 配置的 service-cluster-ip-range CIDR 范围内。 如果你尝试使用无效的 clusterIP 地址值创建 Service,API server 会返回一个 422 的 HTTP 状态码来表示存在问题。

7. 服务发现

Kubernetes 支持两种主要的模式来找到一个 Service - 环境变量和 DNS。

7.1. 环境变量

当节点上运行一个 Pod,kubelet 回味每个激活的 Service 添加一组环境变量。它既支持 Docker lins compatible 变量又支持简单的 {SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT 变量,Service 名称为大写字母,并且破折号转换为下划线。

比如,Service redis-master 暴露了 TCP 端口 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 需要访问一个 Service,并且你使用环境变量的方法来将端口和集群 IP 发布到客户端 Pod, 你必须在客户端 Pods 产生之前创建 Service。否则这些客户端 Pods 不会填充其环境变量。

如果你仅使用 DNS 查找服务的集群 IP,那么不用担心顺序问题。

7.2. DNS

你应该添加插件来为你的集群创建一个 DNS 服务(并且总应该这么做)。

一个支持集群的 DNS 服务,比如 CoreDNS,监听 Kubernetes API 的 Service 创建,然后创建为每个记录创建一组 DNS 记录。 如果在整个集群中都启用了 DNS,那么所有的 Pods 都应该能够通过其 DNS 名称解析其 Services。

比如说在 Kubernetes 空间 my-ns 中包含一个 Service 叫做 my-service ,控制平面和 DNS 服务共同为 my-service.my-ns 创建一个 DNS 记录。在 my-ns 空间中可以简单的通过 my-service 找到对应的服务,使用 my-service.my-ns 也可以。。

在其它空间的服务必须使用 my-service.my-ns 才可以。这些名字会被解析为 Service 的集群 IP。

Kubernetes 还支持命名端口的 DNS SRV(Service) 记录。如果 my-service.my-ns Serivce 包含一个命名端口 http 协议为 TCP , 你可以为 _http._tcp.my-service.my-ns 做一个 DNS SRV 来发现 http 的端口号,以及 IP 地址。

Kubernetes DNS 服务器是唯一访问 ExternalName Services 的方法。你可以在 DNS Pods 和 Services 中找到更多的关于 ExternalName 的解释。

8. Headless Services

有时你不需要负载均衡和单独的 Service IP。这种情况下,你可以创建一个名为 "headless" 的 Services,通过显示的将集群 IP .spec.clusterIP 设置为 "None"

你可以使用 Headless Service 与其他的服务发现机制接口,而不需要跟 Kubernetes 的实现绑定到一起。

对于 headless Services ,集群 IP 不会被分配,kube-proxy 也不会处理这些 Services,也不会给他们做负载均衡或者代理。 如何自动配置 DNS 取决于是否定义了选择器:

8.1. 有选择器(with selectors)

如果定义了选择器,endpoints 控制器在 API 中创建 Endpoints 记录,然后修改 DNS 配置返回记录(指向 Service 后面的 Pods )。

8.2. 无选择器(without selectors)

如果没有定义选择器的话,endpoints 控制器不会创建 Endpoints 记录。然而,DNS 系统会查找并配置以下任意一项:

  • ExternalName 类型 Services 的 CNAME 记录;
  • 其他的类型,任何的 Endpoints 与 Services 共享名字的记录;

9. 发布 Services (ServiceTypes)

对于一些应用,你可能希望把 Service 暴露成一个集群外部的 IP 地址。

Kubernetes 的 ServiceTypes 允许你指定你所需要的 ServiceType 。默认是 ClusterIP

Type 的值以及行为如下:

  • ClusterIP :集群内部 IP 暴露 Service。只能在集群内部进行访问。这也是默认的 SerivceType
  • NodePort:在每一个节点的 IP 上暴露一个静态端口,也就是 NodePort 。自动创建一个 ClusterIP Service,路由到 NodePort Service, 在外部集群,你可以通过请求 <NodeIP>:<NodePort> 来访问 NodePort 服务。
  • LoadBalancer:使用云厂商提供的负载均衡器暴露服务。自动创建负载均衡路由到 NodePortClusterIP Services。
  • ExternalName: 通过返回带有 CNAME 记录的值,把服务映射到 externalName 字段的内容(比如 foo.bar.example.com )。没有设置任何代理。

    注意 需要 kube-dns 1.7 或者 CoreDNS 0.0.8 或者更高的版本才能使用 ExternalName 类型。

你也可以使用 Ingress 来暴露服务。Ingress 不是一个 Service 类型,但是扮演者集群访问的入口。它可以将你的路由规则整合成一个资源, 因为它可以在同一个 IP 地址下公开多个服务。

9.1. NodePort 类型

当你的 type 字段设置为 NodePort ,Kubernetes 控制平面会从 --service-node-port-range 中分配一个端口 (默认为 30000-32767)。每一个节点都会代理这个端口到你的 Service 中。你的 Service 在它的 .spec.ports[*].nodePort 字段中 报告分配端口的值。

如果你想要指定特定的 IP(s) 代理到端口,你可以设置 --nodeport-addresses 在 kube-proxy 中指定 IP 块; 这个特性从 Kubernetes v1.10 开始支持。该标识用逗号分割的 IP 块(比如:10.0.0.0/8, 192.0.2.0/25)指定 IP 地址范围 以 kube-proxy 应该视该节点为本地节点。

比如,你启动 kube-proxy 设置 -nodeport-addresses=127.0.0.0/8 ,kube-proxy 只会选择 NodePort Services 的回环接口。 默认设置的 --nodeport-addresses 是一个空的列表。这意味着 kube-proxy 应该考虑 NodePort 的所有可用网络接口 (这也与早起的 Kubernetes 版本兼容)。

如果你想要指定端口号,你可以在 nodePort 字段中指定值。控制平面会为你分配该端口或者告诉你 API 事务失败。也就是说 你要自己注意端口是否会冲突。你还必须要使用有效的端口号,确保端口号在配置的范围内。

使用 NodePort 会给你自由设置自己的负载均衡方案,配置 Kubernetes 不完全支持的环境,甚至是直接暴露一个或者多个节点的 IP。

注意 Service 以 <NodeIP>:spec.ports[*].nodePort.spec.clusterIP:spec.ports[*].port 可见(如果 --nodeport-addresses 在 kube-proxy 中设置了,将被过滤 NodeIPs )。

比如:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  spec:
    type: NodePort
    selector:
      app: MyApp
      ports:
        # By default and for convenience, the `targetPort` is set to the same value as the `port` field.
        - port: 80
          targetPort: 80
          # Optional field
          # By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
          nodePort: 30007

9.3. ExternalName 类型

类型为 ExternalName 的 Service 会将 Service 映射为一个 DNS 名字,而不是一个典型的类似 my-service 或者 cassandra 的选择器。 你可以使用 .spec.externalName 参数来指定这些 Services。

下面的 Service 定义,在 prod 空间中将 my-service 映射到 my.database.example.com :

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

注意: ExternalName 接受一个 IPv4 的字符串地址,包括数字组成的 DNS 地址,而不是 IP 地址。 类似与 IPv4 地址的 ExternalName 不会被 CoreDNS 或者 ingress-nginx 解析,因为 ExternalName 用于指定规范的 DNS 名称。 要对 IP 地址进行硬编码,请考虑使用 headless Services。

当查找 my-service.prod.svc.cluster.local 的主机时,集群的 DNS 服务将返回值为 my.database.example.com 的 CNAME 值。 访问 my-service 的方式与其他 Services 的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或者转发。 如果以后你决定将数据库迁移到集群中,则可以启动其 Pods,添加使用的选择器或者 endpoints,并修改它的 Services 类型。

警告: 你可以在使用 ExternalName 一些协议的过程中遇到麻烦,包括 HTTP 和 HTTPS。 如果你 ExternalName ,则集群内的客户端使用的主机名和 ExternalName 应用的名字不同。

对于使用主机名的协议,此差异可能导致错误或者意外的回包。HTTP 请求将具有原始服务器无法识别的 Host: header; TLS 服务器将无法提供与客户端连接的主机名匹配的证书。

9.4. ExternalIPs

如果有外部的 IP 路由到一个或者多个集群节点,Kubernetes Services 可以在 externalIPs 上暴露。 作为外部 IP(作为目标 IP)流量通过 ingresses 到集群,在 Service 端口上,会被路又道 Service 的 endpoints 之一。 externalIPs 不会受 Kubernetes 的管控,而是集群管理员的责任。

在 Service spec 中, externalIPsServiceTypes 一起指定。如下, my-service 可以被客户端在 80.11.12.10:80 上访问。

apiVersion: v1
kind: Service
metadata:
  name: my-service
  spec:
    selector:
      app: MyApp
      ports:
        - name: http
          protocol: TCP
          port: 80
          targetPort: 9376
          externalIPs:
            - 80.11.12.10

10. 缺点

VIPs 使用用户空间代理模式可以在中小型范围内使用,但不能扩展到具有千个服务的超大型集群。最初的设计稿 有说明。

使用用户空间代理会模糊访问 Service 的源 IP 地址。这使得某些网络过滤(防火墙)成为不可能。iptables 代理模式不会掩盖集群中的源 IP, 但仍会影响通过负载均衡器或者节点端口的客户端。

Type 字段被设计成一个嵌套功能 - 每个界别都会添加到上一个级别。并非所有的云厂商都强制需要(比如,Google 计算引擎不需要分配 一个 NodePort 来使 LoadBalancer 生效,但是 AWS 需要),但是当前的 API 要求使用它。

11. 虚拟 IP 实现

对于只想使用 Services 的人来说,之前的信息应该足够了。但是,Services 后面的很多事情也值得我们去了解。

11.1. 避免冲突(Avoiding collisions)

Kubernetes 的一个主要的设计哲学是:不是你的错误,你不应该面临可能导致失败的场景。对于 Service 资源的设计,意味着如果你选择的端口 跟可能跟别的端口相冲突,那就不要让你来选择端口。这就是个隔离失败。

为了允许你为你的 Services 选择一个端口号,我们必须要保证两个 Services 可以冲突。Kubernetes 是通过每个 Service 都分配一个自己的 IP 地址来实现的。

为了保证每个 Service 都有一个唯一的 IP,内部分配器在创建 Service 之前原子更新 etcd 中的全局分配映射。映射对象必须在注册表中, Services 才能分配到一个 IP 地址。否则会创建失败,并且提示无法分配 IP 地址的错误信息。

在控制平面中,后台控制器负责创建该映射关系,Kubernetes 还使用控制器来检查无效的分配(比如管理员的干预)和清理以不再被任何 Services 使用的 IP 地址。

11.2. Service IP 地址

不想 Pod 的 IP 地址,真实的路由到固定的目的地,Service IPs 不是由真实的单个主机来应答。而是,kube-proxy 使用 iptables( Linux 上的包逻辑处理进程)来定义一个 虚拟的 IP 地址来透明的重定向到需要的地方。当客户端来连接 VIP 的时候,他们的流量 自动传输到适当的 endpoints。Services 的环境变量和 DNS 实际上是根据 Services 的虚拟 IP 地址(和端口)来填充的。

kube-proxy 支持三种代理模式 - 用户空间(usespace),iptables 和 IPVS - 每个操作都略有不同。

11.2.1. 用户空间

作为一个范例,考虑上面的描述的图像处理程序。当后端 Servie 创建了之后,Kubernetes master 会分配一个虚拟 IP 地址,比如说是 10.0.0.1. 假设 Services 的端口是 1234,集群中所有 kube-proxy 实例都可以观察到该服务。当代理看到一个新的 Service 时,它打开一个新的随机端口, 建立一个 iptables 从虚拟 IP 地址重定向到新的端口,并开始接受连接。

当客户端连接到 Service 的虚拟 IP 地址,iptables 规则会被启动,并将数据包重定向到自己代理的端口。“Service 代理”选择一个后端, 然后开始把客户端的流量代理到后端。

也就是说,Service 自己可以选择任何他们想要的端口而不用考虑会有冲突的风险。客户端简单的连接 IP 和端口,不需要考虑实际访问的 Pods。

11.2.2. iptables

同样,当后端 Service 创建之后,Kubernetes 控制平面分配一个虚拟 IP 地址,比如是 10.0.0.1。Services 的端口是 1234, 集群中所有 kube-proxy 实例都可以观察到该服务。当代理看到一个新的 Service 时,它安装一系列的 iptables 规则,这些规则蛔虫虚拟的 IP 地址重定向到每一个 Service 的规则。每个 Service 的规则会链接到每一个 Endpoints 规则,它将流量重定向到后端(使用 NAT)。

当客户端连接到服务器的虚拟 IP 地址时,iptables 规则就会生效,选择一个后端(基于回话亲和性或者随机选择)并将数据包重定向到后端。 不像用户空间代理,包不会拷贝到用户空间,kube-proxy 不必运行虚拟 IP 地址即可正常工作,节点看到的是未更改的客户端 IP 地址的流量。

当流量通过 node-port 或者负载均衡器时,执行相同的基本流程,但在这种情况下,客户端 IP 确实会更改。

11.2.3. IPVS

在大型集群中 iptables 操作会明显变慢,比如说 10000 个 Service。IPVS 专门为负载均衡而设计,并且基于内核的哈希表。 因此你可以通过 IPVS 的 kube-proxy 实现大量服务的性能一致性。与此同事,基于 IPVS 的 kube-proxy 有更多复杂的负载均衡算法 (最小连接数,按地区,权重,一致性)。

12. API 对象

Service 是 Kubernetes REST API 的顶级资源,在 Service API 对象 找到更多的信息。

13. 支持的协议

13.1. TCP

在任何的 Services 上你可以使用 TCP 协议,这也是默认的网络协议。

13.2. UDP

在大多数 Services 上你可以使用 UDP 协议,对于 type=LoadBalancer 的 Services,UDP 的支持取决于提供此功能的云厂商。

13.3. SCTP

特性状态: kubernetes v1.20 [stable]

当使用 SCTP 流量的网络插件时,你可以在大多数 Service 上使用 SCTP ,对于 type=LoadBalancer 的 Services,SCTP 的支持取决于 提供此功能的云厂商(大部分都不支持)。

13.3.1. 警告

支持多宿主 SCTP 关联

警告:

The support of multihomed SCTP associations requires that the CNI plugin can support the assignment of multiple interfaces and IP addresses to a Pod.

NAT for multihomed SCTP associations requires special logic in the corresponding kernel modules.

13.4. Windows

注意: SCTP 不支持基于 Windows 的节点。

用户空间 kube-proxy

Warning: The kube-proxy does not support the management of SCTP associations when it is in userspace mode.

13.5. HTTP

如果你的云厂商提供了它,你就可以使用 Service 的 LoadBalancer 模式来设置外部的 HTTP/HTTPS 反向代理,转发到 Service 的 Endpoints。

注意: 你也使用 Ingress 替换 Service 来暴露 HTTP/HTTPS 协议。

13.6. PROXY 协议

如果你的云厂商提供了它,你就可以使用 Service 的 LoadBalancer 模式来配置 Kubernetes 自身之外的负载均衡,它会转发以 PROXY 协议 为前缀的连接。

负载均衡器将发送一系列的出十字街,描述传入的连接,类似于:

PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n

其次是来自客户端的数据。

14. 官方文档之外

<2021-02-22 Mon 13:58> Headless Service 应用场景

当部署 Service 时,Kubernetes 为会其分配一个 DNS 名称,集群中的其它组件可以使用此名称和 DNS 与上游 Pods 通信(Endpoints)。 (上面文档有写)。格式类似:

my-svc.my-namespace.svc.cluster-domain.example

ServiceTypes 类型为 ClusterIP 时, 值设置为 "None" 创建的 Service,称之为 Headless Service。 因为没有 Cluster IP,kube-proxy 不会处理它。

对于 Headless Service 关联的 Pod,也会被配置 A 或者 AAAA 记录,这样对 Headless Service 指定 DNS 查询时,会直接解析到所连接的 Pod 的 IP 地址。

区别在于,对于 Service,client 到达 Pod 的流程为:

  1. 通过 DNS 服务,拿到 Service 的虚拟 IP 地址;
  2. 使用时,VIP 通过 Kube-proxy(iptables/ipvs) 通过负载均衡策略,从 Endpoints 中选择一个 Pod 的 IP: ;

不管有多少个 Endpoints,DNS 查询时最终只返回 Service 的 VIP 地址。具体 client 访问时哪个 Pod,是由负载均衡器(ipvs)来决定的。

而对于 Headless Service,DNS 查询会直接返回 Endpoint(IP) 列表。

一般的场景在于:

  • 客户端有自助选择权来决定使用那个 Pod,不走集群负载均衡的策略;
  • Headless Service 对应的 Endpoints,每个 Pod 都有一个唯一的 DNS 域名。这样 Pod 之间就可以互相访问。 这种情况下通常和 statefulset 一起使用来部署有状态应用;

参考:

<2021-02-22 Mon 14:37> ExternalName 类型 Service 应用场景

将集群内部的名称映射到集群外部。只是一层重定向,与其它类型的 Service 完全不同,没有 Pod 相关联。

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

当在集群内部访问 my-xn-service 内部的 DNS 会被映射到外部的 example.com

参考:

<2021-02-22 Mon 14:44> Kubernetes 中的服务发现和负载均衡组件关系

刚开始的时候这块会比较混乱(尤其是我这种不是运维出身的)。这里放一些总结:

  • Service 是 Pods 访问的抽象,集群控制面板会给 Service 分配一个虚拟 IP,这个 IP 只能在集群内部使用,是个完全虚拟的;
  • Service 和 Pods 的关系是通过标签选择器来建立的(Kubernetes 中的资源关系大都是这么建立的);
  • kube-proxy 会将 Service VIP 和 Endpoints 的规则写入到 iptables/ipvs 规则中;当 client 访问 VIP 时,kube-proxy 通过 负载均衡策略为 client 选择一个 endpoints(IP:Port);
  • 创建 Service 时,集群的 DNS 插件(如 coredns)会为 Service 创建一个 DNS 记录。

所以,访问流程为: my-service -> DNS -> VIP -> kubeproxy -> IP:Port

First created: 2020-04-28 21:18:10
Last updated: 2021-11-15 Mon 10:18
Power by Emacs 27.2 (Org mode 9.4.6)