Kubernetes - Service : 應用之間是如何溝通
kubernetes 可以創建多個 Pods,Pod 內有一個或多個 Container,那麼 Container 之間是怎麼溝通的的呢 ? 這裡歸類出一些 case :
- 不同網路下,不同 pod 間的 container 的通訊
- 同一網路下,不同 pod 間的 container 的通訊
- 同一個 pod 中,不同的 container 的通訊
以下來對這些 case 進行說明。
Scenario
比如一個 web 應用,通常會有 frontend 和 backend ,分別創建在不同的 pod 裡。在正常情況下,我們只期望外部訪問 frontend ,而不希望 backend 直接被外部訪問到的。假設我們已經寫完 backend 的 k8s yaml,其中 containerPort: 8080
並啟動了 backend Pod 。接下來想要開始寫 frontend k8s yaml,一個重點問題是:
frontend 要如何才能訪問到 backend Pod 呢?
先使用 kubectl get pods -o wide
,來查看到到後端 Pod 的資訊。這邊假設看到 backend Pod IP 為 10.1.1.69
,那麼前端能不能通過 10.1.1.69:8080
這個 Pod IP ,來訪問後端 Pod 呢?
本機測試一下吧 ~
首先先創建一個 k8s frontend service yaml ,讓本機端可以透過 k8s frontend service 連接到前端 Pod ,再來前端 Pod 才用 10.1.1.69:8080
這個 Pod IP ,來訪問後端 Pod。 frontend service yaml 要填幾個重點 :
spec.selector
,並給一個標籤值spec.type: NodePort
spec.ports.nodePort: :30002
nodePort
30000-32767
,注意範圍,不要超過或小於了
再來創建 k8s frontend deployment yaml ,並重點要加上 :
spec.selector.matchLabels
且要和 k8s backend service yaml 的spec.selector
標籤值一模一樣- 環境變數傳入
10.1.1.69:8080
使得前端可以訪問後端
YAML 部屬成功後,本機端應該就可以經由 http://localhost:30002/
正常訪問網站資源了 ! 但要來考慮一些需要面對的問題 :
Deployment 會根據 replicas 創建對應數量的 Pods,此時如果某個 Pod 掛了, k8s 會發現 Pods 數量和 replicas 定義的不一樣,變會重新再起一個 Pod ,此時 Pod 的 IP 可能就會變了。 上面針對 Pod IP 把它交給應用是不對的 !
另外我們設置了 replicas 的數量主要是為了做負載均衡,所以如果在應用裡將 IP 寫死指定某個 Pod 的話,就起不到負載均衡了。
上面的寫法並不好 ~ 不要直接寫死後端 Pod IP 直接連接,應該在後端 Pod 前面加一個 backend service 來管理 Pod !
故新寫一個 k8s backend service yaml ,把後端暴露出來。寫完 YAML 部屬後,可通過 kubectl get service -o wide
查看 Service 詳情。這邊假設 k8s backend Service yaml 的 type 為 ClusterIP 且 Service IP 是 10.99.100.101
且使用了 8080
作為對外 Port。
雖然我們本機端是不能直接通過這個 10.99.100.101:8080
訪問到 backend Service 的;但是如果在 k8s 裡的 Pod ,可以通過 10.99.100.101:8080
來訪問到的。故再回到之前 k8s frontend deployment yaml 更新它,把環境變數改傳入 10.99.100.101:8080
這個 Service ClusterIP ,這樣前端也是可以訪問後端了 ! 但繼續考慮一下問題 :
使用 service 的 ClusterIP 雖然可以解決了由於 pod 的重啟更換 IP 的問題,但是如果 service 重啟,那麼 service的 IP 也會改變,這肯定是不行的…
上面解法全部都不是好解法呢…那怎麼樣才是好的解法呢 ? 以下說明一下各個場景 :
不同網路下,不同 pod 間的通訊
使用 DNS 來解析服務位址
在創建 k8s-Service 時, Kubernetes 會創建一個內部相應的 DNS 紀錄,這個名稱是根據 k8s-Service 名稱和 Namespace 生成的,可以使用一致的 DNS 名稱而非 IP 存取 Service。形式是 :
<service-name>.<namespace>.svc.cluster.local
特別注意,中間有個 svc
重要常用設定 ! DNS 查詢會因為 Pod 所在的 namespace ,而返回不同的結果。不指定 namespace 的 DNS 查詢會被限制在本身 Pod 所在的 namespace 內。 要訪問其他名字空間中的 Service,需要在 DNS 查詢中指定名字空間。
假定有:
- namespace 為
np1
;service 為svc1
;內有一個Pod1
- namespace 為
np2
;service ,為svc2
在Pod1
查詢 svc2
會找不到,因為會是在是 Pod1
的 namespace np1
找svc2
,這當然會找不到。若在Pod1
查詢 svc2.np2
或者 svc2.np2.svc.cluster.local
時,則會返回預期的結果,因為查詢中指定了 namespace。在本範例中np1
namespace 中的 Pod 可以成功地解析:
my-svc.np2
my-svc.np2.svc.cluster.local
同一網路下,不同 pod 間的通訊
這種情況有兩種方式可以使用: DNS 、 環境變數
使用 DNS 來解析服務位址 (比較推薦)
承前範例, 如果 Deployment 和 Service 都沒有指定 namespace 的話,預設是創建在了 default 的 namespace。故在創建 k8s-Service 時, k8s 會創建一個相應的 DNS 紀錄,形式邊來說是 :
<service-name>.default.svc.cluster.local
。
使用環境變數來訪問 Service
承前範例,使用
kubectl exec -it <deployment-name> /bin/bash
命令進入到 Pod 內部,接著使用env
命令查看系統的環境變數,會發現可以看到和 Service 有關的環境變數如下:
< backend-service-name 轉成全大寫且"中橫線- " 變成"下底線_ " >_HOST=10.99.100.101
< backend-service-name 轉成全大寫且"中橫線- " 變成"下底線_ " >_PORT=8080
可以發現
10.99.100.101
和8080
剛好是前面提過的 backend Service IP 和對應的 Port 。有了這環境變數,我們就可以動態獲取 backend Service IP , 藉由 Service IP 當然就可以連接到 deployment 的某個 Pod ,便可完成通信 !
可以使用環境變數來解析 Service IP ,但有一個限制,Pod 和 Service 須在同一個 namespace 中。
同一個 pod 中,不同的 container 的通訊
解答: 用 localhost 來互相通訊
在 kubernetes 集群中,每個 Pod 會被分配一個 IP 位址。 同一個 Pod 內的多個容器會共用這個 IP 位址對外通信,同時,這些容器之間也可以直接通過 localhost:Port
的方式進行通信。因為 container 是共用一個網路 namespace 的,所以可以通過 localhost 來互相通訊,也因此流量並不會經過 eth0 介面。 localhost 不依賴 data link layer 和 physical layer 協議,一旦 transport layer 偵測到目的是 localhost ,資料封包離開 network layer 時,就會被傳回本機的連接埠應用。 這種模式傳輸效率較高,非常適合容器間進程的頻繁通訊。
對 container 來說, hostname 就是 Pod 的名稱。因為 Pod 中的所有 container 共用同一個 IP 和 port 空間,因此需要為每個容器分配不同的 port 。
Pod 中的應用需要自己協調管理 port 的使用。
k8s Pod 在啟動其他 container 的時候會先啟動一個叫 pause container,新創建的 container 和 pause container 共用同一個網路 namespace,而不是和宿主機共用。