Kubernetes 系列六】Kubernetes 服務發現

什麼是服務發現?

服務發現就是一種提供服務發佈和查找的服務,是基於服務架構(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"]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章