Kubernetes: 通過無頭服務(wù)(Headless Service)實現(xiàn)客戶端負載均衡

寫在前面
分享一些 K8s 中 Headless Service 的筆記
博文內(nèi)容涉及:
Headless Service 的簡單介紹
Headless Service 創(chuàng)建
集群內(nèi)/外 獲取 Headless Service對應(yīng) Pod 列表的 Demo
理解不足小伙伴幫忙指正
在認識一經(jīng)出現(xiàn)時,情欲就引退。  -----昂克敵·杜伯?。骸多w布涅伽研究》第二卷第216頁 -----《作為意志和表象的世界》

Headless Service 簡單介紹
在某些場景中,如果我們希望自己控制 Pod 實例的負載均衡 策略,或者希望直接和 Pod 交互但是又不希望通過端口映射的方式,比如數(shù)據(jù)庫根據(jù)情況做一些讀寫分離,或者一些應(yīng)用在客戶端做流量控制等,不使用 Service 提供的由 Kube-proxy 代理實現(xiàn)的默認負載均衡的功能。希望明確是由那幾個 pod 提供能力,即直接通過 Pod 發(fā)布服務(wù), 而不是只有一個 集群 IP Cluster IP 或者使用 NodePort、LoadBalancer、ExternalName 來發(fā)布服務(wù)。

這個時候,K8s 提供了 Headless Service ,即不為 Service 設(shè)置 ClusterIP(入口IP地址),也叫 無頭服務(wù),這里分兩種情況

有選擇器
第一種是有對應(yīng)的服務(wù)能力提供者,即通過標簽選擇器選擇了對應(yīng)的后端能力,比如 pod,deployment,statefulset 等

在這種情況下,會通過Label Selector將被選擇的后端 Pod 列表返回給調(diào)用的客戶端, K8s 不會為這樣的 Service 分配任何 IP, DNS 會為這些Service 的 Name 添加一系列的 A(AAA)記錄(IP 地址指向),直接指向后端映射的 Pod。 當然前提是 通過 標簽選擇器選擇到了對應(yīng)的 pod。

Kube-prosy 不會處理這類型的 Service ,沒有負載均衡機制也沒有請求映射,這里 Endpoint Controller 任然會創(chuàng)建 pod 對應(yīng)的 Endpoint , 同時 Kubernetes 控制平面會在 Kubernetes API 中創(chuàng)建 EndpointSlice 對象

EndpointSlices 表示針對服務(wù)的后端網(wǎng)絡(luò)端點的子集(切片),這是在 1.21 版本才出現(xiàn)的,提供了一種簡單的方法來跟蹤 Kubernetes 集群中的網(wǎng)絡(luò)端點(network endpoints)。EndpointSlices 為 Endpoints 提供了一種可擴縮和可拓展的替代方案。

在 Kubernetes 中,EndpointSlice 包含對一組網(wǎng)絡(luò)端點的引用。 控制面會自動為設(shè)置了選擇算符的 Kubernetes Service 創(chuàng)建 EndpointSlice,EndpointSlice 將包含對與 Service 選擇算符匹配的所有 Pod 的引用。 EndpointSlice 通過唯一的協(xié)議、端口號和 Service 名稱將網(wǎng)絡(luò)端點組織在一起

Headless Service 通過暴露的 Endpoints 列表 應(yīng)用可以通過編碼實現(xiàn)客戶端的負載均衡。

沒有選擇器
第二種是沒有對應(yīng)的服務(wù)能力提供者,即沒有通過選擇運算符來獲取當前 集群的能力,這個時候,系統(tǒng)不會創(chuàng)建對應(yīng) Endpoint ,也不會創(chuàng)建對應(yīng)的 EndpointSlice.

這種情況下,DNS 系統(tǒng)會查找和配置以下之一

對于 type: ExternalName 服務(wù),查找和配置其 CNAME 記錄
對所有其他類型的服務(wù),針對 Service 的就緒端點的所有 IP 地址,查找和配置 DNS A / AAAA 條記錄
對于 IPv4 端點,DNS 系統(tǒng)創(chuàng)建 A 條記錄。
對于 IPv6 端點,DNS 系統(tǒng)創(chuàng)建 AAAA 條記錄。
Headless Service 創(chuàng)建
定義一個 Service 里面的 ClusterIP 為 None ,并且擁有 Selector 標簽選擇器,這樣的 Service 為 Headlsee Service

