1. 程式人生 > >Kubernetes之服務發現Service

Kubernetes之服務發現Service

一、service的概念

在Kubernetes中,Pod是有生命週期的,當Pod的生命週期結束之後,Pod會被重新分配IP。這樣就會導致一個問題:在Kubernetes叢集中,如果一組Pod(稱為backend)為其他Pod(稱為frontend)提供服務,那麼那些frontend該如何發現並連線到作為backend的Pod呢? Kubernetes中service是一組提供相同功能的Pods的抽象,併為他們提供一個同意的入口。藉助Service,應用可以方便的實現服務發現於負載均衡,並實現應用的零宕機升級。Service通過spec.selector來選取後端服務,一般配合ReplicationController或者Deployment來保證後端容器的正常執行。 在這裡插入圖片描述

Service能夠提供負載均衡的能力,但是在使用上有以下限制:

  • 只提供4層負載均衡能力,而沒有7層功能,但有時我們可能需要更多的匹配規則來轉發請求,這點上4層負載均衡是不支援的
  • 使用NodePort型別的Service時,需要在叢集外部部署外部的負載均衡器
  • 使用LoadBalancer型別的Service時,Kubernetes必須執行在特定的雲服務上

二、Service的型別

有以下四種類型:

  • ClusterIp:預設型別,自動分配一個僅Cluster內部可以訪問的虛擬IP

  • NodePort:在ClusterIP基礎上為Service在每臺機器上繫結一個埠,這樣就可以通過<NodeIP>: NodePort來訪問該服務

  • LoadBalancer:在NodePort的基礎上,藉助cloud provider建立一個外部負載均衡器,並將請求轉發到<NodeIP>: NodePort

  • ExternalName:把叢集外部的服務引入到叢集內部來,在叢集內部直接使用。沒有任何型別代理被建立,這隻有kubernetes 1.7或更高版本的kube-dns才支援 在這裡插入圖片描述

  • ClusterIP clusterIP主要在每個node節點使用iptables,將發向clusterIP對應埠的資料,轉發到kube-proxy中。然後kube-proxy自己內部實現有負載均衡的方法,並可以查詢到這個service下對應pod的地址和埠,進而把資料轉發給對應的pod的地址和埠

  • NodePort nodePort的原理在於在node上開了一個埠,將向該埠的流量匯入到kube-proxy,然後由kube-proxy進一步到給對應的pod

  • LoadBalancer loadBalancer和nodePort其實是同一種方式。區別在於loadBalancer比nodePort多了一步,就是可以呼叫cloud provider去建立LB來向節點導流。

1. ClusterIP

此模式會提供一個叢集內部的虛擬IP(與Pod不在同一網段),以供叢集內部的Pod之間通訊使用。 在這裡插入圖片描述 為了實現圖上的功能,主要需要以下幾個元件的協同工作:

  • apiserver 使用者通過kubectl命令向apiserver傳送建立service的命令,apiserver接收到請求後將資料儲存到etcd中
  • kube-proxy kubernetes的每個節點中都有一個叫做kube-porxy的程序,這個程序負責感知service,pod的變化,並將變化的資訊寫入本地的iptables規則中
  • iptables 使用NAT等技術將virtualIP的流量轉至endpoint中

1.1. 釋出一個deployment,並指定replicas為3

建立myapp-deploy.yaml檔案

[[email protected] manifests]# vim myapp-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deploy
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      release: stabel
  template:
    metadata:
      labels:
        app: myapp
        release: stabel
        env: test
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v2
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80

儲存退出。 使用kubectl apply命令建立deployment

[[email protected] manifests]# kubectl apply -f myapp-deploy.yaml 
deployment.apps/myapp-deploy created

檢視建立好的deployment和pod

[[email protected] manifests]# kubectl get deployment
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
myapp-deploy   3         3         3            3           7s
[[email protected] manifests]# kubectl get pods
NAME                            READY     STATUS    RESTARTS   AGE
myapp-deploy-6d9c8d8797-4k8hl   1/1       Running   0          10s
myapp-deploy-6d9c8d8797-bllxq   1/1       Running   0          10s
myapp-deploy-6d9c8d8797-bvhw2   1/1       Running   0          10s

這樣就建立好了副本數為3的deployment了。

