Kubernetes Service詳解

爲何須要service

Kubernetes能夠方便的爲容器應用提供了一個持續運行且方便擴展的環境,可是,應用最終是要被用戶或其餘應用訪問、調用的。要訪問應用pod,就會有如下兩個問題:node

  1. pod是有生命週期的。它會根據集羣的指望狀態不斷的在建立、刪除、更新,因此pod的ip也在不斷變化,如何訪問到不斷變化的pod?
  2. 一般一個應用不會單隻有一個pod,而是由多個相同功能的pod共同提供服務的。那麼對這個應用的訪問,如何在多個pod中負載均衡?

service主要就是用來解決這兩個問題的。簡單來講,它是一個抽象的api對象,用來表示一組提供相同服務的pod及對這組pod的訪問方式。web

service的實現

service做爲一個相似中介的角色,對內,它要能代理訪問到不斷變換的一組後端Pod;對外,它要能暴露本身給集羣內部或外部的其餘資源訪問。咱們分別來看下具體是怎麼實現的。segmentfault

後端代理

以前的文章kubeadm部署最後的測試部分,建立了一組pod及服務來驗證業務,繼續以這個例子來講明:後端

集羣中已經有以下一組pod:api

NAME                     READY   STATUS       IP            NODE     APP
goweb-55c487ccd7-5t2l2   1/1     Running     10.244.1.15   node-1   goweb
goweb-55c487ccd7-cp6l8   1/1     Running     10.244.3.9    node-2   goweb
goweb-55c487ccd7-gcs5x   1/1     Running     10.244.1.17   node-1   goweb
goweb-55c487ccd7-pp6t6   1/1     Running     10.244.3.10   node-2   goweb

pod都帶有app:goweb標籤,對外暴露8000端口,訪問/info路徑會返回主機名。架構

建立service

建立一個servcie有兩種方式app

  • 命令式
$ kubectl expose deployment goweb --name=gowebsvc --port=80  --target-port=8000
  • 聲明式
# 定義服務配置文件
# svc-goweb.yaml
apiVersion: v1
kind: Service
metadata:
  name: gowebsvc
spec:
  selector:
    app: goweb
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 8000
  type: ClusterIP
# 建立服務
$ kubectl apply -f svc-goweb.yaml

咱們來看下配置文件中幾個重點字段:負載均衡

  • selector指定了app: goweb標籤。說明該svc代理全部包含有"app: goweb"的pod
  • port字段指定了該svc暴露80端口
  • targetPort指定改svc代理對應pod的8000端口
  • type定義了svc的類型爲ClusterIP,這也是svc的默認類型

經過apply建立服務後,來查看一下服務狀態less

$ kubectl  get svc gowebsvc  -o wide
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
gowebsvc   ClusterIP   10.106.202.0   <none>        80/TCP    3d   app=goweb

能夠看到,Kubernetes自動爲服務分配了一個CLUSTER-IP。經過這個訪問這個IP的80端口,就能夠訪問到"app: goweb"這組pod的8000端口,而且能夠在這組pod中負載均衡。dom