查看當前 k8s 中是否存在 Headlsee

┌──[root@vms81.liruilongs.github.io]-[~]
└─$kubectl get svc -A | grep None
awx                    awx-demo-postgres-13                              ClusterIP   None             <none>        5432/TCP                       52d
kube-system            liruilong-kube-prometheus-kubelet                 ClusterIP   None             <none>        10250/TCP,10255/TCP,4194/TCP   324d
一般情況下 SatefulSet 需要 Headless Service 來實現(xiàn) Pod 的網(wǎng)絡(luò)的一致性(必須創(chuàng)建此服務(wù)),為客戶端返回多個服務(wù)端地址。可以看到當前的集群中有兩個 Headless Service, 一個是有狀態(tài)應(yīng)用(SatefulSet) postgres 數(shù)據(jù)庫創(chuàng)建,一個是搭建 prometheus 集群監(jiān)控創(chuàng)建的。

┌──[root@vms81.liruilongs.github.io]-[~]
└─$kubectl get svc awx-demo-postgres-13 -o yaml
apiVersion: v1
kind: Service
metadata:
  ..................
  name: awx-demo-postgres-13
  namespace: awx
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - port: 5432
    protocol: TCP
    targetPort: 5432
  selector:
    app.kubernetes.io/component: database
    app.kubernetes.io/instance: postgres-13-awx-demo
    app.kubernetes.io/managed-by: awx-operator
    app.kubernetes.io/name: postgres-13
    app.kubernetes.io/part-of: awx-demo
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}
這里我們可以大概的看到一個 Headless 資源文件定義,對這樣的 Service 進行訪問,得到的就是一個 符合選擇器的全部的 Pod 列表,然后客戶端去自行的處理這些 Pod 列表。上面的 Service 中,客戶端訪問 postgres 數(shù)據(jù)庫,會返回符合當前選擇器的所有 postgres pod。

下面我們來看幾個實際的 Demo

有狀態(tài) Headless 服務(wù)
對于有狀態(tài)服務(wù)開來講,需要創(chuàng)建 StatefulSet 為其提供能力,資源文件定義,只是一個 Demo ,所以我們這里沒有定義 存儲卷相關(guān)。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  creationTimestamp: null
  labels:
    app: web-headless
  name: web
spec:
  serviceName: web-headless
  replicas: 3
  selector:
    matchLabels:
      app: web-headless
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web-headless
    spec:
      containers:
        - image: nginx
          name: nginx-web
          ports:
            - containerPort: 80
              name: nginx-web
          resources: {}
通過資源文件創(chuàng)建有狀態(tài)的 pod

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get statefulsets.apps web  -o wide
NAME   READY   AGE   CONTAINERS   IMAGES
web    3/3     96s   nginx-web    nginx
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get pods -o wide  | grep web*
web-0                                              1/1     Running   0              2m13s   10.244.217.10    vms155.liruilongs.github.io   <none>           <none>
web-1                                              1/1     Running   0              2m11s   10.244.194.67    vms156.liruilongs.github.io   <none>           <none>
web-2                                              1/1     Running   0              114s    10.244.217.11    vms155.liruilongs.github.io   <none>           <none>
之后我們需要創(chuàng)建對應(yīng)的 Headless Service ,這里需要注意的是 clusterIP: None,選擇器:app: web-headless

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-headless
  labels:
    app: nginx_headless
spec:
  ports:
  - port: 30088
    targetPort: 80
    name: nginx-web-headless
  clusterIP: None
  selector:
    app: web-headless
創(chuàng)建對應(yīng)的 Headless SVC

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl  apply  -f headless.yaml
service/web-headless configured
┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl get svc web-headless
NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
web-headless   ClusterIP   None         <none>        30088/TCP   24h
可以看到,他對每個 pod 都創(chuàng)建了對應(yīng)的 Endpoint

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl describe svc web-headless
Name:              web-headless
Namespace:         awx
Labels:            app=nginx_headless
Annotations:       <none>
Selector:          app=web-headless
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                None
IPs:               None
Port:              nginx-web-headless  30088/TCP
TargetPort:        80/TCP
Endpoints:         10.244.194.67:80,10.244.217.10:80,10.244.217.11:80
Session Affinity:  None
Events:            <none>
可以通過不同的方式獲取 Headless Service 的 Pod 列表。