1.2. 釋出一個service,並指定步驟1中的label

建立myapp-service.yaml檔案

[[email protected] manifests]# vim myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: myapp
    release: stabel
  ports:
  - name: http
    port: 80
    targetPort: 80

使用kubectl apply命令建立service

[[email protected] manifests]# kubectl apply -f myapp-service.yaml 
service/myapp created
[[email protected] manifests]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   12d
myapp        ClusterIP   10.100.68.105   <none>        80/TCP    4s	

這樣就建立好ClusterIP型別的service了。此時myapp的Cluster-IP(也可以稱為VirtualIP)為10.100.68.105。 檢視myapp的endpoint資訊:

[[email protected] manifests]# kubectl get endpoints
NAME         ENDPOINTS                                      AGE
kubernetes   192.168.116.130:6443                           12d
myapp        10.244.1.29:80,10.244.2.32:80,10.244.2.33:80   10m

然後檢視剛剛創建出來的pod的資訊:

[[email protected] manifests]# kubectl get pods -o wide
NAME                            READY     STATUS    RESTARTS   AGE       IP            NODE
myapp-deploy-6d9c8d8797-4k8hl   1/1       Running   0          33m       10.244.1.29   node1
myapp-deploy-6d9c8d8797-bllxq   1/1       Running   0          33m       10.244.2.33   node2
myapp-deploy-6d9c8d8797-bvhw2   1/1       Running   0          33m       10.244.2.32   node2

可以發現myapp對應的endpoint剛好是三個pod的地址

1.3. 檢視iptables,觀察其NAT表中的資訊(只截取了部分和這個servcie有關的資訊)

檢視iptables中NAT表的命令:iptables -t nat -nvL

[[email protected] manifests]# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target     prot opt in     out     source               destination         
86  5488 KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
268 16156 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
....
....
Chain KUBE-SERVICES (2 references)
pkts bytes target     prot opt in     out     source               destination
...
...
   0     0 KUBE-MARK-MASQ  tcp  --  *      *      !10.244.0.0/16        10.100.68.105        /* default/myapp:http cluster IP */ tcp dpt:80
   0     0 KUBE-SVC-JOCJVTCKLKOLHFVR  tcp  --  *      *       0.0.0.0/0            10.100.68.105        /* default/myapp:http cluster IP */ tcp dpt:80
...
...
Chain KUBE-SVC-JOCJVTCKLKOLHFVR (1 references)
pkts bytes target     prot opt in     out     source               destination         
0     0 KUBE-SEP-AJTG34DMLXUWJC3E  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ statistic mode random probability 0.33332999982
0     0 KUBE-SEP-S4C5QHO5KWOWFNCG  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ statistic mode random probability 0.50000000000
0     0 KUBE-SEP-4H47DMTPBM73YXWU  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */
...
...
Chain KUBE-SEP-AJTG34DMLXUWJC3E (1 references)
pkts bytes target     prot opt in     out     source               destination         
0     0 KUBE-MARK-MASQ  all  --  *      *       10.244.1.29          0.0.0.0/0            /* default/myapp:http */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ tcp to:10.244.1.29:80
...
...
Chain KUBE-SEP-S4C5QHO5KWOWFNCG (1 references)
pkts bytes target     prot opt in     out     source               destination         
0     0 KUBE-MARK-MASQ  all  --  *      *       10.244.2.32          0.0.0.0/0            /* default/myapp:http */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ tcp to:10.244.2.32:80
...
...
Chain KUBE-SEP-4H47DMTPBM73YXWU (1 references)
pkts bytes target     prot opt in     out     source               destination         
0     0 KUBE-MARK-MASQ  all  --  *      *       10.244.2.33          0.0.0.0/0            /* default/myapp:http */
0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ tcp to:10.244.2.33:80

在PREROUTING鏈中會先匹配到KUBE-SERVICES這個Chain; 所有destination IP為10.100.68.105的包都轉到KUBE-SVC-JOCJVTCKLKOLHFVR這個Chain; 這裡能看到請求會平均執行KUBE-SEP-AJTG34DMLXUWJC3EKUBE-SEP-S4C5QHO5KWOWFNCGKUBE-SEP-4H47DMTPBM73YXWU這三個Chain; 最後我們看一下KUBE-SEP-AJTG34DMLXUWJC3E這條Chain:這條Chain使用了DNAT規則將流量轉發到10.244.1.29:80這個地址上,這樣從其他Pod發出來的流量通過本地的iptables規則將流量轉到myapp Service定義的Pod上。

