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 應用程式。當浏覽器中輸入時,(假設本地電腦沒有域名緩存)會先将該域名發送到域名解析伺服器(DNS),DNS 會去查找
www.baidu.com
www.baidu.com
對應的 IP 位址是多少,并将結構傳回給浏覽器所在的電腦。
本地電腦的
檔案中的
/etc/resolv.conf
表示給本地電腦組態的域名解析伺服器的 IP 位址。大家可以試試,如果把該字段屏蔽掉,或者将 IP 位址随便修改成一個不存在的,再在浏覽器中輸入
nameserver 198.18.0.2
時,就會顯示請求失敗。
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 的名字和它所在的命名空間時,也就等于是知道了它的域名。
做個試驗,試試~~
- 查詢 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
,再翻到上面對對。
- 通過域名通路 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 在這裡。