[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-gcs5x
[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-cp6l8
[root@master-1 ~]# curl http://10.106.202.0/info
Hostname: goweb-55c487ccd7-pp6t6

請求代理轉發

cluster-ip是一個虛擬的ip地址,並非某張網卡的真實地址。那具體的請求代理轉發過程是怎麼實現的呢? 答案是iptables。咱們來看下iptables中與cluster-ip相關的規則

[root@master-1 ~]# iptables-save | grep 10.106.202.0
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.106.202.0/32 -p tcp -m comment --comment "default/gowebsvc:default cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.106.202.0/32 -p tcp -m comment --comment "default/gowebsvc:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-SEG6BTF25PWEPDFT

能夠看到,目的地址爲CLUSTER-IP、目的端口爲80的數據包,會被轉發到KUBE-MARK-MASQ與KUBE-SVC-SEG6BTF25PWEPDFT鏈上。其中,KUBE-MARK-MASQ鏈的做用是給數據包打上特定的標記(待驗證),重點來看下KUBE-SVC-SEG6BTF25PWEPDFT鏈:

-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-5ZXTVLEM4DKNW7T2
-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-EBFXI7VOCPDT2QU5
-A KUBE-SVC-SEG6BTF25PWEPDFT -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-C3PKSXKMO2M43SPF
-A KUBE-SVC-SEG6BTF25PWEPDFT -j KUBE-SEP-2GQCCNJGO65Z5MFS

能夠看到,KUBE-SVC-SEG6BTF25PWEPDFT鏈經過設置--probability,將請求等機率轉發到4條鏈上,查看其中一條轉發鏈:

[root@master-1 ~]# iptables-save | grep  "A KUBE-SEP-5ZXTVLEM4DKNW7T2" 
-A KUBE-SEP-5ZXTVLEM4DKNW7T2 -s 10.244.1.15/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-5ZXTVLEM4DKNW7T2 -p tcp -m tcp -j DNAT --to-destination 10.244.1.15:8000

發現KUBE-SEP-5ZXTVLEM4DKNW7T2這條規則對請求的目的地址做了DNAT到10.244.1.15:8000,這正是goweb組中goweb-55c487ccd7-5t2l2這個pod的ip地址。這樣,對svc的CLUSTER-IP的請求,就會經過iptables規則轉發到相應的pod。

可是,還有個問題,svc是怎麼跟蹤pod的ip變化的?
注意到前面的nat規則,第一次轉發的鏈名稱是KUBE-SVC-xxx,第二次轉發給具體pod的鏈名稱是KUBE-SEP-xxx,這裏的SEP實際指的是kubernetes另外一個對象endpoint,咱們能夠經過vkubectl get ep命令來查看:

[root@master-1 ~]# kubectl  get ep gowebsvc
NAME         ENDPOINTS 
gowebsvc     10.244.1.15:8000,10.244.1.17:8000,10.244.3.10:8000 + 1 more...   35d

在svc建立的時候,kube-proxy組件會自動建立同名的endpoint對象,動態地跟蹤匹配selector的一組pod當前ip及端口,並生成相應的iptables KUBE-SVC-xxx規則。

請求代理的三種方式

上面說的請求代理轉發的方式,是kubernetes目前版本的默認方式,實際上,service的代理方式一共有三種:

Userspace 模式

在這種模式下,kube-proxy爲每一個服務都打開一個隨機的端口,全部訪問這個端口的請求都會被轉發到服務對應endpoints指定的後端。最後,kube-proxy還會生成一條iptables規則,把訪問cluster-ip的請求重定向到上面說的隨機端口,最終轉發到後端pod。整個過程以下圖所示:

clipboard.png

Userspace模式的代理轉發主要依靠kube-proxy實現,工做在用戶態。因此,轉發效率不高。較爲不推薦用該種模式。

iptables 模式

iptables模式是目前版本的默認服務代理轉發模式,上兩小節作過詳細說明的就是這種模式,來看下請求轉發的示意圖:

clipboard.png

與userspace模式最大的不一樣點在於,kube-proxy只動態地維護iptables,而轉發徹底靠iptables實現。因爲iptables工做在內核態,不用在用戶態與內核態切換,因此相比userspace模式更高效也更可靠。可是每一個服務都會生成若干條iptables規則,大型集羣iptables規則數會很是多,形成性能降低也不易排查問題。

ipvs 模式

在v1.9版本之後,服務新增了ipvs轉發方式。kube-proxy一樣只動態跟蹤後端endpoints的狀況,而後調用netlink接口來生成ipvs規則。經過ipvs來轉發請求:

clipboard.png

ipvs一樣工做在內核態,並且底層轉發是依靠hash表實現,因此性能比iptables還要好的多,同步新規則也比iptables快。同時,負載均衡的方式除了簡單rr還有多種選擇,因此很適合在大型集羣使用。而缺點就是帶來了額外的配置維護操做。

集羣內部服務發現

在集羣內部對一個服務的訪問,主要有2種方式,環境變量與DNS。

環境變量方式

當一個pod建立時,集羣中屬於同個namespace下的全部service對象信息都會被做爲環境變量添加到pod中。隨便找一個pod查看一下:

$ kubectl exec goweb-55c487ccd7-5t2l2 'env' | grep GOWEBSVC
GOWEBSVC_PORT_80_TCP_ADDR=10.106.202.0
GOWEBSVC_SERVICE_PORT=80
GOWEBSVC_SERVICE_PORT_DEFAULT=80
GOWEBSVC_PORT_80_TCP=tcp://10.106.202.0:80
GOWEBSVC_PORT_80_TCP_PROTO=tcp
GOWEBSVC_PORT_80_TCP_PORT=80
GOWEBSVC_PORT=tcp://10.106.202.0:80
GOWEBSVC_SERVICE_HOST=10.106.202.0

能夠看到,pod經過{SVCNAME}_SERVICE_HOST/PORT就能夠方便的訪問到某個服務。這種訪問方式簡單易用,能夠用來快速測試服務。但最大的問題就是,服務必須先於pod建立,後建立的服務是不會添加到現有pod的環境變量中的。

DNS方式

DNS組件是k8s集羣的可選組件,它會不停監控k8s API,在有新服務建立時,自動建立相應的DNS記錄。。以gowebsvc爲例,在服務建立時,會建立一條gowebsvc.default.svc.cluster.local的dns記錄指向服務。並且dns記錄做用域是整個集羣,不侷限在namespace。
雖然是可選組件,但DNS生產環境能夠說是必備的組件了。這裏先簡單說明,後面打算專門開篇文章來詳細介紹。

集羣外部的服務暴露

服務發現解決了集羣內部訪問pod問題,但不少時候,pod提供的服務也是要對集羣外部來暴露訪問的,最典型的就是web服務。k8s中的service有多種對外暴露的方式,能夠在部署Service時經過ServiceType字段來指定。默認狀況下,ServiceType配置是隻能內部訪問的ClusterIP方式,前面的例子都是這種模式,除此以外,還能夠配置成下面三種方式:

NodePort方式:

該方式把服務暴露在每一個Node主機IP的特定端口上,同一個服務在全部Node上端口是相同的,並自動生成相應的路由轉發到ClusterIP。這樣,集羣外部經過<NodeIP>:<NodePort>就能夠訪問到對應的服務。舉個例子:

## 建立svc,經過Nodeport方式暴露服務
$ kubectl expose deployment goweb --name=gowebsvc-nodeport --port=80  --target-port=8000  --type=NodePort 
## 查看svc,能夠看到NodePort隨機分配的端口爲32538
$ kubectl get svc gowebsvc-nodeport 
NAME                TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
gowebsvc-nodeport   NodePort   10.101.166.252   <none>        80:32538/TCP   86s
## 隨便訪問一個nodeip的32538端口,均可以訪問到gowebsvc-nodeport服務對應的pod
$ curl 172.16.201.108:32538/info
Hostname: goweb-55c487ccd7-pp6t6
$ curl 172.16.201.109:32538/info
Hostname: goweb-55c487ccd7-5t2l2

LoadBalance:

LoadBalance方式主要是給公有云服務使用的,經過配置LoadBalance,能夠觸發公有云建立負載均衡器,並把node節點做爲負載的後端節點。每一個公有云的配置方式不一樣,具體能夠參考各公有云的相關文檔。

ExternalName:

當ServiceType被配置爲這種方式時,該服務的目的就不是爲了外部訪問了,而是爲了方便集羣內部訪問外部資源。舉個例子,假如目前集羣的pod要訪問一組DB資源,而DB是部署在集羣外部的物理機,尚未容器化,能夠配置這麼一個服務:

apiVersion: v1
kind: Service
metadata:
  name: dbserver
  namespace: default
spec:
  type: ExternalName
  externalName: database.abc.com

這樣,集羣內部的pod經過dbserver.default.svc.cluster.local這個域名訪問這個服務時,請求會被cname到database.abc.com來。事後,假如db容器化了,不須要修改業務代碼,直接修改service,加上相應selector就能夠了。

幾種特殊的service

除了上面這些一般的service配置,還有幾種特殊狀況:

Multi-Port Services

service能夠配置不止一個端口,好比官方文檔的例子:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  - name: https
    protocol: TCP
    port: 443
    targetPort: 9377

這個service保留了80與443端口,分別對應pod的9376與9377端口。這裏須要注意的是,pod的每一個端口必定指定name字段(默認是default)。

Headless services

Headless services是指一個服務沒有配置了clusterIP=None的服務。這種狀況下,kube-proxy不會爲這個服務作負載均衡的工做,而是交予DNS完成。具體又分爲2種狀況:

  • 有配置selector: 這時候,endpoint控制器會爲服務生成對應pod的endpoint對象。service對應的DNS返回的是endpoint對應後端的集合。
  • 沒有配置selector:這時候,endpoint控制器不會自動爲服務生成對應pod的endpoint對象。若服務有配置了externalname,則生成一套cnmae記錄,指向externalname。若是沒有配置,就須要手動建立一個同名的endpoint對象。dns服務會建立一條A記錄指向endpoint對應後端。

External IPs

若是有個非node本地的IP地址,能夠經過好比外部負載均衡的vip等方式被路由到任意一臺node節點,那就能夠經過配置service的externalIPs字段,經過這個IP地址訪問到服務。集羣以這個IP爲目的IP的請求時,會把請求轉發到對應服務。參考官方文檔的例子:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  externalIPs:
  - 80.11.12.10

這裏的80.11.12.10就是一個不禁kubernetes維護的公網IP地址,經過80.11.12.10:80就能夠訪問到服務對應的pod。

簡單總結下,service對象實際上解決的就是一個分佈式系統的服務發現問題,把相同功能的pod作成一個服務集也能很好的對應微服務的架構。在目前的kubernetes版本中,service還只能實現4層的代理轉發,而且要搭配好DNS服務才能真正知足生產環境的需求。

相關文章
相關標籤/搜索