Service的功能及作用
一、Service概述
为何需要 Service
Kubernetes 中 Pod 是随时可以消亡的(节点故障、容器内应用程序错误等原因)。如果使用 Deployment运行应用程序,Deployment 将会在 Pod 消亡后再创建一个新的 Pod 以维持所需要的副本数。每一个 Pod 有自己的 IP 地址,然而,对于 Deployment 而言,对应 Pod 集合是动态变化的。
这个现象导致了如下问题:
- 如果某些 Pod(假设是 ‘backends’)为另外一些 Pod(假设是 ‘frontends’)提供接口,在 ‘backends’ 中的 Pod 集合不断变化(IP 地址也跟着变化)的情况下,’frontends’ 中的 Pod 如何才能知道应该将请求发送到哪个 IP 地址?
Service 存在的意义,就是为了解决这个问题
Kubernetes Service
Kubernetes 中 Service 是一个 API 对象,通过 kubectl + YAML,定义一个 Service,可以将符合 Service 指定条件的 Pod 作为可通过网络访问的服务提供给服务调用者。
Service 是 Kubernetes 中的一种服务发现机制:
- Pod 有自己的 IP 地址
- Service 被赋予一个唯一的 dns name
- Service 通过 label selector 选定一组 Pod
- Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中
例如,假设有一个无状态的图像处理后端程序运行了 3 个 Pod 副本。这些副本是相互可替代的(前端程序调用其中任何一个都可以)。在后端程序的副本集中的 Pod 经常变化(销毁、重建、扩容、缩容等)的情况下,前端程序不应该关注这些变化。
Kubernetes 通过引入 Service 的概念,将前端与后端解耦
二、Service详细描述
创建 Service
Kubernetes Servies 是一个 RESTFul 接口对象,可通过 yaml 文件创建。
例如,假设您有一组 Pod:
- 每个 Pod 都监听 9376 TCP 端口
- 每个 Pod 都有标签 app=MyApp
1 | apiVersion: v1 |
上述 YAML 文件可用来创建一个 Service:
- 名字为
my-service - 目标端口为 TCP 9376
- 选取所有包含标签 app=MyApp 的 Pod
关于 Service:
Kubernetes 将为该 Service 分配一个 IP 地址(ClusterIP 或 集群内 IP),供 Service Proxy 使用。
Kubernetes 将不断扫描符合该 selector 的 Pod,并将最新的结果更新到与 Service 同名
my-service的 Endpoint 对象中。Service 从自己的 IP 地址和
port端口接收请求,并将请求映射到符合条件的 Pod 的targetPort。为了方便,默认targetPort的取值 与port字段相同Pod 的定义中,Port 可能被赋予了一个名字,可以在 Service 的
targetPort字段引用这些名字,而不是直接写端口号。这种做法可以使得将来修改后端程序监听的端口号,而无需影响到前端程序。Service 的默认传输协议是 TCP,也可以使用其他支持的传输协议。
Kubernetes Service 中,可以定义多个端口,不同的端口可以使用相同或不同的传输协议。
创建 Service(无 label selector)
Service 通常用于提供对 Kubernetes Pod 的访问,但是也可以将其用于任何其他形式的后端。例如:
- 在生产环境中使用一个 Kubernetes 外部的数据库集群,在测试环境中使用 Kubernetes 内部的 数据库
- 将 Service 指向另一个名称空间中的 Service,或者另一个 Kubernetes 集群中的 Service
- 将程序迁移到 Kubernetes,但是根据迁移路径,只将一部分后端程序运行在 Kubernetes 中
在上述这些情况下,可以定义一个没有 Pod Selector 的 Service。例如:
1 | apiVersion: v1 |
因为该 Service 没有 selector,相应的 Endpoint 对象就无法自动创建。可以手动创建一个 Endpoint 对象,以便将该 Service 映射到后端服务真实的 IP 地址和端口:
1 | apiVersion: v1 |
注意:
- 对于 Service 的访问者来说,Service 是否有 label selector 都是一样的。在上述例子中,Service 将请求路由到 Endpoint 192.0.2.42:9376 (TCP)。
- Endpoint 中的 IP 地址不可以是集群中其他 Service 的 ClusterIP。
Kubernetes 支持三种 proxy mode(代理模式)
1.User space 代理模式
在 user space proxy mode 下:
- kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
- kube-proxy 在其所在的节点(每个节点都有 kube-proxy)上为每一个 Service 打开一个随机端口
- kube-proxy 安装 iptables 规则,将发送到该 Service 的 ClusterIP(虚拟 IP)/ Port 的请求重定向到该随机端口
- 任何发送到该随机端口的请求将被代理转发到该 Service 的后端 Pod 上(kube-proxy 从 Endpoint 信息中获得可用 Pod)
- kube-proxy 在决定将请求转发到后端哪一个 Pod 时,默认使用 round-robin(轮询)算法,并会考虑到 Service 中的
SessionAffinity的设定
如下图所示:
2.Iptables 代理模式 默认模式
在 iptables proxy mode 下:
kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
kube-proxy 在其所在的节点(每个节点都有 kube-proxy)上为每一个 Service 安装 iptable 规则
iptables 将发送到 Service 的 ClusterIP / Port 的请求重定向到 Service 的后端 Pod 上
- 对于 Service 中的每一个 Endpoint,kube-proxy 安装一个 iptable 规则
- 默认情况下,kube-proxy 随机选择一个 Service 的后端 Pod
iptables proxy mode 的优点:
- 更低的系统开销:在 linux netfilter 处理请求,无需在 userspace 和 kernel space 之间切换
- 更稳定
与 user space mode 的差异:
- 使用 iptables mode 时,如果第一个 Pod 没有响应,则创建连接失败
- 使用 user space mode 时,如果第一个 Pod 没有响应,kube-proxy 会自动尝试连接另外一个后端 Pod
可以配置 Pod 就绪检查(readiness probe)确保后端 Pod 正常工作,此时,在 iptables 模式下 kube-proxy 将只使用健康的后端 Pod,从而避免了 kube-proxy 将请求转发到已经存在问题的 Pod 上。
3.IPVS 代理模式
在 IPVS proxy mode 下:
kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
kube-proxy 根据监听到的事件,调用 netlink 接口,创建 IPVS 规则;并且将 Service/Endpoint 的变化同步到 IPVS 规则中
当访问一个 Service 时,IPVS 将请求重定向到后端 Pod
IPVS 模式的优点
IPVS proxy mode 基于 netfilter 的 hook 功能,与 iptables 代理模式相似,但是 IPVS 代理模式使用 hash table 作为底层的数据结构,并在 kernel space 运作。这就意味着
- IPVS 代理模式可以比 iptables 代理模式有更低的网络延迟,在同步代理规则时,也有更高的效率
- 与 user space 代理模式 / iptables 代理模式相比,IPVS 模式可以支持更大的网络流量
注意:
- 如果要使用 IPVS 模式,必须在启动 kube-proxy 前为节点的 linux 启用 IPVS
- kube-proxy 以 IPVS 模式启动时,如果发现节点的 linux 未启用 IPVS,则退回到 iptables 模式
代理模式总结
在所有的代理模式中,发送到 Service 的 IP:Port 的请求将被转发到一个合适的后端 Pod,而无需调用者知道任何关于 Kubernetes/Service/Pods 的细节。
Service 中额外字段的作用:
service.spec.sessionAffinityservice.spec.sessionAffinityConfig.clientIP.timeoutSeconds1
2
3
4
5
- 默认值为 "None"
- 如果设定为 "ClientIP",则同一个客户端的连接将始终被转发到同一个 Pod
-1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 默认值为 10800 (3 小时)
- 设定会话保持的持续时间
## 多端口的Service
Kubernetes 中,可以在一个 Service 对象中定义多个端口,但必须为每个端口定义一个名字。如下所示:
```yaml
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
使用自定义的 IP 地址
创建 Service 时,如果指定 .spec.clusterIP 字段,可以使用自定义的 Cluster IP 地址。该 IP 地址必须是 APIServer 中配置字段 service-cluster-ip-range CIDR 范围内的合法 IPv4 或 IPv6 地址,否则不能创建成功。
服务发现
Kubernetes 支持两种主要的服务发现模式:
- 环境变量
- DNS
环境变量
kubelet 查找有效的 Service,并针对每一个 Service,向其所在节点上的 Pod 注入一组环境变量。支持的环境变量有:
- {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT
- Service name 被转换为大写
- 小数点
.被转换为下划线_
如果要在 Pod 中使用基于环境变量的服务发现方式,必须先创建 Service,再创建调用 Service 的 Pod。否则,Pod 中不会有该 Service 对应的环境变量。
DNS
CoreDNS 监听 Kubernetes API 上创建和删除 Service 的事件,并为每一个 Service 创建一条 DNS 记录。集群中所有的 Pod 都可以使用 DNS Name 解析到 Service 的 IP 地址。
虚拟 IP 的实现
避免冲突
Kubernetes 的一个设计哲学是:尽量避免非人为错误产生的可能性。就设计 Service 而言,Kubernetes 应该将选择的端口号与其他人选择的端口号隔离开。为此,Kubernetes 为每一个 Service 分配一个该 Service 专属的 IP 地址。
为了确保每个 Service 都有一个唯一的 IP 地址,kubernetes 在创建 Service 之前,先更新 etcd 中的一个全局分配表,如果更新失败(例如 IP 地址已被其他 Service 占用),则 Service 不能成功创建。
Kubernetes 使用一个后台控制器检查该全局分配表中的 IP 地址的分配是否仍然有效,并且自动清理不再被 Service 使用的 IP 地址。
Service 的 IP 地址
Pod 的 IP 地址路由到一个确定的目标,然而 Service 的 IP 地址则不同,通常背后并不对应一个唯一的目标。 kube-proxy 使用 iptables (Linux 中的报文处理逻辑)来定义虚拟 IP 地址。当客户端连接到该虚拟 IP 地址时,它们的网络请求将自动发送到一个合适的 Endpoint。Service 对应的环境变量和 DNS 实际上反应的是 Service 的虚拟 IP 地址(和端口)。
Userspace
以上面提到的图像处理程序为例。当后端 Service 被创建时,Kubernetes master 为其分配一个虚拟 IP 地址(假设是 10.0.0.1),并假设 Service 的端口是 1234。集群中所有的 kube-proxy 都实时监听者 Service 的创建和删除。Service 创建后,kube-proxy 将打开一个新的随机端口,并设定 iptables 的转发规则(以便将该 Service 虚拟 IP 的网络请求全都转发到这个新的随机端口上),并且 kube-proxy 将开始接受该端口上的连接。
当一个客户端连接到该 Service 的虚拟 IP 地址时,iptables 的规则被触发,并且将网络报文重定向到 kube-proxy 自己的随机端口上。kube-proxy 接收到请求后,选择一个后端 Pod,再将请求以代理的形式转发到该后端 Pod。
这意味着 Service 可以选择任意端口号,而无需担心端口冲突。客户端可以直接连接到一个 IP:port,无需关心最终在使用哪个 Pod 提供服务。
iptables
仍然以上面提到的图像处理程序为例。当后端 Service 被创建时,Kubernetes master 为其分配一个虚拟 IP 地址(假设是 10.0.0.1),并假设 Service 的端口是 1234。集群中所有的 kube-proxy 都实时监听者 Service 的创建和删除。Service 创建后,kube-proxy 设定了一系列的 iptables 规则(这些规则可将虚拟 IP 地址映射到 per-Service 的规则)。per-Service 规则进一步链接到 per-Endpoint 规则,并最终将网络请求重定向(使用 destination-NAT)到后端 Pod。
当一个客户端连接到该 Service 的虚拟 IP 地址时,iptables 的规则被触发。一个后端 Pod 将被选中(基于 session affinity 或者随机选择),且网络报文被重定向到该后端 Pod。与 userspace proxy 不同,网络报文不再被复制到 userspace,kube-proxy 也无需处理这些报文,且报文被直接转发到后端 Pod。
在使用 node-port 或 load-balancer 类型的 Service 时,以上的代理处理过程是相同的。
IPVS
在一个大型集群中(例如,存在 10000 个 Service)iptables 的操作将显著变慢。IPVS 的设计是基于 in-kernel hash table 执行负载均衡。因此,使用 IPVS 的 kube-proxy 在 Service 数量较多的情况下仍然能够保持好的性能。同时,基于 IPVS 的 kube-proxy 可以使用更复杂的负载均衡算法(最少连接数、基于地址的、基于权重的等)
支持的传输协议
- TCP
- UDP
- HTTP
- Proxy Protocol
- SCTP
三、发布Service
Service 类型
Kubernetes 中可以通过不同方式发布 Service,通过 ServiceType 字段指定,该字段的默认值是 ClusterIP,可选值有:
- ClusterIP: 默认值。通过集群内部的一个 IP 地址暴露 Service,只在集群内部可以访问
- NodePort: 通过每一个节点上的的静态端口(NodePort)暴露 Service,同时自动创建 ClusterIP 类型的访问方式
- 在集群内部通过 $(ClusterIP): $(Port) 访问
- 在集群外部通过 $(NodeIP): $(NodePort) 访问
- LoadBalancer: 通过云服务供应商(AWS、Azure、GCE 等)的负载均衡器在集群外部暴露 Service,同时自动创建 NodePort 和 ClusterIP 类型的访问方式
- 在集群内部通过 $(ClusterIP): $(Port) 访问
- 在集群外部通过 $(NodeIP): $(NodePort) 访问
- 在集群外部通过 $(LoadBalancerIP): $(Port) 访问
- ExternalName: 将 Service 映射到
externalName指定的地址(例如:foo.bar.example.com),返回值是一个 CNAME 记录。不使用任何代理机制。
ClusterIP
ClusterIP 是 ServiceType 的默认值
NodePort
对于 NodePort 类型的 Service,Kubernetes 为其分配一个节点端口(对于同一 Service,在每个节点上的节点端口都相同),该端口的范围在初始化 apiserver 时可通过参数 --service-node-port-range 指定(默认是:30000-32767)。节点将该端口上的网络请求转发到对应的 Service 上。可通过 Service 的 .spec.ports[*].nodePort 字段查看该 Service 分配到的节点端口号。
在启动 kube-proxy 时使用参数 --nodeport-address 可指定阶段端口可以绑定的 IP 地址段。该参数接收以逗号分隔的 CIDR 作为参数值(例如:10.0.0.0/8,192.0.2.0/25),kube-proxy 将查找本机符合该 CIDR 的 IP 地址,并将节点端口绑定到符合的 IP 地址上。
NodePort 类型的 Service 可通过如下方式访问:
- 在集群内部通过 $(ClusterIP): $(Port) 访问
- 在集群外部通过 $(NodeIP): $(NodePort) 访问
LoadBalancer
在支持外部负载均衡器的云环境中(例如 GCE、AWS、Azure 等),将 .spec.type 字段设置为 LoadBalancer,Kubernetes 将为该Service 自动创建一个负载均衡器,负载均衡器的创建操作异步完成。
ExternalName
ExternalName 类型的 Service 映射到一个外部的 DNS name,而不是一个 pod label selector。可通过 spec.externalName 字段指定外部 DNS name。
External IP
如果有外部 IP 路由到 Kubernetes 集群的一个或多个节点,Kubernetes Service 可以通过这些 externalIPs 进行访问。externalIP 需要由集群管理员在 Kubernetes 之外配置。