1. 程式人生 > >Kubernetes 系列六】Kubernetes 服務發現

Kubernetes 系列六】Kubernetes 服務發現

目錄

  • 什麼是服務發現?
  • 環境變數
  • DNS 服務
    • Linux 中 DNS 查詢原理
    • Kubernetes 中 DNS 查詢原理
    • 除錯 DNS 服務
    • 存根域及上游 DNS

什麼是服務發現?

服務發現就是一種提供服務釋出和查詢的服務,是基於服務架構(SOA)的核心服務,需具備以下關鍵特性:

  1. 註冊(Registration),新增服務到服務列表;
  2. 目錄(Directory),即服務列表;
  3. 查詢(Lookup),通過服務名找到服務。

服務發現的關鍵在於服務元資料(metadata)的儲存,包括服務名、服務 IP、服務埠等資訊。

Kubernetes 支援兩種服務發現方式,環境變數和 DNS。

環境變數

當 Pod 建立時,Kubernetes 會將每個活躍的 Service 的相關環境變數設定到 Pod 中。值得注意的是,這些環境變數不會因為相關 Service 改變而改變(筆者親手試驗過)。

Kubernetes 會設定兩類環境變數,分別是:

  1. Kubernetes Service 環境變數
  2. Docker Link 環境變數

Kubernetes Service 環境變數形如(假定服務名為 latte,且訪問埠為 8080):

LATTE_SERVICE_HOST=10.100.251.57
LATTE_SERVICE_PORT=8080

Docker Link 環境變數形如(假定服務名為 latte,且訪問埠為 8080):

LATTE_PORT_8080_TCP_ADDR=10.100.251.57
LATTE_PORT_8080_TCP_PORT=8080
LATTE_PORT_8080_TCP_PROTO=tcp
LATTE_PORT=tcp://10.100.251.57:8080
LATTE_PORT_8080_TCP=tcp://10.100.251.57:8080

可以通過進入 Pod 的終端,執行 env 命令檢視設定的環境變數驗證。

kubectl exec -ti <pod-name> env --namespace=<my-namespace>

此種方式的服務發現缺點很明顯:

  1. 先前的服務必須先執行起來,否則 Pod 無法發現;
  2. 如依賴的服務宕機或繫結新地址,Pod 無法發現,仍然持有舊的地址。

幸好,我們還有另一種服務發現機制。

DNS 服務

在講述 Kubernetes 中使用 DNS 進行服務發現之前,我們不得不先了解下 Linux 中是如何進行 DNS 查詢的。

Linux 中 DNS 查詢原理

