天天看點

淺談 kubernetes 服務發現1. 什麼叫服務發現?2. 基于 IP 位址的服務發現3. 基于 DNS 的服務發現

1. 什麼叫服務發現?

service ip 在 service 的整個生命周期中都是不會發生變化的,是以當給 pod 建立 service 後,就可以通過 service ip 穩定的通路到 pod,service 後面的 pod 可能會重建,或者數量發生變化,導緻 pod 的 ip 位址變化,但是通過穩定不變的 service ip 總能通路到這些 pod。

那問題來了,pod-a 是怎麼通過 svc-b 通路到 pod-b 中的應用程式的,更直白點,pod-a 是怎麼發現(通路) svc-b 的?

在回答這個問題之間,我們先來看下平時都是怎麼通路 web 程式的:

場景一:我們建立一個 Spring Boot HelloWorld 項目并在本地啟動,在浏覽器中輸入:

http://127.0.0.1.8080

就可以看到後端傳回的 “hello world” 字元串。

場景二:通路百度,在浏覽器中輸入

www.baidu.com

就可以通路到百度的首頁面。

上述情況中,場景一直接使用 IP 位址通路,場景二使用域名通路。這是我們平常通路 web 程式的兩種方式,在kubernetes 中 pod 通路 service 也是通過這兩種方式實作的。通路執行的關鍵點是要提前知道 IP 位址或者域名,如果二者都不知道,那通路個毛線。兩個場景中的通路發起者都是人,同時

http://127.0.0.1.8080

無論如何變化,人眼一看便知, www.baidu.com 的域名是備了案,不會變的,是以使用者找到并且輸入正确的 IP 位址都不是難事,那 kubernetes 中的 pod 是如何提前知道 service 的 IP 位址或者域名的呢?

當 pod 知道 service 的 IP 位址或者域名,那麼這個 service 就被“發現了”,“服務發現” 就是這個意思。

2. 基于 IP 位址的服務發現

這種方式在有些書中也叫做 ”通過環境變量發現服務“,為什麼這麼叫呢,原因在下面。複現下這種場景:

先建立 service

# flask-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: flask-hello
spec:
  selector:
    app: flask-hello
  ports:
    - port: 80
      targetPort: 5001
           

執行指令:

kubectl create -f flask-svc.yaml
           

檢視 flask-hello service

kubectl get svc
           
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
flask-hello         ClusterIP   10.101.113.175   <none>        80/TCP     29h
kubernetes          ClusterIP   10.96.0.1        <none>        443/TCP    113d
           

目前叢集配置設定給 flask-hello 的 IP 位址是:10.101.113.175。記住這個 IP 位址,後面要對照看的。

再建立兩個 pod。

# flask-hello-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-hello
  labels:
    app: flask-hello-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flask-hello
  template:
    metadata:
      name: flask-hello
      labels:
        app: flask-hello
    spec:
      containers:
        - name: flask-hello
          image: diego1109/flask-hello:latest
          ports:
            - name: http
              containerPort: 5001
           

建立 deployment

kubectl create -f flask-hello-deployment.yaml
           

檢視 pod

kubectl get pods
           
NAME                           READY   STATUS    RESTARTS   AGE
flask-hello-694d455dfc-hjd6g   1/1     Running   0          3m57s
flask-hello-694d455dfc-xrdp7   1/1     Running   0          3m57s
           

檢視 pod 中的環境變量

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=flask-hello-694d455dfc-hjd6g
FLASK_HELLO_PORT_80_TCP=tcp://10.101.113.175:80
KUBERNETES_SERVICE_PORT=443
FLASK_HELLO_SERVICE_PORT=80
FLASK_HELLO_PORT=tcp://10.101.113.175:80
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
FLASK_HELLO_PORT_80_TCP_ADDR=10.101.113.175
KUBERNETES_SERVICE_HOST=10.96.0.1
FLASK_HELLO_SERVICE_HOST=10.101.113.175  (<-- 看這裡!)
FLASK_HELLO_PORT_80_TCP_PROTO=tcp
FLASK_HELLO_PORT_80_TCP_PORT=80		(<-- 看這裡!)
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
HOME=/root
           

