Kubernetes Servies 是一个 RESTFul 接口对象,可通过 yaml 文件创建。
例如,假设您有一组 Pod:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述 YAML 文件可用来创建一个 Service:
my-service
关于 Service,您还需要了解:
my-service
的 Endpoint 对象中。Service 从自己的 IP 地址和
port
端口接收请求,并将请求映射到符合条件的 Pod 的targetPort
。为了方便,默认targetPort
的取值 与port
字段相同
targetPort
字段引用这些名字,而不是直接写端口号。这种做法可以使得您在将来修改后端程序监听的端口号,而无需影响到前端程序。Service 通常用于提供对 Kubernetes Pod 的访问,但是您也可以将其用于任何其他形式的后端。例如:
在上述这些情况下,您可以定义一个没有 Pod Selector 的 Service。例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
因为该 Service 没有 selector,相应的 Endpoint 对象就无法自动创建。您可以手动创建一个 Endpoint 对象,以便将该 Service 映射到后端服务真实的 IP 地址和端口:
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
- Endpoint 中的 IP 地址不可以是 loopback(127.0.0.0/8 IPv4 或 ::1/128 IPv6),或 link-local(169.254.0.0/16 IPv4、224.0.0.0/24 IPv4 或 fe80::/64 IPv6)
- Endpoint 中的 IP 地址不可以是集群中其他 Service 的 ClusterIP
对于 Service 的访问者来说,Service 是否有 label selector 都是一样的。在上述例子中,Service 将请求路由到 Endpoint 192.0.2.42:9376 (TCP)。
ExternalName Service 是一类特殊的没有 label selector 的 Service,该类 Service 使用 DNS 名字。
Kubernetes 集群中的每个节点都运行了一个 kube-proxy
,负责为 Service(ExternalName 类型的除外)提供虚拟 IP 访问。
许多用户都对 Kubernetes 为何使用服务代理将接收到的请求转发给后端服务,而不是使用其他途径,例如:是否可以为 Service 配置一个 DNS 记录,将其解析到多个 A value(如果是 IPv6 则是 AAAA value),并依赖 round-robin(循环)解析?
Kubernetes 使用在 Service 中使用 proxy 的原因大致有如下几个:
Kubernetes 支持三种 proxy mode(代理模式),他们的版本兼容性如下:
代理模式 | Kubernetes 版本 | 是否默认 |
---|---|---|
User space proxy mode | v1.0 + | |
Iptables proxy mode | v1.1 + | 默认 |
Ipvs proxy mode | v1.8 + |
在 user space proxy mode 下:
SessionAffinity
的设定在 iptables proxy mode 下:
iptables proxy mode 的优点:
与 user space mode 的差异:
您可以配置 Pod 就绪检查(readiness probe)确保后端 Pod 正常工作,此时,在 iptables 模式下 kube-proxy 将只使用健康的后端 Pod,从而避免了 kube-proxy 将请求转发到已经存在问题的 Pod 上。
在 IPVS proxy mode 下:
IPVS 模式的优点
IPVS proxy mode 基于 netfilter 的 hook 功能,与 iptables 代理模式相似,但是 IPVS 代理模式使用 hash table 作为底层的数据结构,并在 kernel space 运作。这就意味着
IPVS 提供更多的负载均衡选项:
- 如果要使用 IPVS 模式,您必须在启动 kube-proxy 前为节点的 linux 启用 IPVS
- kube-proxy 以 IPVS 模式启动时,如果发现节点的 linux 未启用 IPVS,则退回到 iptables 模式
在所有的代理模式中,发送到 Service 的 IP:Port 的请求将被转发到一个合适的后端 Pod,而无需调用者知道任何关于 Kubernetes/Service/Pods 的细节。
Service 中额外字段的作用:
service.spec.sessionAffinity
service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
Kubernetes 中,您可以在一个 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 的命名规则,且,端口的名字只能包含小写字母、数字、
-
,并且必须以数字或字母作为开头及结尾。例如:
合法的 Port 名称:
123-abc
、web
非法的 Port 名称:
123_abc
、-web
创建 Service 时,如果指定 .spec.clusterIP
字段,可以使用自定义的 Cluster IP 地址。该 IP 地址必须是 APIServer 中配置字段 service-cluster-ip-range
CIDR 范围内的合法 IPv4 或 IPv6 地址,否则不能创建成功。
可能用到自定义 IP 地址的场景:
Kubernetes 支持两种主要的服务发现模式:
kubelet 查找有效的 Service,并针对每一个 Service,向其所在节点上的 Pod 注入一组环境变量。支持的环境变量有:
.
被转换为下划线 _
例如,Service redis-master
暴露 TCP 端口 6379,其 Cluster 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,再创建调用 Service 的 Pod。否则,Pod 中不会有该 Service 对应的环境变量。
如果使用基于 DNS 的服务发现,您无需担心这个创建顺序的问题
oreDNS 监听 Kubernetes API 上创建和删除 Service 的事件,并为每一个 Service 创建一条 DNS 记录。集群中所有的 Pod 都可以使用 DNS Name 解析到 Service 的 IP 地址。
例如,名称空间 my-ns
中的 Service my-service
,将对应一条 DNS 记录 my-service.my-ns
。 名称空间 my-ns
中的Pod可以直接 nslookup my-service
(my-service.my-ns
也可以)。其他名称空间的 Pod 必须使用 my-service.my-ns
。my-service
和 my-service.my-ns
都将被解析到 Service 的 Cluster IP。
Kubernetes 同样支持 DNS SRV(Service)记录,用于查找一个命名的端口。假设 my-service.my-ns
Service 有一个 TCP 端口名为 http
,则,您可以 nslookup _http._tcp.my-service.my-ns
以发现该Service 的 IP 地址及端口 http
对于 ExternalName
类型的 Service,只能通过 DNS 的方式进行服务发现
“Headless” Service 不提供负载均衡的特性,也没有自己的 IP 地址。创建 “headless” Service 时,只需要指定 .spec.clusterIP
为 “None”。
“Headless” Service 可以用于对接其他形式的服务发现机制,而无需与 Kubernetes 的实现绑定。
对于 “Headless” Service 而言:
DNS 的配置方式取决于该 Service 是否配置了 selector:
配置了 Selector
Endpoints Controller 创建 Endpoints
记录,并修改 DNS 配置,使其直接返回指向 selector 选取的 Pod 的 IP 地址
没有配置 Selector
Endpoints Controller 不创建 Endpoints
记录。DNS服务返回如下结果中的一种:
Endpoints
的 A 记录Kubernetes 的一个设计哲学是:尽量避免非人为错误产生的可能性。就设计 Service 而言,Kubernetes 应该将您选择的端口号与其他人选择的端口号隔离开。为此,Kubernetes 为每一个 Service 分配一个该 Service 专属的 IP 地址。
为了确保每个 Service 都有一个唯一的 IP 地址,kubernetes 在创建 Service 之前,先更新 etcd 中的一个全局分配表,如果更新失败(例如 IP 地址已被其他 Service 占用),则 Service 不能成功创建。
Kubernetes 使用一个后台控制器检查该全局分配表中的 IP 地址的分配是否仍然有效,并且自动清理不再被 Service 使用的 IP 地址。
Pod 的 IP 地址路由到一个确定的目标,然而 Service 的 IP 地址则不同,通常背后并不对应一个唯一的目标。 kube-proxy 使用 iptables (Linux 中的报文处理逻辑)来定义虚拟 IP 地址。当客户端连接到该虚拟 IP 地址时,它们的网络请求将自动发送到一个合适的 Endpoint。Service 对应的环境变量和 DNS 实际上反应的是 Service 的虚拟 IP 地址(和端口)。
以上面提到的图像处理程序为例。当后端 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 提供服务。
仍然以上面提到的图像处理程序为例。当后端 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 时,以上的代理处理过程是相同的。
在一个大型集群中(例如,存在 10000 个 Service)iptables 的操作将显著变慢。IPVS 的设计是基于 in-kernel hash table 执行负载均衡。因此,使用 IPVS 的 kube-proxy 在 Service 数量较多的情况下仍然能够保持好的性能。同时,基于 IPVS 的 kube-proxy 可以使用更复杂的负载均衡算法(最少连接数、基于地址的、基于权重的等)
默认值。任何类型的 Service 都支持 TCP 协议。
大多数 Service 都支持 UDP 协议。对于 LoadBalancer 类型的 Service,是否支持 UDP 取决于云供应商是否支持该特性。
如果您的云服务商支持,您可以使用 LoadBalancer 类型的 Service 设定一个 Kubernetes 外部的 HTTP/HTTPS 反向代理,将请求转发到 Service 的 Endpoints。
使用 Ingress
如果您的云服务上支持(例如 AWS),您可以使用 LoadBalancer 类型的 Service 设定一个 Kubernetes 外部的负载均衡器,并将连接已 PROXY 协议转发到 Service 的 Endpoints。
负载均衡器将先发送描述该 incoming 连接的字节串,如下所示:
PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n
然后在发送来自于客户端的数据