當新手剛學習k8s時候,會被各種的IP 和port 搞暈,其實它們都與k8s service的訪問有密切關系,梳理它們之間的差異可以更好了解k8s的服務訪問機制。
不同類型的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
為了實現圖上的功能主要需要以下幾個組件的協同工作:
- apiserver:在創建service時,apiserver接收到請求以后將數據存儲到etcd中。
- kube-proxy:k8s的每個節點中都有該進程,負責實現service功能,這個進程負責感知service,pod的變化,並將變化的信息寫入本地的iptables中。
- iptables:使用NAT等技術將virtualIP的流量轉至endpoint中
根據是否生成ClusterIP又可分為普通Service和Headless Service兩類:
-
普通Service:通過為Kubernetes的Service分配一個集群內部可訪問的固定虛擬IP(Cluster IP),實現集群內的訪問。為最常見的方式。
-
Headless Service:該服務不會分配Cluster IP,也不通過kube-proxy做反向代理和負載均衡。而是通過DNS提供穩定的網絡ID來訪問,DNS會將headless service的后端直接解析為Pod IP列表。
主要供StatefulSet使用
。
不同類型的Port
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。其他用戶就可以通過瀏覽器http://node:30001訪問到該web服務。而數據庫等服務可能不需要被外界訪問,只需被內部服務訪問即可,那么我們就不必設置service的NodePort
TargetPort
- targetPort 是pod的端口,從port和nodePort來的流量經過kube-proxy流入到后端pod的targetPort上,最后進入容器。
containerPort
- containerPort是pod內部容器的端口,targetPort映射到containerPort。
hostPort
這是一種直接定義Pod網絡的方式。hostPort是直接將容器的端口與所調度的節點上的端口路由,這樣用戶就可以通過宿主機的IP加上來訪問Pod了,如
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 <CLUSTER-IP>:<PORT>
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
apiVersion: v1
kind: Service
metadata:
name: mysql-production
spec:
ports:
- port: 3306
創建mysql-endpoints.yaml
kind: Endpoints
apiVersion: v1
metadata:
name: mysql-production
namespace: default
subsets:
- addresses:
- ip: 192.168.1.25
ports:
- port: 3306
[root@k8s-master endpoint]# kubectl describe svc mysql-production
Name: mysql-production
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.254.218.165
Port: <unset> 3306/TCP
Endpoints: 192.168.1.25:3306
Session Affinity: None
Events: <none>
使用Endpoint引用外部服務
service 不僅可以代理pod, 還可以代理任意其它的后端(運行在k8s集群外部的服務,比如mysql mongodb)。如果需要從k8s里面鏈接外部服務(mysql),可定義同名的service和endpoint
在實際生成環境中,像mysql mongodb這種IO密集行應用,性能問題會顯得非常突出,所以在實際應用中,一般不會把這種有狀態的應用(mysql 等)放入k8s里面,而是使用單獨的服務來部署,而像web這種無狀態的應用更適合放在k8s里面 里面k8s的自動伸縮,和負載均衡,故障自動恢復 等強大功能
創建service (mongodb-service-exten)
kind: Service
apiVersion: v1
metadata:
name: mongodb
namespace: name
spec:
ports:
- port: 30017
name: mongodb
targetPort: 30017
創建 endpoint(mongodb-endpoint)
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類型的服務。
ExternalName類型的服務創建后,pod可以通過external-service.default.svc.cluster.local
域名連接到外部服務,或者通過externale-service
。當需要指向其他外部服務時,只需要修改spec.externalName的值即可。