1. 程式人生 > >kubernetes服務訪問與負載均衡(一)

kubernetes服務訪問與負載均衡(一)

當使用kubernetes來發布應用時,Pod例項的生命週期是短暫,PodIP也是易變的。所以kubernetes中抽象了一個叫Service的概念,給應用提供一個固定的訪問入口。

定義一個Service

假如現在:你有一個tomcat映象,你用它建立了一個Deployment,釋出了三個Pod例項;這組Pod暴露了8080埠,並且都擁有標籤app=tomcat。那麼,你可以建立如下的一個Service物件,然後在kubernetes叢集的容器內或叢集的Node上,都可以通過該 Service的ClusterIPPort 來訪問這組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

的Pod加入到該Endpoints中。

ClusterIP和Service代理

在上面的Service定義檔案中,ClusterIP是一個虛擬IP。它既不是某個Pod的IP,也不是某個Node的IP。kubernetes叢集中的每個節點上都執行一個kube-proxy,它負責監聽ServiceEndpoints的建立與刪除,並相應地修改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 probeliveness 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有四種類型:ClusterIPNodePortExternalNameLoadBalancer。本文主要介紹一下前兩種。

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[*].nodePort欄位進行檢視。

如果你想要一個特定的埠號,你可以在該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-MASQKUBE-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 表,這裡不做討論了。