1. 程式人生 > >【Kubernetes學習筆記】-服務訪問之 IP & Port & Endpoint 辨析

【Kubernetes學習筆記】-服務訪問之 IP & Port & Endpoint 辨析

[TOC] 當新手剛學習k8s時候,會被各種的IP 和port 搞暈,其實它們都與k8s service的訪問有密切關係,梳理它們之間的差異可以更好了解k8s的服務訪問機制。 ![IP&Port](https://gitee.com/owen2016/pic-hub/raw/master/pics/20201122230932.png) ## 不同型別的IP - Node IP:Node節點的IP地址。 節點物理網絡卡ip - Pod IP:Pod的IP地址。 Docker Engine根據docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網路 - Cluster IP:Service的IP地址。 屬於Kubernetes叢集內部的地址,無法在叢集外部直接使用這個地址 ### Pod IP Pod IP 地址是實際存在於某個網絡卡(可以是虛擬裝置)上的,但Service Cluster IP就不一樣了,沒有網路裝置為這個地址負責。它是由kube-proxy使用Iptables規則重新定向到其本地埠,再均衡到後端Pod的。 例如,當Service被建立時,Kubernetes給它分配一個地址10.0.0.1。這個地址從我們啟動API的service-cluster-ip-range引數(舊版本為portal_net引數)指定的地址池中分配,比如--service-cluster-ip-range=10.0.0.0/16。假設這個Service的埠是1234。叢集內的所有kube-proxy都會注意到這個Service。當proxy發現一個新的service後,它會在本地節點開啟一個任意埠,建相應的iptables規則,重定向服務的IP和port到這個新建的埠,開始接受到達這個服務的連線。 當一個客戶端訪問這個service時,這些iptable規則就開始起作用,客戶端的流量被重定向到kube-proxy為這個service開啟的埠上,kube-proxy隨機選擇一個後端pod來服務客戶。 ### Cluster IP Service的IP地址,此為虛擬IP地址。外部網路無法ping通,只有kubernetes叢集內部訪問使用。通過命令 `kubectl -n 名稱空間 get Service` 即可查詢ClusterIP Cluster IP是一個虛擬的IP,但更像是一個偽造的IP網路,原因有以下幾點 - Cluster IP僅僅作用於Kubernetes Service這個物件,並由Kubernetes管理和分配P地址 - Cluster IP無法被ping,他沒有一個“實體網路物件”來響應 - Cluster IP只能結合Service Port組成一個具體的通訊埠`Endpoint`,單獨的Cluster IP不具備通訊的基礎,並且他們屬於Kubernetes叢集這樣一個封閉的空間。 - 在不同Service下的pod節點在叢集間相互訪問可以通過Cluster IP ![不同服務的Pod訪問](https://gitee.com/owen2016/pic-hub/raw/master/pics/20201022001845.png) 為了實現圖上的功能主要需要以下幾個元件的協同工作: - apiserver:在建立service時,apiserver接收到請求以後將資料儲存到etcd中。 - kube-proxy:k8s的每個節點中都有該程序,負責實現service功能,這個程序負責感知service,pod的變化,並將變化的資訊寫入本地的iptables中。 - iptables:使用NAT等技術將virtualIP的流量轉至endpoint中 根據是否生成ClusterIP又可分為普通Service和Headless Service兩類: 1. **普通Service:**通過為Kubernetes的Service分配一個叢集內部可訪問的固定虛擬IP(Cluster IP),實現叢集內的訪問。為最常見的方式。 2. **Headless Service:**該服務不會分配Cluster IP,也不通過kube-proxy做反向代理和負載均衡。而是通過DNS提供穩定的網路ID來訪問,DNS會將headless service的後端直接解析為Pod IP列表。`主要供StatefulSet使用`。 ## 不同型別的Port ``` yaml apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort // 有配置NodePort,外部流量可訪問k8s中的服務 ports: - port: 30080 // 服務訪問埠,叢集內部訪問的埠 targetPort: 80 // pod控制器中定義的埠(應用訪問的埠) nodePort: 30001 // NodePort,外部客戶端訪問的埠 selector: name: nginx-pod ``` ### port - port是k8s叢集`內部訪問`service的埠(service暴露在Cluster IP上的埠),即通過`clusterIP: port`可以訪問到某個service ### nodePort - nodePort是`外部訪問`k8s叢集中service的埠,通過`nodeIP: nodePort`可以從外部訪問到某個service。 該埠號的範圍是 kube-apiserver 的啟動引數 –service-node-port-range指定的,在當前測試環境中其值是 30000-50000。表示只允許分配30000-50000之間的埠。 比如外部使用者要訪問k8s叢集中的一個Web應用,那麼我們可以配置對應service的type=NodePort,nodePort=30001。其他使用者就可以通過瀏覽器訪問到該web服務。而資料庫等服務可能不需要被外界訪問,只需被內部服務訪問即可,那麼我們就不必設定service的NodePort ### TargetPort - targetPort 是pod的埠,從port和nodePort來的流量經過kube-proxy流入到後端pod的targetPort上,最後進入容器。 ### containerPort - containerPort是pod內部容器的埠,targetPort對映到containerPort。 ### hostPort 這是一種直接定義Pod網路的方式。hostPort是直接將容器的埠與所排程的節點上的埠路由,這樣使用者就可以通過宿主機的IP加上來訪問Pod了,如 ``` yaml apiVersion: v1 kind: Pod metadata: name: influxdb spec: containers: - name: influxdb image: influxdb ports: - containerPort: 8086 # 此處定義暴露的埠 hostPort: 8086 ``` 這樣做有個缺點,因為Pod重新排程的時候該Pod被排程到的宿主機可能會變動,這樣就變化了,使用者必須自己維護一個Pod與所在宿主機的對應關係。 使用了 hostPort 的容器只能排程到埠不衝突的 Node 上,除非有必要(比如執行一些系統級的 daemon 服務),不建議使用埠對映功能。如果需要對外暴露服務,建議使用 NodePort Service。 總的來說,port和nodePort都是service的埠,前者暴露給叢集內客戶訪問服務,後者暴露給叢集外客戶訪問服務。從這兩個埠到來的資料都需要經過反向代理kube-proxy流入後端 pod的targetPod,從而到達pod上的容器內。 ## Endpoint 建立Service的同時,會自動建立跟Service同名的Endpoints。 Endpoint 是k8s叢集中一個資源物件,儲存在etcd裡面,用來記錄一個service對應的所有pod的訪問地址。service通過selector和pod建立關聯。 `Endpoint = Pod IP + Container Port` service配置selector endpoint controller 才會自動建立對應的endpoint 物件,否則是不會生產endpoint 物件 一個service由一組後端的pod組成,這些後端的pod通過service endpoint暴露出來,如果有一個新的pod建立創建出來,且pod的標籤名稱(label:pod)跟service裡面的標籤(label selector 的label)一致會自動加入到service的endpoints 裡面,如果pod物件終止後,pod 會自動從edponts 中移除。在叢集中任意節點 可以使用curl請求service `:` ### Endpoint Controller Endpoint Controller是k8s叢集控制器的其中一個元件,其功能如下: - 負責生成和維護所有endpoint物件的控制器 - 負責監聽service和對應pod的變化 - 監聽到service被刪除,則刪除和該service同名的endpoint物件 - 監聽到新的service被建立,則根據新建service資訊獲取相關pod列表,然後建立對應endpoint物件 - 監聽到service被更新,則根據更新後的service資訊獲取相關pod列表,然後更新對應endpoint物件 - 監聽到pod事件,則更新對應的service的endpoint物件,將podIp記錄到endpoint中 ### 定義 Endpoint 對於Service,我們還可以定義Endpoint,Endpoint 把Service和Pod動態地連線起來,Endpoint 的名稱必須和服務的名稱相匹配。 建立mysql-service.yaml ``` yaml apiVersion: v1 kind: Service metadata: name: mysql-production spec: ports: - port: 3306 ``` 建立mysql-endpoints.yaml ``` yaml kind: Endpoints apiVersion: v1 metadata: name: mysql-production namespace: default subsets: - addresses: - ip: 192.168.1.25 ports: - port: 3306 ``` ``` yaml [root@k8s-master endpoint]# kubectl describe svc mysql-production Name: mysql-production Namespace: default Labels: Annotations: Selector: Type: ClusterIP IP: 10.254.218.165 Port: 3306/TCP Endpoints: 192.168.1.25:3306 Session Affinity: None Events: ``` ### 使用Endpoint引用外部服務 service 不僅可以代理pod, 還可以代理任意其它的後端(執行在k8s叢集外部的服務,比如mysql mongodb)。如果需要從k8s裡面連結外部服務(mysql),可定義同名的service和endpoint 在實際生成環境中,像mysql mongodb這種IO密集行應用,效能問題會顯得非常突出,所以在實際應用中,一般不會把這種有狀態的應用(mysql 等)放入k8s裡面,而是使用單獨的服務來部署,而像web這種無狀態的應用更適合放在k8s裡面 裡面k8s的自動伸縮,和負載均衡,故障自動恢復 等強大功能 建立service (mongodb-service-exten) ``` yaml kind: Service apiVersion: v1 metadata: name: mongodb namespace: name spec: ports: - port: 30017 name: mongodb targetPort: 30017 ``` 建立 endpoint(mongodb-endpoint) ``` yaml kind: Endpoints apiVersion: v1 metadata: name: mongodb namespace: tms-test subsets: - addresses: - ip: xxx.xxx.xx.xxx ports: - port: 30017 name: mongod ``` 可以看到service跟endpoint成功掛載一起了,表面外面服務成功掛載到k8s裡面了,在應用中配置連結的地方使用`mongodb://mongodb:30017` 連結資料 ### 建立ExternalName型別的服務 除了手動配置服務的endpoint來代替公開外部服務方法,還可以通過`完全限定域名(FQDN)`訪問外部服務——建立ExternalName型別的服務。 ![20201020135304113_1274858680](https://gitee.com/owen2016/pic-hub/raw/master/1606053762_20201122220236684_720492790.png) ExternalName型別的服務建立後,pod可以通過`external-service.default.svc.cluster.local`域名連線到外部服務,或者通過`externale-service`。當需要指向其他外部服務時,只需要修改spec.externalName的值