2. NodePort

對於這種型別,Kubernetes將會在每個Node上開啟一個埠並且每個Node的埠都是一樣的,通過<NodeIP>:NodePort的方式Kubernetes叢集外部的程式可以訪問Service。 修改myapp-service.yaml,將Service的type改為NodePort型別。

[[email protected] manifests]# vim myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  type: **NodePort**
  selector:
    app: myapp
    release: stabel
  ports:
  - name: http
    port: 80
    targetPort: 80

儲存退出。

[[email protected] manifests]# kubectl apply -f myapp-service.yaml 
service/myapp configured

在這裡插入圖片描述 可以看到kubernetes已經隨機給我們分配了一個NodePort埠了。 觀察iptables中的變化,KUBE-NODEPORTS這個Chain中增加了如下內容

Chain KUBE-NODEPORTS (1 references)
pkts bytes target     prot opt in     out     source               destination         
0     0 KUBE-MARK-MASQ  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ tcp dpt:31693
0     0 KUBE-SVC-JOCJVTCKLKOLHFVR  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* default/myapp:http */ tcp dpt:31693

可以看到流量會轉到KUBE-SVC-JOCJVTCKLKOLHFVR這個Chain中處理一次,也就是ClusterIP中提到的通過負載均衡將流量平均分配到3個endpoint上。

3. LoadBalancer

這種型別Service需要藉助cloud provider能力建立LB來向節點導流。 在這裡插入圖片描述

4. ExternalName

這種型別的Service通過返回CNAME和它的值,可以將服務對映到externalName欄位的內容(例如:foo.bar.example. com)。 ExternalName Service是Service的特例,它沒有selector,也沒有定義任何的埠和Endpoint。相反的,對於執行在叢集外部的服務,它通過返回該外部服務的別名這種方式來提供服務。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

當查詢主機my-service.prod.svc.cluster.local(SVC_NAME.NAMESPACE.svc.cluster.local)時,叢集的DNS服務將返回一個值my.database.example.com的CNAME記錄。訪問這個服務的工作方式和其他的相同,唯一不同的是重定向發生在DNS層,而且不會進行代理或轉發。

三、Service的實現模式

在Kubernetes叢集中,每個Node執行一個kube-proxy程序。kube-proxy負責為Service實現一種VIP(虛擬IP)的形式,而不是ExternalName的形式。在Kubernetes v1.0版本中,代理完全在userspace。在Kubernetes v1.1版本中,新增了iptables代理,但並不是預設的執行模式。從Kubernetes v1.2起,預設就是iptables代理。在Kubernetes v1.8.0-beta.0中,添加了ipvs代理。在Kubernetes v1.0版本中,Service是“4層”(TCP/UDP over IP)概念。在Kubernetes v1.1版本中,新增了Ingress API(beta版),用來表示“7層”(HTTP)服務。 kube-proxy這個元件始終監視著apiserver中有關service的變動資訊,獲取任何一個與service資源相關的變動狀態,通過watch監視,一旦有servcie資源相關的變動和建立,kube-proxy都要轉換為當前節點上的能夠實現排程的規則(例如:iptables、ipvs)。 在這裡插入圖片描述

3.1. userspace代理模式

這種模式下,當Client Pod請求核心空間的service ip後,把請求轉到使用者空間kube-porxy監聽的埠,由kube-proxy處理後,再由kube-proxy將請求轉給核心空間的service ip,再由service ip根據請求轉給各節點中的service pod。 由此可見這個模式將流量反覆的從使用者空間進出核心空間,這樣帶來的效能損耗是非常大的。 在這裡插入圖片描述

3.2. iptables代理模式

這種模式下,Client Pod直接請求本地核心service ip,根據iptables規則直接將請求轉發到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的效能損耗。另外,如果叢集中存在上萬的Service/Endpoint,那麼Node上的iptables rules將會非常龐大,效能還會再打折扣。在這裡插入圖片描述

3.3. ipvs代理模式

