kubernetes服務訪問與負載均衡(一)
當使用kubernetes來發布應用時,Pod例項的生命週期是短暫,PodIP也是易變的。所以kubernetes中抽象了一個叫Service的概念,給應用提供一個固定的訪問入口。
定義一個Service
假如現在:你有一個tomcat映象,你用它建立了一個Deployment,釋出了三個Pod例項;這組Pod暴露了8080埠,並且都擁有標籤app=tomcat
。那麼,你可以建立如下的一個Service物件,然後在kubernetes叢集的容器內或叢集的Node上,都可以通過該 Service的ClusterIP
和Port
來訪問這組Pod的8080埠:(具體原理見文章後面的實驗部分)
kind: Service
apiVersion: v1
metadata:
name: tomcat
spec:
type: ClusterIP
clusterIP: 10.254.11.12 # 可以指定,也可以隨機生成
selector:
app: tomcat
ports:
- protocol: TCP
port: 8080 # Service的Port
targetPort: 8080 # Pod的Port
建立如上的一個Service物件後,Endpoints Controller也會自動建立一個與該Service同名的Endpoints。然後根據Service中的selector,把叢集中label中含有app=tomcat
ClusterIP和Service代理
在上面的Service定義檔案中,ClusterIP是一個虛擬IP。它既不是某個Pod的IP,也不是某個Node的IP。kubernetes叢集中的每個節點上都執行一個kube-proxy,它負責監聽Service
與Endpoints
的建立與刪除,並相應地修改Node上的iptables規則。
kube-proxy有三種代理模式:userspace、iptables、ipvs。從kubernetes v1.2開始,iptables成為預設的代理模式,在kubernetes v1.9-alpha中,加入了ipvs代理模式。本文主要介紹一下iptables代理模式。
Proxy-mode:iptables
在這種模式下,對於每一個Service,它會在Node上新增相應的iptables規則,將訪問該Service的ClusterIP與Port的連線重定向到Endpoints中的某一個後端Pod。預設情況下,選擇哪一個Pod是隨機。
當選擇的某個後端Pod沒有響應時,iptables模式無法自動重新連線到另一個Pod,所以需要使用者自已利用Pod的readness probe
和liveness probe
來規避這種情況。下圖(截自官網)給出了通過ServiceIP:ServicePort
訪問應用的原理:
服務發現
在上面的內容中,我們講述了可以通過Service的IP和埠來訪問應用。不過,在Pod中(注意在Node上不行),我們也可以通過Service的名字加埠來訪問應用,它依賴於kubernetes的服務發現機制。
kubernetes的服務發現機制有兩種:環境變數與DNS。
環境變數
當kubelet建立一個Pod時,對於每一個活動的Service,它會往該Pod中加入一組環境變數,形如:{SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
等。其中服務名是大寫的,破折號會被轉化為下劃線。
例如,有一個Service,名叫redis-master
,暴露了6379的TCP埠,ClusterIP為 10.0.0.11,那麼會產生下面的環境變數:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
順序很重要: 一個Pod想要訪問一個Service,則服務必須在該Pod之前建立,否則該Service的環境變數將不會被加入到Pod中。DNS不會有這個限制。
DNS
DNS伺服器是一個可選的外掛(強烈推薦使用)。DNS伺服器會通過Kubernetes的API監聽Service的建立,然後為Service建立一組DNS記錄。如果在整個叢集中開啟了DNS,則所有的Pod都能夠自動地做Service的域名解析。
例如,如果在Kubernetes的名稱空間my-ns
中有一個叫my-service
的Service,那麼就會建立一條my-service.my-ns
的DNS記錄。名稱空間my-ns
中的Pod可以直接使用my-service
做域名解析。其他名稱空間中的Pod則使用my-service.my-ns
做域名解析.解析出來的結果是Service的ClusterIP。
Service的型別
kubernetes的Service有四種類型:ClusterIP
,NodePort
,ExternalName
,LoadBalancer
。本文主要介紹一下前兩種。
ClusterIP
ClusterIP
是Service的預設型別,如果不設定type
欄位,則預設為ClusterIP
。這種型別的Service把服務暴露成一個內部的叢集IP,只能在叢集內訪問(Pod和Node上都可以)。clusterIP
欄位的值可以自動生成,也可以手動設定,不過一定要在規則的範圍內,可通過kube-apiserver
的啟動引數--service-cluster-ip-range
指定ClusterIP
的範圍。
NodePort
如果將type
欄位設定為NodePort
,則Kubernetes master將從啟動引數--service-node-port-range
配置的範圍(預設值:30000-32767)分配埠,並且每個節點將該埠(每個節點上埠號相同)代理到你的Service。該埠可以在Service的spec.ports[*].nodePor
t欄位進行檢視。
如果你想要一個特定的埠號,你可以在該nodePort
欄位中指定一個值,系統將為你分配該埠,或者API事務將失敗(也就是說,你需要注意自己可能發生的埠衝突)。你指定的值必須位於節點埠的配置範圍內。
這種型別的Service也會有一個ClusterIP
。這意味著,它有兩種訪問方式:通過ServiceIP:SerivcePort
訪問,或者通過NodeIP:NodePort
訪問。
實驗一
釋出一個
ClusterIP
型別的Service,檢視在Node上通過ServiceIP:ServicePort
訪問服務是如何被重定向到後端Pod的。
1、環境準備
搭建好叢集,本文中的實驗叢集資訊如下:
$ kubectl get node
NAME STATUS ROLES AGE VERSION
132.122.232.101 Ready <none> 31d v1.8.6
132.122.232.78 Ready <none> 13d v1.8.6
2、建立deployment
建立一個deployment,釋出三個例項,yaml檔案如下:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tomcat
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: tomcat
template:
metadata:
labels:
app: tomcat
spec:
containers:
- name: container1
image: 10.142.232.115:8021/public/tomcat:8
ports:
- containerPort: 8080
建立完deployment後,檢視pod的情況如下:
$ kubectl get pod -o wide | grep tomcat
tomcat-b85775b9f-dzqj6 1/1 Running 0 25s 172.27.48.24 132.122.232.101
tomcat-b85775b9f-hxhh5 1/1 Running 0 26s 172.27.135.8 132.122.232.78
tomcat-b85775b9f-xgk7c 1/1 Running 0 26s 172.27.135.7 132.122.232.78
3、建立service,yaml檔案如下
apiVersion: v1
kind: Service
metadata:
name: myservice
namespace: default
spec:
type: ClusterIP
selector:
app: tomcat
ports:
- name: port1
port: 8080
targetPort: 8080
建立service,然後檢視service的資訊如下:
$ kubectl get service myservice
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myservice ClusterIP 10.254.197.221 <none> 8080/TCP 5m
檢視自動生成的endpoints的資訊如下:
$ kubectl get endpoints myservice
NAME ENDPOINTS AGE
myservice 172.27.135.7:8080,172.27.135.8:8080,172.27.48.24:8080 7m
4、檢視node上iptables規則
注意:下面的內容需要對linux的iptables有一定的基礎,可閱讀相關文章
接下來我們來研究,當我們從Node上通過Service的IP與埠來訪問服務時,是如何被轉發到後端的某個Pod上去的。
出站資料流經過的鏈依次為:OUTPUT -> 主機核心路由 -> POSTROUTING
。如下圖:
檢視nat表(其他表的內容已省略,轉發規則在這個表中)的OUTPUT鏈如下(其他行已省略,用...
代替),發現引用了KUBE-SERVICES
鏈
$ iptables -t nat -L OUTPUT -n
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
...
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
...
檢視KUBE-SERVICES
鏈的內容,如下:
$ iptables -t nat -L KUBE-SERVICES -n
Chain KUBE-SERVICES (2 references)
target prot opt source destination
...
KUBE-SVC-FPR422BQLZ77Y5BA tcp -- 0.0.0.0/0 10.254.197.221 /* default/myservice:port1 cluster IP */ tcp dpt:8080
...
這條規則的意思是:協議為tcp、目的地址為10.254.197.221、目的埠為8080的包,都轉到KUBE-SVC-FPR422BQLZ77Y5BA
鏈。10.254.197.221:8080
就是Service的IP與埠。
我們接著檢視KUBE-SVC-FPR422BQLZ77Y5BA
鏈的內容,如下:
$ iptables -t nat -L KUBE-SVC-FPR422BQLZ77Y5BA -n
Chain KUBE-SVC-FPR422BQLZ77Y5BA (1 references)
target prot opt source destination
KUBE-SEP-4NXNX7EV3NGCZ67M all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */ statistic mode random probability 0.33332999982
KUBE-SEP-CG5TE3ILWI4BIYIZ all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */ statistic mode random probability 0.50000000000
KUBE-SEP-6SZNOYYRDNRYRRHV all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */
上面三條規則的意思是:隨機選擇其中的一條鏈。
我們檢視上面三條鏈的內容如下:
$ iptables -t nat -L KUBE-SEP-4NXNX7EV3NGCZ67M -n
Chain KUBE-SEP-4NXNX7EV3NGCZ67M (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.135.7 0.0.0.0/0 /* default/myservice:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */ tcp to:172.27.135.7:8080
$ iptables -t nat -L KUBE-SEP-CG5TE3ILWI4BIYIZ -n
Chain KUBE-SEP-CG5TE3ILWI4BIYIZ (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.135.8 0.0.0.0/0 /* default/myservice:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */ tcp to:172.27.135.8:8080
$ iptables -t nat -L KUBE-SEP-6SZNOYYRDNRYRRHV -n
Chain KUBE-SEP-6SZNOYYRDNRYRRHV (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.48.24 0.0.0.0/0 /* default/myservice:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice:port1 */ tcp to:172.27.48.24:8080
可以看出,三條鏈剛好代表了三個Pod,隨機選擇一條鏈即隨機選擇後端的某個Pod。
在經過這三條鏈中的某一條鏈時,資料包就會做目的地址轉換(DNAT),目的地址變成了後端Pod的IP與埠。然後再經過主機的路由判斷,轉發到後端的Pod去。Pod之間的網路連通性,是由網路外掛實現的,與這裡的Service沒有關係。
實驗二
釋出一個
NodePort
型別的Service,檢視在叢集外通過NodeIP:NodePort
訪問服務是如何被重定向到後端Pod的。
1
、前提工作
搭建好叢集,叢集資訊如下
$ kubectl get node
NAME STATUS ROLES AGE VERSION
132.122.232.101 Ready <none> 31d v1.8.6
132.122.232.78 Ready <none> 13d v1.8.6
2、建立deployment,yaml檔案如下:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tomcat2
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: tomcat2
template:
metadata:
labels:
app: tomcat2
spec:
containers:
- name: container1
image: 10.142.232.115:8021/public/tomcat:8
ports:
- containerPort: 8080
建立完後,檢視pod的資訊如下:
$ kubectl get pod -o wide | grep tomcat2
tomcat2-f85bd4487-gx45x 1/1 Running 0 14s 172.27.135.10 132.122.232.78
tomcat2-f85bd4487-lqvh2 1/1 Running 0 14s 172.27.135.9 132.122.232.78
tomcat2-f85bd4487-mtc9z 1/1 Running 0 14s 172.27.48.25 132.122.232.101
3、建立service
建立service,yaml檔案如下:
apiVersion: v1
kind: Service
metadata:
name: myservice2
namespace: default
spec:
type: NodePort
selector:
app: tomcat2
ports:
- name: port1
port: 8080
targetPort: 8080
建立完成後,檢視service的資訊如下,它仍然有ClusterIP和ServicePort(下面的8080埠),同時還增加了一個NodePort(下面的31406埠)
$ kubectl get service myservice2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myservice2 NodePort 10.254.136.47 <none> 8080:31406/TCP 17s
檢視endpoints的資訊如下:
$ kubectl get endpoints myservice2
NAME ENDPOINTS AGE
myservice2 172.27.135.10:8080,172.27.135.9:8080,172.27.48.25:8080 3m
4、檢視node上的iptables規則
當我們從叢集外通過NodeIP:NodePort
訪問服務時,資料包進入Node後會依次通過如下的線路:
PREROUTING -> 主機核心路由 -> FOWARD -> POSTROUTING
檢視PREROUTING鏈的規則如下(其他行已省略,用...
代替),引用了KUBE-SERVICES
鏈
$ iptables -t nat -L PREROUTING -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
...
KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
...
繼續檢視KUBE-SERVICES
鏈的內容,如下
$ iptables -t nat -L KUBE-SERVICES -n
Chain KUBE-SERVICES (2 references)
target prot opt source destination
...
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
上面KUBE-NODEPORTS
一行的意思是,如果目的地址是LOCAL
型別(比如Node有兩個網絡卡,則兩個IP都是),則進入KUBE-NODEPORTS
鏈。
繼續檢視KUBE-NODEPORTS
鏈的內容,如下
$ iptables -t nat -L KUBE-NODEPORTS -n
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ tcp dpt:31406
KUBE-SVC-ZLEWOEFITNP2NY7D tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ tcp dpt:31406
檢視KUBE-MAKR-MASQ
與KUBE-SVC-ZLEWOEFITNP2NY7D
鏈的內容如下:
$ iptables -t nat -L KUBE-MARK-MASQ -n
Chain KUBE-MARK-MASQ (11 references)
target prot opt source destination
MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
$ iptables -t nat -L KUBE-SVC-ZLEWOEFITNP2NY7D -n
Chain KUBE-SVC-ZLEWOEFITNP2NY7D (2 references)
target prot opt source destination
KUBE-SEP-7354SXOGHIVJMV7G all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ statistic mode random probability 0.33332999982
KUBE-SEP-7CDANDZR4NYGM5QP all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ statistic mode random probability 0.50000000000
KUBE-SEP-6JKWW5STFDSOOTVT all -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */
KUBE-MARK-MASQ
這條鏈會給資料包打一個mark標記,在POSTROUTING
鏈中會給打了mark標記的資料包做源地址轉換(SNAT),這個在這裡不做詳細討論。
從KUBE-SVC-ZLEWOEFITNP2NY7D
鏈可以看出,資料包會隨機地進入到其中的某一條鏈。
檢視上面三條鏈的內容,如下:
$ iptables -t nat -L KUBE-SEP-7354SXOGHIVJMV7G -n
Chain KUBE-SEP-7354SXOGHIVJMV7G (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.135.10 0.0.0.0/0 /* default/myservice2:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ tcp to:172.27.135.10:8080
$ iptables -t nat -L KUBE-SEP-7CDANDZR4NYGM5QP -n
Chain KUBE-SEP-7CDANDZR4NYGM5QP (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.135.9 0.0.0.0/0 /* default/myservice2:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ tcp to:172.27.135.9:8080
$ iptables -t nat -L KUBE-SEP-6JKWW5STFDSOOTVT -n
Chain KUBE-SEP-6JKWW5STFDSOOTVT (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 172.27.48.25 0.0.0.0/0 /* default/myservice2:port1 */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/myservice2:port1 */ tcp to:172.27.48.25:8080
上面三條鏈的內容表示,資料包的目的IP與埠會從NodeIP:NodePort
轉換為PodIP:PodPort
。資料包後面如何通過主機核心路由及POSTROUTING
表,這裡不做討論了。