第 13 行、第15 行分别是 flask-hello service 的 IP 位址和端口号。原來 Kubernetes 将 service 的 IP 位址和端口号都設定成 pod 的環境變量了。那就可以在 pod-a 中直接通過環境變量通路 service 了,進而通路 pod-b中的應用程式。試試~~~

進入 pod

curl service

curl -X GET "http://${FLASK_HELLO_SERVICE_HOST}:${FLASK_HELLO_PORT_80_TCP_PORT}"
Hello k8s ! 
           

在這種方式中,pod 通過 環境變量發現了 service 的 IP 和 port ,進而通路到了 pod-b。”通過環境變量發現服務“ 這個名字描述的也挺形象的。

注意一個細節:在上面的 case 中,我們是先建立 service 再建立 pod,是以 pod 的環境變量中有 service 的 ip 和 port。那如果把順序颠倒一下,先建立 pod 再建立 service,結果會怎麼樣呢?答案是:pod 的環境變量中不會有 service 的 ip 和 port,(這個場景大家可以自己測試下)。

當 pod 的環境變量中沒有 service 的 IP 和 port 時,pod 該怎麼”發現“ service 呢?kubernetes 提供的另外一種服務發現方式可以應對這種場景。

3. 基于 DNS 的服務發現

先來看下平時我們是怎麼使用 DNS 的。當使用域名通路 web 應用程式時,首先要做的事情是解析域名,擷取到該域名對應的 IP 位址,再使用 IP 位址去通路 web 應用程式。當浏覽器中輸入

www.baidu.com

時,(假設本地電腦沒有域名緩存)會先将該域名發送到域名解析伺服器(DNS),DNS 會去查找

www.baidu.com

對應的 IP 位址是多少,并将結構傳回給浏覽器所在的電腦。

本地電腦的

/etc/resolv.conf

檔案中的

nameserver 198.18.0.2

表示給本地電腦組態的域名解析伺服器的 IP 位址。大家可以試試,如果把該字段屏蔽掉,或者将 IP 位址随便修改成一個不存在的,再在浏覽器中輸入

www.baidu.com

時,就會顯示請求失敗。

kubernetes 叢集中的域名解析也是這麼做的,接下來就梳理下叢集中域名解析過程。

域名解析首先得有個”域名解析伺服器“ 的東西存在,那它在哪裡呢?

kubectl get svc -n kube-system
           
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   123d
           

Kube-system 命名空間中 kube-dns 就是叢集的”域名解析伺服器“,記住它的 IP 位址 10.96.0.10。 Kube-dns 是 service,它是個連接配接件或者導航器之類的東西,真正的執行邏輯處理的是它後面的 pod,是以域名解析過程應該是發生在 pod 中的。

kubectl get pods -n kube-system
           
NAME                                      READY   STATUS    RESTARTS   AGE
calico-kube-controllers-75d555c48-cj4tk   1/1     Running   4          123d
calico-node-2f2md                         1/1     Running   1          123d
calico-node-drdx2                         1/1     Running   1          123d
calico-node-rxflt                         1/1     Running   4          123d
coredns-7ff77c879f-bsn6c                  1/1     Running   4          123d
coredns-7ff77c879f-c62qp                  1/1     Running   4          123d
etcd-master-aliyun                        1/1     Running   4          123d
kube-apiserver-master-aliyun              1/1     Running   4          123d
kube-controller-manager-master-aliyun     1/1     Running   4          123d
kube-proxy-mfn7v                          1/1     Running   1          123d
kube-proxy-svvgr                          1/1     Running   1          123d
kube-proxy-zzl9d                          1/1     Running   5          123d
kube-scheduler-master-aliyun              1/1     Running   4          123d
           

第6行,第7行 應該就是執行域名解析的 pod 了,這兩個 pod 知道叢集中運作的所有的 service。因為 kube-dns service 的 label 正好和這兩個 pod 的 label 是比對的。我一直想 exec 進去看下 pod 裡面到底寫的什麼,一直都沒成功…,繼續尋找正确的打開方式。

叢集中個的域名解析伺服器找到了,那 pod 是怎麼知道這個 DNS 的呢?

kubectl exec flask-hello-694d455dfc-hjd6g -- cat /etc/resolv.conf
           
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
           