在 Linux 的 /etc/ 目錄中,存在 3 個我們需要關注的檔案,分別是(參考:http://man7.org/linux/man-pages/man5/host.conf.5.html):

  1. /etc/host.conf:DNS 解析器配置,包含 trim、multi、order、reorder 和 nospoof 等等配置。
  2. /etc/hosts:本地 hosts 資料庫,存放本地的域名到 IP 的配置。
  3. /etc/resolv.conf:DNS 解析器配置,包含 nameserver、domain、search、sortlist 和 options 等配置。

下面是一臺 Linux 伺服器中 3 個相關檔案的內容:

# /etc/host.conf
multi on
# /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost6 localhost6.localdomain6
# /etc/resolv.conf
; generated by /usr/sbin/dhclient-script
search us-west-2.compute.internal
options timeout:2 attempts:5
nameserver 192.168.0.2

/etc/resolv.conf 配置解釋如下:

配置項 功能 備註
nameserver DNS 伺服器 值必須是 IP 地址
domain 本地域名 域中的查詢可以使用相對於本地域名的短名稱
search 主機名查詢列表 預設只包含本地域名。閾值為 6 個域名,256 個字元。
options 選項 修改內部 DNS 解析器變數值。

options 中常見的配置項有:

配置項 功能
ndots 所有查詢中,如果.的個數少於給定的數值,則會根據search中配置的列表依次在對應域中先進行搜尋,如果沒有返回,則最後再直接查詢域名本身。閾值為 15。
timeout 等待 DNS 伺服器響應的超時時間,單位為秒。閾值為 30 s。
attempts 向同一個 DNS 伺服器發起重試的次數,單位為次。閾值為 5。

筆者在本地試驗時發現,檔案 /etc/resolv.conf 是網路連線時自動生成的,依據是:

  1. 當本機處以斷網狀態時,cat /etc/resolv.conf 返回空文字;
  2. 當本機連上網路時,cat /etc/resolv.conf 返回以下內容:
#
# macOS Notice
#
# This file is not consulted for DNS hostname resolution, address
# resolution, or the DNS query routing mechanism used by most
# processes on this system.
#
# To view the DNS configuration used by this system, use:
#   scutil --dns
#
# SEE ALSO
#   dns-sd(1), scutil(8)
#
# This file is automatically generated.
#
nameserver 58.250.162.58
nameserver 8.8.8.8

第一個 DNS 伺服器是中國聯通的,通過訪問 https://whois.domaintools.com/58.250.162.58 可知;

第二個 DNS伺服器是 Google 的,通過 nslookup 8.8.8.8 或者訪問 https://whois.domaintools.com/8.8.8.8 可知。

Kubernetes 中 DNS 查詢原理

Kubernetes 中有兩個可選的 DNS 服務外掛(處在 kube-system 名稱空間):

外掛 說明
kube-dns 其程式碼已經從 kubernetes 庫中分離到單獨的倉庫維護,見 https://github.com/kubernetes/dns
CoreDNS 支援 Kubernetes v1.9 及以上;Kubernetes v1.12 起,官方推薦使用來替換 kube-dns;Kubernetes v1.13 起,成為預設 DNS 服務;佔用記憶體小,查詢速度快。

注意:kube-dns 在 Kubernetes 中有多重含義,要注意區別。

  1. 與 CoreDNS 對比時,使用狹義,表示名為 kube-dns 的 DNS 服務;
  2. 當泛指時,表示 Kubernetes 中的 DNS 服務。

使用 kubeadm 建立 v1.11 及以後的 Kubernetes 叢集,預設啟用 CoreDNS(處於 GA 狀態,見 Software release life cycle)。(來源)

筆者通過 aws 提供的 eksctl 工具建立的 v1.15 的叢集預設也是啟用了 CoreDNS,查閱 eksctl 原始碼可以獲取其預設啟用的外掛。

Kubernetes 通過修改每個 Pod 中每個容器的域名解析配置檔案 /etc/resolv.conf 來達到服務發現的目的。在筆者建立的叢集中獲取其中一個容器的域名解析配置檔案如下:

# /etc/resolv.conf
nameserver 10.100.0.10
search cafe.svc.cluster.local svc.cluster.local cluster.local us-west-2.compute.internal
options ndots:5

其含義是:DNS 伺服器為 10.100.0.10,當查詢關鍵詞中 . 的數量少於 5 個,則根據 search 中配置的域名進行查詢,當查詢都沒有返回正確響應時再嘗試直接查詢關鍵詞本身。

例如,執行 host -v cn.bing.com 後我們將會看到:

Trying "cn.bing.com.cafe.svc.cluster.local"
Trying "cn.bing.com.svc.cluster.local"
Trying "cn.bing.com.cluster.local"
Trying "cn.bing.com.us-west-2.compute.internal"
Trying "cn.bing.com"
...

解析過程是如此緩慢,當對某些服務訪問頻繁時建議額外配置 DNS 記錄。

注:獲取過程如下

# 1) 查詢指定名稱空間中的所有 pod
kubectl  get pods --namespace=cafe
# 2) 進入其中一個 pod 的互動終端
kubectl exec -ti macchiato-6746674bdd-5hmtw sh --namespace=cafe
# 3) 檢視 /etc/resolv.conf
cat /etc/resolv.conf

DNS 伺服器會監聽著叢集內所有 Service API,以在服務不可用時移除記錄,在新服務建立時插入新記錄。

這些記錄存放在哪裡呢?

答案是:儲存在 kube-dns 外掛中的 cache 也可配置到 etcd。

儲存的 DNS 記錄有哪些種類呢?

我們過去或多或少了解到的 DNS 記錄有以下幾種:

類別 作用
A Address record,域名到 IP 地址的記錄
CNAME Canonical name record,別名記錄,設定域名的別名
NS Name server record,域名伺服器記錄
MX Mail exchange record,郵件服務記錄
TXT Text record,為某條記錄設定說明
AAAA IPv6 address record,域名到 IPv6 地址 ( 128 = 32 * 4 )的記錄

這次我們要多認識一個稱之為 SRV(Service locator)的 DNS 記錄,用來通用化地定位服務。

Kubernetes 的 DNS 服務(簡稱為 kube-dns)支援 Service 的 A 記錄、 SRV 記錄和 CNAME 記錄。

我們知道 Kubernetes 中的 Service 是 Pod 的邏輯分組,有 Cluster IP 和 Label Selector 有無之別。沒有設定 Cluster IP 的我們稱之為 Headless Service,否則稱之為 Normal Service。設定了 Label Selector 的會同時產生一個 Endpoints 物件,宣告叢集內部 Service 的訪問端點。

假定有一個 cafe 名稱空間下名為 latte 的 Normal Service,開放了名為 http 的 TCP 埠 8080,kube-dns 會為其生成以下的 A 記錄和 SRV 記錄:

latte.cafe.svc.cluster.local. 5 IN A 10.100.71.56
_http._tcp.latte.cafe.svc.cluster.local. 30 IN SRV 10 100 443 latte.cafe.svc.cluster.local.

注:查詢 DNS 記錄的方法

(1)安裝 dig 工具

將下面的部署配置儲存到檔案 dnsutils.yaml,然後執行 kubectl apply -f dnsutils.yaml 部署。

apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
  - name: dnsutils
 image: tutum/dnsutils
 command:
      - sleep
      - "3600"
 imagePullPolicy: IfNotPresent
restartPolicy: Always

(2)使用 dig 工具獲取 DNS 記錄

# 用法
dig @<DNS伺服器> <記錄型別> <域名> <可選值>
# 示例
## 1)獲取 DNS 服務地址
kubectl get svc kube-dns -n kube-system
NAME       TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.100.0.10   <none>        53/UDP,53/TCP   8d
## 2)進入 dnsutils 的 shell 終端
kubectl exec -ti dnsutils  sh
## 3)查詢 latte 服務的 A 記錄
dig @10.100.0.10 A  latte.cafe.svc.cluster.local  +noall +answer

假如有一個 cafe 名稱空間下名為 mocha 的 Headless Service,kube-dns 會為其生成以下的 A 記錄集(域名到 Pod IPs 的對映):

mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.111
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.112
mocha.cafe.svc.cluster.local. 5 IN A 192.168.62.113

如若有一個 cafe 名稱空間下名為 macchiato 的 Headless 但設定了以下 Endpoints 的 Service:

kind: Endpoints
apiVersion: v1
metadata:
  name: macchiato
  namespace: cafe
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

kube-dns 會為其生成以下的 A 記錄:

macchiato.cafe.svc.cluster.local. 4 IN A 1.2.3.4

如果有一個 cafe 名稱空間下名為 cappuccino 的 Headless 但設定了以下 ExternalName 的 Service:

kind: Service
apiVersion: v1
metadata:
  name: cappuccino
  namespace: cafe
spec:
  type: ExternalName
  externalName: cappuccino.cafe.com

kube-dns 會為其生成以下的 CNAME 記錄:

cappuccino.cafe.svc.cluster.local. 10 IN CNAME cappuccino.cafe.com.

Kubernetes 的 DNS 服務除了支援 Service 的 DNS 記錄外,還支援 Pod 的 A 記錄,使用 hostname + subdomain 方式實現。仔細閱讀以下部署配置。

apiVersion: v1
kind: Service
metadata:
  name: default-subdomain
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: foo # Actually, no port is needed.
    port: 1234
    targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox
spec:
  hostname: busybox-1
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox
spec:
  hostname: busybox-2
  subdomain: default-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox

我們發現其建立了:

  1. name 為 busybox1,hostname 為 busybox-1,subdomain 為 default-subdomain 的 Pod;
  2. name 為 busybox2,hostname 為 busybox-2,subdomain 為 default-subdomain 的 Pod;
  3. name 為 default-subdomain 的 Headless Service。

產生以下 A 記錄:

busybox-1.default-subdomain.svc.cluster.local. 4 IN A 192.168.51.119
busybox-2.default-subdomain.svc.cluster.local. IN A
busybox-2.default-subdomain.svc.cluster.local. 4 IN A 192.168.36.188
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.187
default-subdomain.svc.cluster.local. 4 IN A 192.168.62.188

這些記錄是怎樣的一種格式呢?

參見:https://github.com/kubernetes/dns/blob/master/docs/specification.md

除錯 DNS 服務

使用 busybox 除錯 DNS 服務,因為 busybox 中有 nslookup 工具可以使用。

(1)儲存以下文字到檔案 busybox.yaml(此處使用名稱空間為 cafe )

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: cafe
spec:
  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

(2)應用 busybox.yaml,並檢視狀態

kubectl apply -f busybox.yaml --namespace=cafe
kubectl get pods busybox --namespace=cafe

(3)進入終端互動介面並支援 nslookup 查詢服務 latte

kubectl exec -ti busybox sh --namespace=cafe
nslookup latte

存根域及上游 DNS

當無自定義配置時,不匹配的 DNS 查詢(比如上文說的cn.bing.com)會使用從 Node 中繼承的 nameserver 進行解析。

當有自定義的配置時,會在 DNS 快取層查詢無果後,根據查詢名稱字尾決定去往的 DNS 解析器:

  • 查詢名稱帶有集群后綴的(比如 ".cluster.local"),轉發到 kube-dns。
  • 查詢名稱帶有存根域名字尾的(比如 ".acme.local"),轉發到 custom DNS。
  • 查詢名稱不匹配的(比如 "widget.com"),轉發到 upstream DNS。

以上配置使用 Kubernetes 的ConfigMap 外掛,配置如下:

apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-dns
  namespace: kube-system
data:
  stubDomains: |
    {"acme.local": ["1.2.3.4"]}
  upstreamNameservers: |
    ["8.8.8.8","8.8.4.4"]