集群外獲取 Headless Service 的 Pod 列表
可以直接通過調(diào)用 Rest 接口的 方式獲取 Headless 對應(yīng)的 Endpoints,這里為了方便暴露 Rest 服務(wù),通過  kubectl proxy 做一個內(nèi)部代理。

┌──[root@vms81.liruilongs.github.io]-[~]
└─$nohup  kubectl proxy --port=30021 &
[1] 109103
┌──[root@vms81.liruilongs.github.io]-[~]
└─$nohup: 忽略輸入并把輸出追加到"nohup.out"
測試一下

┌──[root@vms81.liruilongs.github.io]-[~]
└─$curl http://localhost:30021/api/ -s
{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "192.168.26.81:6443"
    }
  ]
}
對于 1.21 之前的版本獲取 endpoins,可以通過 Endpoints的方式獲取

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl describe endpoints web-headless
Name:         web-headless
Namespace:    awx
Labels:       app=nginx_headless
              service.kubernetes.io/headless=
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2022-12-09T07:13:15Z
Subsets:
  Addresses:          10.244.194.67,10.244.217.10,10.244.217.11
  NotReadyAddresses:  <none>
  Ports:
    Name                Port  Protocol
    ----                ----  --------
    nginx-web-headless  80    TCP

Events:  <none>
通過 curl 調(diào)用對應(yīng)的 REST 接口






┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$curl -s  http://localhost:30021/api/v1/namespaces/awx/endpoints/web-headless  | jq .subsets
[
  {
    "addresses": [
      {
        "ip": "10.244.194.67",
        "hostname": "web-1",
        "nodeName": "vms156.liruilongs.github.io",
        "targetRef": {
          "kind": "Pod",
          "namespace": "awx",
          "name": "web-1",
          "uid": "d71722db-1c41-44ee-a55d-0042c7d2086e",
          "resourceVersion": "14843070"
        }
      },
      {
        "ip": "10.244.217.10",
        "hostname": "web-0",
        "nodeName": "vms155.liruilongs.github.io",
        "targetRef": {
          "kind": "Pod",
          "namespace": "awx",
          "name": "web-0",
          "uid": "7fa21492-d0f5-4840-8517-a05ed04651a4",
          "resourceVersion": "14843008"
        }
      },
      {
        "ip": "10.244.217.11",
        "hostname": "web-2",
        "nodeName": "vms155.liruilongs.github.io",
        "targetRef": {
          "kind": "Pod",
          "namespace": "awx",
          "name": "web-2",
          "uid": "7b262352-b12c-4ad2-8d83-8143c71e8c27",
          "resourceVersion": "14843135"
        }
      }
    ],
    "ports": [
      {
        "name": "nginx-web-headless",
        "port": 80,
        "protocol": "TCP"
      }
    ]
  }
]
如果使用的 1.21 以及之后的版本,我們可以通過 EndpointSlicp 來獲取對應(yīng)的 pod 列表

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl describe endpointslices web-headless-888xr
Name:         web-headless-888xr
Namespace:    awx
Labels:       app=nginx_headless
              endpointslice.kubernetes.io/managed-by=endpointslice-controller.k8s.io
              kubernetes.io/service-name=web-headless
              service.kubernetes.io/headless=
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2022-12-09T07:13:15Z
AddressType:  IPv4
Ports:
  Name                Port  Protocol
  ----                ----  --------
  nginx-web-headless  80    TCP
Endpoints:
  - Addresses:  10.244.217.10
    Conditions:
      Ready:    true
    Hostname:   web-0
    TargetRef:  Pod/web-0
    NodeName:   vms155.liruilongs.github.io
    Zone:       <unset>
  - Addresses:  10.244.194.67
    Conditions:
      Ready:    true
    Hostname:   web-1
    TargetRef:  Pod/web-1
    NodeName:   vms156.liruilongs.github.io
    Zone:       <unset>
  - Addresses:  10.244.217.11
    Conditions:
      Ready:    true
    Hostname:   web-2
    TargetRef:  Pod/web-2
    NodeName:   vms155.liruilongs.github.ioweb  
    Zone:       <unset>