這種模式下,Client Pod請求到達核心空間時,根據ipvs規則直接分發到各Pod上。kube-proxy會監視Kubernetes Service物件和Endpoints,呼叫netlink介面以相應的建立ipvs規則並定期與Kubernetes Service物件和Endpoints物件同步ipvs規則,以確保ipvs狀態與期望一致。訪問服務時,流量將被重定向到其中一個後端Pod。 與iptables類似,ipvs基於netfilter的hook功能,但使用雜湊表作為底層資料結構並在核心空間中工作。這意味著ipvs可以更快的重定向流量,並且在同步代理規則時具有更好的效能。此外,ipvs為負載均衡演算法提供了更多選項,例如:

  • rr:輪詢排程
  • lc:最小連線數
  • dh:目標雜湊
  • sh:源雜湊
  • sed:最短期望延遲
  • nq:不排隊排程

注意: ipvs代理模式假定在執行kube-proxy之前在節點上都已經安裝了IPVS核心模組。當kube-proxy以ipvs代理模式啟動時,kube-proxy將驗證節點上是否安裝了IPVS模組,如果未安裝,則kube-proxy將回退到iptables代理模式 在這裡插入圖片描述 如果某一個服務後端pod發生變化,標籤選擇器適應的pod又多一個,適應的資訊會立即反映到apiserver上,而kube-proxy一定可以watch到etcd中的資訊變化,而將它立即轉為ipvs或iptables規則中,這一切都是動態和實時的,刪除一個pod也是同樣的原理。如圖: 在這裡插入圖片描述

四、Headless Service

有時不需要或不想要負載均衡,以及單獨的Service IP。遇到這種情況,可以通過指定Cluster IP(spec.clusterIP)的值為“None”來建立Headless Service。 這個選項允許開發人員自由的尋找他們自己的方式,從而降低與Kubernetes系統的耦合性。應用仍然可以使用一種自注冊的模式和介面卡,對其他需要發現機制的系統能夠很容易的基於這個API來構建。 對這類Service並不會分配Cluster IP,kube-proxy不會處理它們,而且平臺也不會為它們進行負載均衡和路由。DNS如何實現自動配置,依賴於Service時候定義了selector。

(1)編寫headless service配置清單
[[email protected] mainfests]# cp myapp-svc.yaml myapp-svc-headless.yaml 
[[email protected] mainfests]# vim myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: "None"  #headless的clusterIP值為None
  ports: 
  - port: 80
    targetPort: 80

(2)建立headless service 
[[email protected] mainfests]# kubectl apply -f myapp-svc-headless.yaml 
service/myapp-headless created
[[email protected] mainfests]# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         5s
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

(3)使用coredns進行解析驗證
[[email protected] mainfests]# dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62028
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.18
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.19
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.15
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.16
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.17

;; Query time: 4 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:27:15 EDT 2018
;; MSG SIZE  rcvd: 349

[[email protected] mainfests]# kubectl get svc -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP   36d

[[email protected] mainfests]# kubectl get pods -o wide -l app=myapp
NAME                            READY     STATUS    RESTARTS   AGE       IP            NODE
myapp-deploy-69b47bc96d-4hxxw   1/1       Running   0          1h        10.244.1.18   k8s-node01
myapp-deploy-69b47bc96d-95bc4   1/1       Running   0          1h        10.244.2.16   k8s-node02
myapp-deploy-69b47bc96d-hwbzt   1/1       Running   0          1h        10.244.1.19   k8s-node01
myapp-deploy-69b47bc96d-pjv74   1/1       Running   0          1h        10.244.2.15   k8s-node02
myapp-deploy-69b47bc96d-rf7bs   1/1       Running   0          1h        10.244.2.17   k8s-node02

(4)對比含有ClusterIP的service解析
[[email protected] mainfests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50445
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp.default.svc.cluster.local. IN    A

;; ANSWER SECTION:
myapp.default.svc.cluster.local. 5 IN    A    10.101.245.119

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:31:16 EDT 2018
;; MSG SIZE  rcvd: 107

[[email protected] mainfests]# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         11m
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

從以上的演示可以看到對比普通的service和headless service,headless service做dns解析是直接解析到pod的,而servcie是解析到ClusterIP的,那麼headless有什麼用呢?這將在statefulset中應用到,這裡暫時僅僅做了解什麼是headless service和建立方法。