说说Headless Service
为什么我在还没有开始讲解Service之前就要拿出来headless Service说一说呢?
因为我自己在回顾知识的时候发现自己并没有想象中的那么懂 Headless Service这个机制。 今天自己再温故学习的同时 输出文档开源供大家公共学习
Headless Service在service中的定位
在Service服务中 ,有三种服务类型, 分别是 ClusterIP、NodePort、LoadBalancer
前者是供集群内访问的 ,不对外暴露。 后两者是提供给公网用户访问 ,用户可以通过 <NodeIP>:<NodePort>
访问服务 。 当然, LoadBalancer需要提供负载均衡器来实现, 负载均衡器自动将流量分配到 NodePort 上的服务 。
说到这里 ,我们都知道 ,他们三个都应该是需要提供ip的。 不管是 对集群内还是集群外。但是, 对于Headless service来说 ,虽然它使用的是ClusterIP, 但它不对外提供ip(他的ClusterIP: None
) 没想到吧。 无头服务就是这么来的。
怎么使用?
既然他是无头服务, 那我怎么使用呢? 没有ip就无法访问了吗? 当然不是, kubernetes中的statefulset就为我们提供了一种使用说明书。
既然无头Service不会获取集群ip, kube-proxy也就不会处理这类 Service。 而且kubernentes集群也不会提供给他们负载均衡或路由支持。
无头 Service 不使用虚拟 IP 地址和代理 配置路由和数据包转发;相反,无头 Service 通过内部 DNS 记录报告各个 Pod 的端点 IP 地址,这些 DNS 记录是由集群的 DNS 服务所提供的。 这些 DNS 记录是由集群内部 DNS 服务所提供的 要定义无头 Service,你需要将 .spec.type
设置为 ClusterIP(这也是 type
的默认值),**并进一步将 **.spec.clusterIP**
设置为 ****None**
。
DNS 如何自动配置取决于 Service 是否定义了选择器。
其实换句话说,Headless Service 不会为其后端 Pods 分配一个集群内的虚拟 IP。相反,它会直接返回后端 Pods 的 IP 地址列表。
带选择算符的服务
对定义了选择算符的无头 Service,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象,并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址), 这些记录直接指向 Service 的后端 Pod 集合。
也就是以 Service 的 VIP(Virtual IP,即:虚拟 IP)方式。比如:当我访问 10.0.23.1 这个 Service 的 IP 地址时,10.0.23.1 其实就是一个 VIP,它会把请求转发到该 Service 所代理的某一个 Pod 上。(就是上面说到的)
无选择算符的服务
对没有定义选择算符的无头 Service,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会执行以下操作之一:
- 对于 type: ExternalName Service,查找和配置其 CNAME 记录;
- 对所有其他类型的 Service,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A / AAAA 记录:
- 对于 IPv4 端点,DNS 系统创建 A 记录。
- 对于 IPv6 端点,DNS 系统创建 AAAA 记录。
当你定义无选择算符的无头 Service 时,port
必须与 targetPort
匹配。
详细点说就是, 比如:只要我访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。
而在第二种 Service DNS 的方式下,具体还可以分为两种处理方法:
- 第一种处理方法,是 Normal Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致了。
- 第二种处理方法,正是 Headless Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。可以看到,这里的区别在于,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。
实例说明
1 | apiVersion: v1 |
可以看到, 所谓的 Headless Service,其实仍是一个标准 Service 的 YAML 文件。只不过,它的 clusterIP 字段的值是:None,即:这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。(上面也说到了, 对应)
而它所代理的 Pod,依然是采用 Label Selector 机制选择出来的,即:所有携带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来。
然后关键。当你按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:
1 | <pod-name>.<svc-name>.<namespace>.svc.cluster.local |
通过 工具包dns-test, 输入nslookup 查询一个特定的 StatefulSet Pod 的 DNS 记录。
这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。
有了这个“可解析身份”,只要你知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址
那么,StatefulSet 又是如何使用这个 DNS 记录来维持 Pod 的拓扑状态的呢?
下面我们来通过一段sts的yaml来看下
1 | apiVersion: apps/v1 |
看这里serviceName: "nginx"
这个字段。这个 YAML 文件,和我们在前面文章中用到的 nginx-deployment 的唯一区别,就是多了一个 serviceName=nginx 字段。
这个字段的作用,就是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。
所以,当你通过 kubectl create 创建了上面这个 Service 和 StatefulSet 之后,就会看到如下两个对象:
通过上面这个 Pod 的创建过程,我们不难看到,StatefulSet 给它所管理的所有 Pod 的名字,进行了编号,编号规则是:**<statefulset name>-<ordinal index>**
。
而且这些编号都是从 0 开始累加,与 StatefulSet 的每个 Pod 实例一一对应,绝不重复。
更重要的是,这些 Pod 的创建,也是严格按照编号顺序进行的。比如,在 web-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,web-1 会一直处于 Pending 状态。
这个可以通过另一个node来监听, 然后master来做创建动作发现
当这两个 Pod 都进入了 Running 状态之后,你就可以查看到它们各自唯一的“网络身份”了。
我们使用 kubectl exec 命令进入到容器中查看它们的 hostname:
1 | $ kubectl exec web-0 -- sh -c 'hostname' |
用 nslookup 命令,解析一下 Pod 对应的 Headless Service 就是上面我提供的那个表中的数据。
通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。
比如,如果 web-0 是一个需要先启动的主节点,web-1 是一个后启动的从节点,那么只要这个 StatefulSet 不被删除,你访问 web-0.nginx 时始终都会落在主节点上,访问 web-1.nginx 时,则始终都会落在从节点上,这个关系绝对不会发生任何变化。
所以,如果我们再用 nslookup 命令,查看一下这个新 Pod 对应的 Headless Service 的话还是可以看到,在这个 StatefulSet 中,这两个新 Pod 的“网络标识”(比如:web-0.nginx 和 web-1.nginx),再次解析到了正确的 IP 地址(比如:web-0 Pod 的 IP 地址 10.244.1.8)。
通过这种方法,Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。
sts的灾备考虑
疑问
前面我们说到
比如,如果 web-0 是一个需要先启动的主节点,web-1 是一个后启动的从节点,那么只要这个 StatefulSet 不被删除,你访问 web-0.nginx 时始终都会落在主节点上,访问 web-1.nginx 时,则始终都会落在从节点上,这个关系绝对不会发生任何变化。
但是, sts管理的pod可能是有启动顺序考虑的, 例如: web-1是依赖于web-0的 ,在启动之初, web-1在web-0正式runing之前是不会启动的, 在running之后, 他才会启动。也就是说他们之间存在着强依赖关系, 但是如果此时删除了web-0, 恰好有用户访问了web-1的某个服务 , 这个服务是依赖着web-0, 但是web-0被删除了 它在sts的控制下重新创建也是需要时间的,也就是说这个服务在这段时间是不可用的。 这显示是一个灾备考虑不周出现的故障了。
如何解决有状态服务的强依赖关系
在高可用性和容灾设计中,单个 Pod 或节点的不可用性不应导致整个服务的中断。所以说这个问题的重视程度应该再涨一个等级。 (经典的单点故障问题)
需要采用多种策略来提高 StatefulSet 的高可用性和容灾能力
- 多主架构
如果应用程序支持,使用多主架构而不是主从架构。例如,某些数据库(如 CockroachDB、Cassandra)支持多主节点架构,允许任意节点处理请求,从而避免单点故障。
- Pod 反亲和性
通过配置 Pod 反亲和性策略,确保 StatefulSet 的 Pod 分布在不同的节点上,从而提高容灾能力。如果一个节点故障,其他节点上的 Pod 仍然可以提供服务
- 服务的多副本
为关键服务部署多个副本,并使用负载均衡器(如 Kubernetes 的 Service 或 Ingress)来分发流量。如果一个副本不可用,流量会自动转发到其他可用副本。
- 多区域和多集群部署
在多个地理区域或多个 Kubernetes 集群中部署应用,以确保即使一个区域或集群发生故障,其他区域或集群仍能继续提供服务。这通常涉及跨区域的负载均衡和数据同步策略。
- 服务健康检查: 你可以配置Kubernetes的liveness和readiness探针,以检查Pod的健康状况。如果一个Pod出现问题,Kubernetes可以自动重启它。