Kubernetes的概念很多,有的着实让人费解,比如说Headless服务,听名字就很拗口。那Headless服务是什么,使用场景是什么。一句话总结:Headless服务就是一组Pod组成的只供集群内访问(没有ClusterIP)的Service,一般结合StatefulSet用于部署有状态应用的场景。
1、Service与服务发现
提到Headless Service就得先说说Service和服务发现。
1.1、Service简述
Service主要用于实现对一组Pod的访问,Service 通过标签选择器来关联 Pod 资源。Service对外暴露服务的方式有nodePort和loadbalancer。Service 根据访问的端口将对应的请求转发至后端Pod的端口上。
Service对象的IP地址(ClusterIP)是虚拟IP地址,仅在 Kubernetes集群内可访问,外部无法访问。一般有以下几种方式将Service暴露给外部访问:
- 通过hostPort方式在单一节点上做端口映射
- 通过Pod的hostNetwork配置让Pod资源使用工作节点上的网络
- 使用NodePort或LoadBalancer类型的Service
- 使用Ingress 资源
本质上来讲,一个Service 对象对应于工作节点内核之中的一组路由规则,这些规则能够将到达Service对象的ClusterIP的流量转发至相应Pod对象的IP地址和端口。
每个工作节点的kube-proxy组件通过API Server持续监听各个Service及其关联的Pod对象,并将Service对象的创建或变动,实时写入到当前工作节点的路由规则上。客户端、Service及Pod对象的关系如下图所示:
1.2、Service类型
Service 一般分为3种类型:ClusterIP、NodePort、LoadBalancer。
ClusterIP
通过集群内部IP 地址暴露服务,CusterIP地址仅在集群内部可以访问,无法被集群外部的客户端访问。
NodePort
NodePort类型,将Service的端口号映射到每个Node的一个端口号上,然后分发给后端的Pod处理。这种类型的Service 既可以被集群内部客户端通过 CIusterIP 直接访问,也可以在集群外部客户端通过nodeIP:nodePort进行访问。
LoadBalancer
LoadBalancer类型建立在 NodePort基础上,将Service映射到一个负载均衡器的IP 地址上,通常在公有云环境中使用。
客户端通过负载均衡器的IP和Service的端号就可以访问到具体的服务,无须再通过 kube-proxy提供的负载均衡机制进行流量转发,可以直接将流量转发到后端 Pod上。
如果是本地搭建LoadBalancer,一般采用metallb方案,官网地址:https://metallb.universe.tf/,有兴趣的朋友自行搭建。
2、Headless Service的概念
在某些场景中,无需对外提供访问能力,只需要在内部找到自己想找到的Pod资源时,可以通过Headless Service来实现。
这种不具有ClusterIP的Service资源就是Headless Service,该 Service 的请求流量不需要 kube-proxy 处理,也不会有负载均衡和路由规则,而是由ClusterDNS的域名解析机制直接去访问固定的Pod资源。
一般Headless会搭配着StatefulSet一起使用,下面继续介绍。
3、StatefulSet结合Headless使用
3.1、StatefulSet概述
StatefulSet是编排有状态应用的控制器。所谓有状态的应用就是一组具有唯一持久数据和固定访问名称的 Pod。StatefulSet主要用来部署有状态应用,比如部署ZK、Kafka、MySQL、Redis等。
有状态的资源通常由两个组件构成:Headless Service和StatefulSet。Headless Service用于为各个Pod资源分配唯一固定的标识,然后生成DNS 解析记录。StatefulSet用于编排Pod 对象,并借助volumeClaimTemplate自动为Pod资源创建专有的存储。
数据的高可用是StatefulSet会极力保障的一个特性,不管是缩容还是扩容的场景。StatefulSet控制器不支持并行扩缩容机制,它一次只启动或者终止一个Pod 资源,避免数据错误。
StatefulSet、volumeClaimTemplate、PVC、PV的关系见下图:
3.2、StatefulSet特性
有序性
StatefulSet借助 Headless Service 为每个 Pod资源分配唯一固定的标识,一般是在Pod名称后面添加-0、-1、-2、...等等,。假设设置副本数replicas=2,启动时,先启动pod-0再启动pod-1,停止时则以相反的顺序进行,先停止pod-1再停止pod-0。
有状态
无状态应用没有固定标识,他们不受其他Pod影响,同样模板创建的任意Pod就可以替换之前的Pod。
有状态应用有固定的名称和存储,会受到同一组内的其他Pod的影响。Pod对象如果被替换,新的Pod仍然具有相同的标识和相同的存储。
StatefulSet使用存储卷模板为每个 Pod 对象创建专用的 PVC存储卷,通过volumeClaimTemplate自动创建绑定的存储PVC不变。
删除 Pod 对象并不会删除相关的 PV 资源,如果Pod 对象由于节点故障等原因被重新调度到其他节点时,之前同名Pod实例专用的 PV数据可以继续复用。
稳定服务发现
因为是有状态的,所以想找到自己想找到的Pod,可以直接通过pod名称.svc名称.命名空间.svc.cluster.local访问。
4、Yaml示例
示例部署一个Headless Service + StatefulSet,比如部署一个带有存储的nginx服务。文中使用到了volumeClaimTemplates,前提要创建一个storageClassName。后面会单独写一篇讲解PV、PVC、StorageClass、Provisioner。
apiVersion: v1
kind: Service
metadata:
name: nginx-statefulset-svc
namespace: dev
spec:
# ClusterIP | NodePort | LoadBalancer
type: ClusterIP
# headless service 这里的clusterIP使用None
clusterIP: None
selector:
app: nginx-statefulset-tpl
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
namespace: dev
labels:
app: nginx-statefulset
spec:
replicas: 2
serviceName: nginx-statefulset-svc
selector:
matchLabels:
app: nginx-statefulset-tpl
template:
metadata:
labels:
app: nginx-statefulset-tpl
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
# volumeClaimTemplates是StatefulSet独有的配置,前提要先创建一个storageClassName
volumeClaimTemplates:
- metadata:
name: www
spec:
resources:
requests:
storage: 200Mi
accessModes:
- ReadWriteOnce
storageClassName: nfs-client
5、总结
一句话总结:Headless服务就是一组Pod组成的只供集群内访问(没有ClusterIP)的Service,一般结合StatefulSet用于部署有状态应用的场景。
既然是Headless Service,那首先它是Service,一般的Service能被内部和外部访问。之所以叫Headless Service是因为只对内提供访问。既然只对内访问,那肯定就需要提供稳定的访问能力了,否则就没什么作用了。比如说拥有固定的Pod名称和存储,所以一般会结合StatefulSet一起使用,用来部署有状态的应用。