Events:         <none>
通過 curl 調(diào)用對應(yīng)的 REST 接口

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$curl -s  http://localhost:30021/apis/discovery.k8s.io/v1/namespaces/awx/endpointslices/web-headless-888xr  | jq .endpoints
[
  {
    "addresses": [
      "10.244.217.10"
    ],
    "conditions": {
      "ready": true,
      "serving": true,
      "terminating": false
    },
    "hostname": "web-0",
    "targetRef": {
      "kind": "Pod",
      "namespace": "awx",
      "name": "web-0",
      "uid": "7fa21492-d0f5-4840-8517-a05ed04651a4",
      "resourceVersion": "14843008"
    },
    "nodeName": "vms155.liruilongs.github.io"
  },
  {
    "addresses": [
      "10.244.194.67"
    ],
    "conditions": {
      "ready": true,
      "serving": true,
      "terminating": false
    },
    "hostname": "web-1",
    "targetRef": {
      "kind": "Pod",
      "namespace": "awx",
      "name": "web-1",
      "uid": "d71722db-1c41-44ee-a55d-0042c7d2086e",
      "resourceVersion": "14843070"
    },
    "nodeName": "vms156.liruilongs.github.io"
  },
  {
    "addresses": [
      "10.244.217.11"
    ],
    "conditions": {
      "ready": true,
      "serving": true,
      "terminating": false
    },
    "hostname": "web-2",
    "targetRef": {
      "kind": "Pod",
      "namespace": "awx",
      "name": "web-2",
      "uid": "7b262352-b12c-4ad2-8d83-8143c71e8c27",
      "resourceVersion": "14843135"
    },
    "nodeName": "vms155.liruilongs.github.io"
  }
]
集群內(nèi)獲取 Headless Service 的 Pod 列表
對于無頭服務(wù),客戶端可以通過連接到服務(wù)的 DNS 名稱來連接到其 pod,就像使用常規(guī)服務(wù)一樣,因為 DNS 返回 pod 的 IP,客戶端直接連接到 pod,所以不是通過服務(wù)代理。這里通過 DNS 解析獲取的 Pod 列表,Headless 服務(wù)仍然提供跨 Pod 的負載平衡,但這僅僅是通過 DNS 循環(huán)機制實現(xiàn)的負載均衡。而不是 sessionAffinity 相關(guān)配置

可以通過 對 服務(wù)的 DNS 解析來獲取 POD 列表。

創(chuàng)建一個測試 Pod

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl run tmp01 --image=tutum/dnsutils -- sleep infinity
pod/tmp01 created
同一命令空間獲取 headless Service 的 Pod 列表

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl exec  tmp01 -it -- /bin/bash
root@tmp01:/# nslookup web-headless
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.67
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.10
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.11
不同命名空間獲取 headless Service 的 Pod 列表

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl exec  tmp01 -it -- /bin/bash
root@tmp01:/# nslookup web-headless.awx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.11
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.67
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.10

root@tmp01:/# nslookup web-headless.awx.svc.cluster.local.
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.11
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.67
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.10
無狀態(tài)的 Headless 服務(wù)
關(guān)于無狀態(tài)的 Headless Service 這里我們也簡單介紹,和,有狀態(tài)的沒什么區(qū)別,對應(yīng)的  SVC 還是用之前的 web-headless, 在 StatefulSet 的基礎(chǔ)上,我們創(chuàng)建一個 deloyment 提供服務(wù)能力

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$cat deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web-headless
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-headless
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web-headless
    spec:
      containers:
      - image: nginx
        name: nginx-web
        ports:
        - containerPort: 80
          name: nginx-web
        resources: {}
status: {}
通過 DNS 獲取IP,可以發(fā)現(xiàn),對應(yīng)的 A 記錄增加到 6個

┌──[root@vms81.liruilongs.github.io]-[~/ansible]
└─$kubectl exec  tmp01 -it -- /bin/bash
root@tmp01:/# nslookup web-headless.awx
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.67
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.11
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.10
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.69
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.194.70
Name:   web-headless.awx.svc.cluster.local
Address: 10.244.217.12

root@tmp01:/# exit
exit
關(guān)于  Headless Service 和小伙伴分享到這里, 通過 無頭服務(wù),我們可以通過 Servcie 來動態(tài)感知 Pod 副本的變化,監(jiān)聽 Pod 的狀態(tài),實現(xiàn)部分分布式集群的動態(tài)構(gòu)建, 同時在有狀態(tài)應(yīng)用中都會涉及 Headless Service。

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

https://stackoverflow.com/questions/52707840/what-is-a-headless-service-what-does-it-do-accomplish-and-what-are-some-legiti

https://cloud.tencent.com/developer/article/1638722

https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoints-v1/

https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoint-slice-v1/

作者:山河已無恙


歡迎關(guān)注微信公眾號 :山河已無恙