kubernetes Headless Services

发布时间 2023-06-21 16:12:16作者: 小吉猫

Headless Services介绍

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过显式指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。

你可以使用一个Headless Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。

Headless Service 不会获得 Cluster IP,kube-proxy 不会处理这类服务, 而且平台也不会为它们提供负载均衡或路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了标签选择器。

应用场景

StatefulSet控制器对象是Headless Service资源的一个典型应用场景。

有标签选择器的服务

对定义了标签选择器的Headless Service,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象, 并且修改 DNS 配置返回 A 或 AAAA 条记录(IPv4 或 IPv6 地址),这些记录直接指向 Service 的后端 Pod 集合。

无标签选择器的服务

对没有定义标签选择器的Headless Service,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会查找和配置以下之一:
对于 type: ExternalName 服务,查找和配置其 CNAME 记录
对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A / AAAA 条记录
  对于 IPv4 端点,DNS 系统创建 A 条记录。
  对于 IPv6 端点,DNS 系统创建 AAAA 条记录

ExternalName Service

ExternalName Service是一种特殊类型的Service资源,它不需要使用标签选择器关联任何Pod对象,也无须定义任何端口或EndpointSlice,但必须要使用spec.externalName属性定义一个CNAME记录,用于返回真正提供服务的服务名称的别名。ClusterDNS会为这种类型的Service资源自动生成<service>.<ns>.svc.<zone>. <ttl> IN CNAME <extname>.格式的DNS资源记录。
ExternalName用于通过DNS别名将外部服务发布到Kubernetes集群上,这类的DNS别名同本地服务的DNS名称具有相同的形式。因而Pod对象可像发现和访问集群内部服务一样来访问这些发布到集群之上的外部服务,这样隐藏了服务的位置信息,使得各工作负载能够以相同的方式调用本地和外部服务。等到了能够或者需要把该外部服务引入到Kubernetes集群上之时,管理员只需要修改相应ExternalName Service对象的类型为集群本地服务即可。

externalname-svc.yaml

apiVersion: v1
kind: Namespace
metadata:
    name: headless-demo

---
kind: Service
apiVersion: v1
metadata:
  name: externalname-web-svc
  namespace: headless-demo
spec:
  type: ExternalName
  externalName: www.baidu.com
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 0
  selector: {}

查看svc资源

# kubectl get svc -n headless-demo
NAME                   TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)   AGE
externalname-web-svc   ExternalName   <none>       www.baidu.com   80/TCP    6m21s

查看svc

# kubectl describe svc externalname-web-svc -n headless-demo
Name:              externalname-web-svc
Namespace:         headless-demo
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ExternalName
IP Families:       <none>
IP:                
IPs:               <none>
External Name:     www.baidu.com
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>

登录client

待Service资源externalname-web-svc创建完成后,各Pod对象即可通过短格式或FQDN格式的Service名称访问相应的服务。DNS会把该名称以CNAME格式解析为.spec.externalName字段中的名称,而后通过DNS服务将其解析为相应主机的IP地址。我们可通过此前Pod对象client-pod对该名称进行解析测试。
# kubectl run client --image=uhub.service.ucloud.cn/xk-base/admin-box -it --rm --restart=Never  -n headless-demo --command -- /bin/sh
root@client #

名称解析

未指定解析类型的,nslookup命令会对解析得到的CNAME结果自动进行更进一步的解析。例如下面命令中,请求解析externalname-web-svc.headless-demo.svc.wgs.local名称得到CNAME格式的结果www.baidu.com将被进一步解析为A记录格式的结果。
root@client # nslookup externalname-web-svc
Server:		10.100.0.2
Address:	10.100.0.2#53

externalname-web-svc.headless-demo.svc.wgs.local	canonical name = www.baidu.com.
www.baidu.com	canonical name = www.a.shifen.com.
Name:	www.a.shifen.com
Address: 110.242.68.4
Name:	www.a.shifen.com
Address: 110.242.68.3

Headless Service

除了为每个Service资源对象在创建时自动指派一个遵循<service>.<ns>.svc.<zone>格式的DNS名称,ClusterDNS还会为Headless Service中的每个端点指派一个遵循<hostname>.<service>.<ns>.svc.<zone>格式的DNS名称,因此,每个Headless Service资源对象的名称都会由ClusterDNS自动生成以下几种类型的资源记录。
根据端点IP地址的类型,在Service名称上为每个IPv4地址的端点生成A记录,为IPv6地址的端点生成AAAA记录。
  <service>.<ns>.svc.<zone>. <ttl> IN A <endpoint-ip>
  <service>.<ns>.svc.<zone>. <ttl> IN AAAA <endpoint-ip>
根据端点IP地址的类型,在端点自身的hostname名称上为每个IPv4地址的端点生成A记录,为IPv6地址的端点生成AAAA记录。
  <hostname>.<service>.<ns>.svc.<zone>. <ttl> IN A <endpoint-ip>
  <hostname>.<service>.<ns>.svc.<zone>. <ttl> IN AAAA <endpoint-ip>
