昨天我們看了應用程式的 配置管理。透過 ConfigMap 將一般設定參數從程式碼中分離,用 Secret 集中管理敏感資訊,甚至實作了 ETCD 靜態加密來加強資料保護。
現在我們已經掌握了 Pod 的建立與管理、副本控制、資源配額管理,以及配置管理。今天我們要來看看 Service,看看 Kubernetes 如何讓 Cluster 內運行的 Pod 要被外界存取,以及Pod 之間如何互相通訊。
Service
是 Kubernetes 中負責連線的關鍵組件。想想看,Pod 的 IP 是動態分配的,Pod 重啟後 IP 就變了,這樣應用程式根本無法穩定連線,而 Service
可以幫助我們把內部各個組件連接在一起。
Service 提供了一個 穩定的網路端點,解決幾個核心問題:
譬如說前端提供給 User 存取,或者前端要像後端請求資料、後端要像資料庫請求資料。
NodePort
在每個 Node 上開啟一個固定 port(範圍 30000-32767),將外部流量轉發到對應的 Pod。
運作原理 如上圖:當有人存取 NodeIP:NodePort
時,流量會自動轉發到 Pod 的指定 port。如果有多個 Pod,Service 會使用隨機演算法做負載平衡。
如果今天我們的 Pod 是分散在不同的 Node,或者是一個 Node 中有多個 Pod,甚至是多個 Node 之中有多個 Pod。Service 都會自動建立跨 Cluster 的映射,將所有符合條件的 Pod 納入管理。你可以透過任一個 Node 的 IP 搭配指定的 NodePort 來存取服務。
ClusterIP
是叢集內部服務通訊的標準方式。由於 Pod IP 不穩定,微服務之間不能直接依賴 Pod IP 進行通訊。
ClusterIP
會為一組 Pod 建立一個虛擬 IP,這個 IP 只能在 Cluster 內部存取。其他 Pod 可以透過這個虛擬 IP 或 Service 名稱來呼叫服務。
Service 會把同樣的 Pod Group 起來,提供一個訪問這個 Pod Group 的唯一入口,而這個入口就是 ClusterIP
會為 Service 分配一個只有 Cluster 內部能存取的虛擬 IP。
如上圖,當前端需要呼叫後端 API 時,Service 會將請求隨機分配給後端的任一個 Pod。這樣當後端 Pod 需要擴展或縮小或更新時,不會影響到前端的正常運作。
LoadBalancer
主要用於雲端環境。當建立 LoadBalancer 類型的 Service 時,Kubernetes 會透過雲端供應商的 API 請求建立真正的 Load Balancer 設備。雲端平台會自動分配一個 External IP 並設定負載平衡規則,這個 IP 會直接指派給 K8s Service 物件。
不過 LoadBalancer
不在 CKAD 考試範圍內,所以後面不會實作這個類型。
首先建立一個 Deployment:
kubectl create deployment nginx --image nginx
然後將 Deployment 暴露為 NodePort Service:
kubectl expose deployment nginx --type=NodePort --port=80
檢查 Service 狀態:
kubectl get svc
可以看到 Service 已經建立,NodePort 被自動分配為 31924。
一般情況下,如果是 VM 作為 Node 安裝的 K8s Cluster,現在就可以透過瀏覽器存取 NodeIP:31924
來查看 nginx 網頁。但因為使用 KinD,只能用 curl 來測試:
# 查看 Node IP
kubectl get nodes -o wide
# 發起 Request
curl http://<節點IP>:31924
查看自動產生的 Service 配置
kubectl get svc nginx -o yaml
可以發現 kubectl
產生的 Service 在 ports 部分沒有 name 參數。雖然目前不影響功能,但在整合其他工具(如 Istio
、Prometheus
)時可能會有問題。
看到 kubectl
命令式幫們建立的 Service 有小瑕疵,因此我們接下來用聲明式建立 Service。
看到上方影片,主要的欄位跟之前其他資源物件都一樣。這邊的 port
和 targetPort
的分法是要從 Service 的角度出發,對 Service 來說 targetPort
對應的就會是 Pod 的 Port,這個 Port 是 Optional 的,所以如果不指定的話,預設會帶入 port 的值,在一般實務上這兩個 port 也會一模一樣。另外看到 nodePort,他也是 Optional 的,如果沒特別指定的話就會從 30000~32767 隨機分配一個可用的作為 NodePort。
但是有了這些設定還不夠,即使我們指定的 targetPort
,但我們沒有指定目標的 Pod。因為可能我們有多個 Pod 得 Port 都是 80。而 Service 指定 Pod 的方式也很簡單,就是跟 ReplicaSet、Deployment 一樣使用 selector
對應到有相關 labels 的 Pod。
NodePort Service 的 YAML 結構需要注意幾個重點:
port
:Service 本身的 porttargetPort
:Pod 的 port(可選,預設等於 port)nodePort
:Node 上對外的 port(可選,會自動分配)selector
:用來選擇要管理的 PodapiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
nodePort: 31924
selector:
app: nginx
也可以用 dry-run 快速產生範本:
kubectl expose deployment nginx --type=NodePort --port=80 --dry-run=client -o yaml
理解 Endpoints 機制:
Service 透過 Endpoints 資源來追蹤符合條件的 Pod:
kubectl get endpoints
Endpoints 記錄了所有符合 Service selector 的 Pod IP:Port。當 Pod 數量變化時,Endpoints 會自動更新:
kubectl scale deployment nginx --replicas=3
如果我們做 scale up 的話可以看到 endpoints 這邊有多個 IP。
當 Pod 被刪除並重建時,Endpoints 也會跟著更新:
⚠️ 注意只有處於 READY 狀態的 Pod 才會出現在 Endpoints 中。
建立 Service 時不指定 type,預設就是 ClusterIP:
kubectl expose deployment nginx --port=80
聲明式建立 ClusterIP Service:
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx
這邊 ClusterIP
的結構跟 NodePort 差不多,唯一的差別就是 type 不一樣然後移除 NodePort 參數。
這樣 ClusterIP
就被我們建立起來了,但因為 ClusterIP
是用於 Cluster 內容元件的連線,因此我們這邊先不實作 Pod 之間的連線,先看完 CKAD 的考試範圍之後,後續再來實戰這個部分!
前面在 NodePort 的部分有提到,礙於 KinD 的限制,因此我要確認 NodePort 有沒有建立成功只能用 curl 的方式,或者進入 Node 裡面來查看 NodePort 有沒有成功被建立。我們先來看看進入 Node 節點後,就可以跟 Local 用一樣的方式請求 Service:
# 進入 kind-worker Node
docker exec -it kind-worker bash
# 任一 Node 名稱配上 NodePort 都可以
curl kind-worker2:31924
但是這樣進入 Node 蠻麻煩的,因此我們可以使用 kubectl
的 port-forward
功能來測試 Service:
kubectl port-forward --address 0.0.0.0 services/nginx 30123:80
可以看到這樣子就成功用瀏覽器進入啦!
port-forward
的一個特色是,它直接對應到 Pod 的 port,所以 ClusterIP
類型的 Service 也可以用這種方式測試:
kubectl port-forward --address 0.0.0.0 services/backend 30123:80
今天我們看了 Service
如何解決 Kubernetes 叢集中的網路通訊問題。透過 NodePort
,我們可以將內部服務暴露給外部使用者存取;透過 ClusterIP
,我們讓叢集內的服務能夠穩定地互相通訊,不用擔心 Pod IP 變動的問題。
我們也看到了 Service
如何透過標籤選擇器自動發現和管理 Pod,以及 Endpoints
如何動態追蹤這些 Pod 的實際位置。這讓我們的微服務架構能夠真正地鬆散耦合,每個服務都可以獨立擴展和更新。
不過現在還有一個重要問題:在叢集內部,所有 Pod 預設都可以互相通訊,這在某些情況下可能不是我們想要的。比如說,我們可能希望前端 Pod 只能存取 API 服務,而不能直接連接到資料庫 Pod。或者不同租戶的應用程式應該彼此隔離,不能互相存取。
明天我們要來看看 Network Policy,看看 Kubernetes 如何幫我們實現網路層級的安全控制,讓 Cluster 內的通訊更加安全和可控!