很明顯了,kubernetes 在 pod 建立時就已經在其内部的

/etc/resolv.conf

檔案中配置了域名解析伺服器的位址。而且這個位址就是 kube-dns service 的 ip 位址。原來在 pod 中使域名通路時,所有的域名查詢請求都會被 kube-dns 響應,查找到對應的 IP 位址并傳回。

到這裡服務就又被發現了,不過還差一點點細節,接着往下看看呗~~

域名

www.baidu.com

是人為設計且備案了的,它幾乎是不會再改變的。 那叢集中每個 service 的域名是什麼呢?在哪能查到呢?

kubernetes 有一套域名規則,叢集中定義的每個 service 都會被配置設定一個域名。這裡我們隻講 ”普通 service“(而不是”無頭 service“) 的 AAAA 記錄。這種域名會被解析成對應 service 的 IP 位址。這裡别被概念給吓到,很簡單的,service 有兩種,普通和無頭,域名也有好多中格式,其中普通 service 的 AAAA 格式的域名會被解析成 service IP。當然也會其他格式的域名會被解析成其他元件的 IP ,不過這裡我們先不去管。

先看下域名的 AAAA 記錄格式長什麼樣子:

svc-name.svc-namespace.svc.cluster-domain.example
           

這個格式翻譯下就是

是以當知道 service 的名字和它所在的命名空間時,也就等于是知道了它的域名。

做個試驗,試試~~

  1. 查詢 flask-hello service 的 IP 位址
kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc.cluster.local
           
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	flask-hello.default.svc.cluster.local
Address: 10.101.113.175
           

第1行、第2行表示使用的域名解析伺服器的 IP 位址,翻到上面去對對,就是 kube-dns。第4行、第5行表示 域名:

flask-hello.default.svc.cluster.local

對應的 IP 位址是:

10.101.113.175

,再翻到上面對對。

  1. 通過域名通路 flask-hello service
kubetcl exec flask-hello-694d455dfc-hjd6g -- curl -X GET http://flask-hello.default.svc.cluster.local:80
           
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    20  100    20    0     0   2000      0 --:--:-- --:--:-- --:--:--  2000
Hello k8s ! 
           

通路到 flask-hello service 後面連接配接到的 pod 是 work 的。

細節補上了,到此為止,基于 DNS 的服務發現就算是基本介紹完了。

最後在啰嗦一個細節了,重要與否不好衡量,但是會變的友善。

執行指令:

kubectl exec flask-hello-694d455dfc-hjd6g -- cat /etc/resolv.conf
           
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
           

第2行有三個字元串,看起來應該是跟域名有關的東東,那具體是幹什麼用的呢。

svc-name.svc-namespace.svc.cluster-domain.example

這種格式的域名有個專有名詞:完全限定域名(fully qualified domain name,FQDN),表示這是域名格式最完整的形式。

當域名不完整時,也是能被解析的:

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
           
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	flask-hello.default.svc.cluster.local
Address: 10.101.113.175
           

解析無誤。同樣,下面兩種格式的域名也能被解析成功。 (自己可以試試)

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default
kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello
           

之是以能解析成功,是因為第2行那三個字元串是拿來做拼接的,kubernetes 會将使用者輸入的域名和 search 後面的三個字元串做拼接,拼成 FQDN 格式再做解析。至于拼接的細節和政策,我沒有研究過,有興趣的可以自己嘗試下。

做個試驗:

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc.cluster
           
Server:		10.96.0.10
Address:	10.96.0.10#53

** server can't find flask-hello.default.svc.cluster: NXDOMAIN

command terminated with exit code 1
           

因為沒有

local

字尾,是以

flask-hello.default.svc.cluster

沒法被拼接成 FQDN。

再做個試驗:

修改 search

search default.svc.cluster.local svc.cluster.local
           

再執行:

kubectl exec flask-hello-694d455dfc-hjd6g -- nslookup flask-hello.default.svc
           
Server:		10.96.0.10
Address:	10.96.0.10#53

** server can't find flask-hello.default.svc: NXDOMAIN

command terminated with exit code 1
           

也是找不見 service 的。

到這基本結束啦,關于 handless service 在這裡。

繼續閱讀