为每个定义了名称的端口生成一个SRV记录,未命名的端口号则不具有该记录。
_<port>._<proto>.<service>.<ns>.svc.<zone>. <ttl> IN SRV <weight> <priority> <portnumber> <service>.<ns>.svc.<zone>.
对于每个给定的每个端点的主机名称的A记录(例如a.b.c.d)或AAAA记录(例如a1a2a3a4:b1b2b3b4:c1c2c3c4:d1d2d3d4:e1e2e3e4:f1f2f3f4:g1g2g3g4:h1h2h3h4),都要生成PTR记录,它们各自的格式如下所示。
  <d>.<c>.<b>.<a>.in-addr.arpa. <ttl> IN PTR <hostname>.<service>.<ns>.svc.<zone>.
  h4.h3.h2.h1.g4.g3.g2.g1.f4.f3.f2.f1.e4.e3.e2.e1.d4.d3.d2.d1.c4.c3.c2.c1.b4.b3.b2.b1.a4.a3.a2.a1.ip6.arpa <ttl> IN PTR <hostname>.<service>.<ns>.svc.<zone>

demoapp-headless-svc.yaml

apiVersion: v1
kind: Namespace
metadata:
    name: demo-headless
---
kind: Service
apiVersion: v1
metadata:
  name: demoapp-headless-svc
  namespace: demo-headless
spec:
  clusterIP: None
  selector:
    app: demoapp
  ports:
  - port: 80
    targetPort: 80
    name: http

depoly-demoapp-v10.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: demoappv10
    version: v1.0
  name: demoappv10
  namespace: demo-headless
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: demoapp
        version: v1.0
    spec:
      containers:
      - image: ikubernetes/demoapp:v1.0
        imagePullPolicy: IfNotPresent
        name: demoapp
        env:
        - name: PORT
          value: "80"
        resources: {}

创建资源

# kubectl apply -f demoapp-headless-svc.yaml -f depoly-demoapp-v10.yaml
namespace/demo-headless created
service/demoapp-headless-svc created
deployment.apps/demoappv10 created

查看svc资源

# kubectl get svc -n demo-headless -o wide
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
demoapp-headless-svc   ClusterIP   None         <none>        80/TCP    40s   app=demoapp

查看pod资源 

# kubectl get pod -n demo-headless -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP               NODE              NOMINATED NODE   READINESS GATES
demoappv10-597c58f6b-9dfhq   1/1     Running   0          5m20s   172.20.154.241   192.168.174.106   <none>           <none>
demoappv10-597c58f6b-lskz4   1/1     Running   0          5m20s   172.20.89.154    192.168.174.108   <none>           <none>
demoappv10-597c58f6b-rqcqx   1/1     Running   0          5m20s   172.20.44.225    192.168.174.107   <none>           <none>

查看svc信息

我们从其资源详细描述中可以看出,demoapp-headless-svc没有ClusterIP,但因标签选择器能够匹配到Pod资源,因此它拥有端点记录。
# kubectl describe svc demoapp-headless-svc -n demo-headless
Name:              demoapp-headless-svc
Namespace:         demo-headless
Labels:            <none>
Annotations:       <none>
Selector:          app=demoapp
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                None
IPs:               None
Port:              http  80/TCP
TargetPort:        80/TCP
Endpoints:         172.20.154.241:80,172.20.44.225:80,172.20.89.154:80
Session Affinity:  None
Events:            <none>

查看解析

# kubectl run client --image=uhub.service.ucloud.cn/xk-base/admin-box -it --rm --restart=Never  -n demo-headless --command -- /bin/sh
# nslookup demoapp-headless-svc
Server:		10.100.0.2
Address:	10.100.0.2#53

Name:	demoapp-headless-svc.demo-headless.svc.wgs.local
Address: 172.20.89.154
Name:	demoapp-headless-svc.demo-headless.svc.wgs.local
Address: 172.20.154.241
Name:	demoapp-headless-svc.demo-headless.svc.wgs.local
Address: 172.20.44.225
其解析结果正是Headless Service通过标签选择器关联到的所有Pod资源的IP地址。于是,客户端向此Service对象发起的请求将直接接入Pod资源中的应用之上,而不再由Service资源进行代理转发,它每次接入的Pod资源是由DNS服务器接收到查询请求时以轮询方式返回的IP地址。

方向解析

另一方面,每个IP地址的反向解析记录(PTR)对应的FQDN名称是相应端点所在主机的主机名称。对于Kubernetes上的容器来说,其所在主机的主机名是指Pod对象上的主机名称,它由Pod资源的spec.hostname字段和spec.subdomain组合定义,格式为<hostname>.subdomain>.<service>.<ns>.svc.<zone>,其中的<subdomain>可省略。若此两者都未定义,则<hostname>值取自IP地址,IP地址a.b.c.d对应的主机名为a-b-c-d,如下面命令的解析结果所示。
# nslookup -query=PTR 172.20.89.154
Server:		10.100.0.2
Address:	10.100.0.2#53

154.89.20.172.in-addr.arpa	name = 172-20-89-154.demoapp-headless-svc.demo-headless.svc.wgs.local.

参考文档

https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#headless-services