【解構雲原生】初識Kubernetes Service

編者按:雲原生是網易杭州研究院(網易杭研)奉行的核心技術方向之一,開源容器平臺Kubernetes做爲雲原生產業技術標準、雲原生生態基石,在設計上不可避免有其複雜性,Kubernetes系列文章基於網易杭研資深工程師總結,多角度多層次介紹Kubernetes的原理及運用,如何解決生產中的實際需求及規避風險,但願與讀者深刻交流共同進步。html

本文由做者受權發佈,未經許可,請勿轉載。node

做者:李嵐清,網易杭州研究院雲計算技術中心資深工程師nginx

爲何引入service

衆所周知,pod的生命週期是不穩定的,可能會朝生夕死,這也就意味着pod的ip是不固定的。web

好比咱們使用三副本的deployment部署了nginx服務,每一個pod都會被分配一個ip,因爲pod的生命週期不穩定,pod可能會被刪除重建,而重建的話pod的ip地址就會改變。也有一種場景,咱們可能會對nginx deployment進行擴縮容,從3副本擴容爲5副本或者縮容爲2副本。當咱們須要訪問上述的nginx服務時,客戶端對於nginx服務的ip地址就很難配置和管理。docker

所以,kubernetes社區就抽象出了service這個資源對象或者說邏輯概念。後端

什麼是service

service是kubernetes中最核心的資源對象之一,kubernetes中的每一個service其實就是咱們常常提到的「微服務」。api

service定義了一個服務的入口地址,它經過label selector 關聯後端的pod。service會被自動分配一個ClusterIP,service的生命週期是穩定的,它的ClusterIP也不會發生改變,用戶經過訪問service的ClusterIP來訪問後端的pod。因此,無論後端pod如何擴縮容、如何刪除重建,客戶端都不須要關心。網絡

0526雲原生配圖001.jpg

(1)建立一個三副本的nginx deployment:
nginx.yamlsession

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
# kubectl create -f nginx.yaml
deployment.extensions/nginx created

# kubectl get pods -o wide
nginx-5c7588df-5dmmp                                            1/1     Running       0          57s     10.120.49.230   pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>
nginx-5c7588df-gb2d8                                            1/1     Running       0          57s     10.120.49.152   pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>
nginx-5c7588df-gdngk                                            1/1     Running       0          57s     10.120.49.23    pubt2-k8s-for-iaas4.dg.163.org   <none>           <none>

(2)建立service,經過label selector關聯nginx pod:app

svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
# kubectl create -f svc.yaml
service/nginx created

# kubectl get svc nginx -o wide
NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
nginx   ClusterIP   10.178.4.2   <none>        80/TCP    23s   app=nginx

(3)在k8s節點上訪問service地址

# curl 10.178.4.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

實現原理

service中有幾個關鍵字段:

  • spec.selector: 經過該字段關聯屬於該service的pod
  • spec.clusterIP: k8s自動分配的虛擬ip地址
  • spec.ports: 定義了監聽端口和目的端口。用戶能夠經過訪問clusterip:監聽端口來訪問後端的pod

當用戶建立一個service時,kube-controller-manager會自動建立一個跟service同名的endpoints資源:

# kubectl get endpoints nginx
NAME    ENDPOINTS                                           AGE
nginx   10.120.49.152:80,10.120.49.23:80,10.120.49.230:80   12m

endpoints資源中,保存了該service關聯的pod列表,這個列表是kube-controller-manager自動維護的,當發生pod的增刪時,這個列表會被自動刷新。

好比,咱們刪除了其中的一個pod:

# kubectl delete pods nginx-5c7588df-5dmmp
pod "nginx-5c7588df-5dmmp" deleted

# kubectl get pods
nginx-5c7588df-ctcml                                            1/1     Running     0          6s
nginx-5c7588df-gb2d8                                            1/1     Running     0          18m
nginx-5c7588df-gdngk                                            1/1     Running     0          18m

能夠看到kube-controller-manager立馬補充了一個新的pod。而後咱們再看一下endpoints資源,後端pod列表也被自動更新了:

# kubectl get endpoints nginx
NAME    ENDPOINTS                                          AGE
nginx   10.120.49.152:80,10.120.49.23:80,10.120.49.73:80   16m

那麼,當用戶去訪問clusterip:port時,流量是如何負載均衡到後端pod的呢?

k8s在每一個node上運行了一個kube-proxy組件,kube-proxy會watch service和endpoints資源,經過配置iptables規則(如今也支持ipvs,不過不在本文章討論範圍以內)來實現service的負載均衡。

能夠在任一個k8s node上看一下上述nginx service的iptables規則:

# iptables -t nat -L PREROUTING
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */

# iptables -t nat -L KUBE-SERVICES
Chain KUBE-SERVICES (2 references)
target     prot opt source               destination
KUBE-SVC-4N57TFCL4MD7ZTDA  tcp  --  anywhere             10.178.4.2           /* default/nginx: cluster IP */ tcp dpt:http

# iptables -t nat -L KUBE-SVC-4N57TFCL4MD7ZTDA
Chain KUBE-SVC-4N57TFCL4MD7ZTDA (1 references)
target     prot opt source               destination
KUBE-SEP-AHN4ALGUQHWJZNII  all  --  anywhere             anywhere             statistic mode random probability 0.33332999982
KUBE-SEP-BDD6UBFFJ4G2PJDO  all  --  anywhere             anywhere             statistic mode random probability 0.50000000000
KUBE-SEP-UR2OSKI3P5GEGC2Q  all  --  anywhere             anywhere

# iptables -t nat -L KUBE-SEP-AHN4ALGUQHWJZNII
Chain KUBE-SEP-AHN4ALGUQHWJZNII (1 references)
target     prot opt source               destination
KUBE-MARK-MASQ  all  --  10.120.49.152        anywhere
DNAT       tcp  --  anywhere             anywhere             tcp to:10.120.49.152:80

當用戶訪問clusterip:port時,iptables會經過iptables DNAT 均衡的負載均衡到後端pod。

service ClusterIP

service的ClusterIP是一個虛擬ip,它沒有附着在任何的網絡設備上,僅僅存在於iptables規則中,經過dnat實現訪問clusterIP時的負載均衡。

當用戶建立service時,k8s會自動從service網段中分配一個空閒ip設置到.spec.clusterIP字段。固然,k8s也支持用戶在建立svc時本身指定clusterIP。

service的網段是經過 kube-apiserver的命令行參數--service-cluster-ip-range配置的,不容許變動。

service網段不能跟機房網絡、docker網段、容器網段衝突,不然可能會致使網絡不通。

service的clusterIP是k8s集羣內的虛擬ip,不一樣的k8s集羣可使用相同的service網段,在k8s集羣外是訪問不通service的clusterIP的。

service域名

kubernetes是有本身的域名解析服務的。好比咱們能夠經過訪問域名nginx.default.svc.cluster.local來訪問上述的nginx服務:

$ curl nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

域名格式爲: ${ServiceName}.${Namespace}.svc.${ClusterDomain}. 其中${ClusterDomain}的默認值是cluster.local,能夠經過kubelet的命令行參數----cluster-domain進行配置。

headless service

當不須要service ip的時候,能夠在建立service的時候指定spec.clusterIP: None,這種service便是headless service。因爲沒有分配service ip,kube-proxy也不會處理這種service。

DNS對這種service的解析:

  • 當service裏定義selector的時候:Endpoints controller會建立相應的endpoints。DNS裏的A記錄會將svc地址解析爲這些pods的地址
  • 當service裏沒有定義selector:Endpoints controller不會建立endpoints。DNS會這樣處理:
  • 首先CNAME到service裏定義的ExternalName
  • 沒有定義ExternalName的話,會搜尋全部的和這個service共享name的Endpoints,而後將A記錄解析到這些Endpoints的地址

service的不一樣類型

service支持多種不一樣的類型,包括ClusterIPNodePortLoadBalancer,經過字段spec.type進行配置。

ClusterIP service

默認類型。對於ClusterIP service, k8s會自動分配一個只在集羣內可達的虛擬的ClusterIP,在k8s集羣外沒法訪問。

NodePort service

k8s除了會給NodePort service自動分配一個ClusterIP,還會自動分配一個nodeport端口。集羣外的客戶端能夠訪問任一node的ip加nodeport,便可負載均衡到後端pod。

nodeport的端口範圍能夠經過kube-apiserver的命令行參數--service-node-port-range配置,默認值是30000-32767,當前咱們的配置是30000-34999

可是客戶端訪問哪一個node ip也是須要考慮的一個問題,須要考慮高可用。並且NodePort會致使訪問後端服務時多了一跳,而且可能會作snat看不到源ip。

另外須要注意的是,service-node-port-range 不可以跟幾個端口範圍衝突:

  • Linux的net.ipv4.ip_local_port_range,能夠配置爲 35000-60999
  • ingress nginx中的四層負載均衡,端口必須小於30000
  • 其餘普通業務的端口也須要小於30000

LoadBalancer service

LoadBalancer service須要對接雲服務提供商的NLB服務。當用戶建立一個LoadBalancer類型的sevice時,cloud-controller-manager會調用NLB的API自動建立LB實例,而且將service後端的pod掛到LB實例後端。

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: LoadBalancer
$ kubectl get svc nginx
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
nginx     LoadBalancer   10.178.8.216   10.194.73.147   80:32514/TCP   3s

service中會話保持

用戶能夠經過配置spec.serviceAffinity=ClientIP來實現基於客戶端ip的會話保持功能。 該字段默認爲None。

還能夠經過適當設置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 來設置最大會話停留時間。 (默認值爲 10800 秒,即 3 小時)

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: ClusterIP
  sessionAffinity: ClientIP

kubernetes service

當咱們部署好一個k8s集羣以後,發現系統自動幫忙在default namespace下建立了一個name爲kubernetes的service:

# kubectl get svc kubernetes -o yaml
apiVersion: v1
kind: Service
metadata:
  labels:
    component: apiserver
    provider: kubernetes
  name: kubernetes
  namespace: default
spec:
  clusterIP: 10.178.4.1
  ports:
  - name: https
    port: 443
    protocol: TCP
    targetPort: 6443
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

能夠看到kubernetes svc的ip是--service-cluster-ip-range的第一個ip,而且該service沒有設置spec.selector。理論上來講,對於沒有設置selector的svc,kube-controller-manager不會自動建立同名的endpoints資源出來。

可是咱們看到是有同名的endpoints存在的,而且多個apiserver的地址也被保存在endpoints資源中:

# kubectl get ep kubernetes
NAME         ENDPOINTS                         AGE
kubernetes   10.120.0.2:6443,10.120.0.3:6443   137d

具體是如何實現的,感興趣的能夠看下源碼k8s.io/kubernetes/pkg/master/reconcilers

Frequently Asked Questions

問題一 爲何service clusterip沒法ping通

由於service clusterip是一個k8s集羣內部的虛擬ip,沒有附着在任何網絡設備上,僅僅存在於iptables nat規則中,用來實現負載均衡。

問題二 爲何service的網段不能跟docker網段、容器網段、機房網段衝突

假如service網段跟上述網段衝突,很容易致使容器或者在k8s node上訪問上述網段時發生網絡不通的狀況。

問題三 爲何在k8s集羣外沒法訪問service clusterip

service clusterip是k8s集羣內可達的虛擬ip,集羣外不可達。不一樣的k8s集羣可使用相同的service網段。

或者說,集羣外的機器上沒有本k8s集羣的kube-proxy組件,沒有建立對應的iptables規則,所以集羣外訪問不通service clusterip。

問題四 可否擴容service網段

原則上這個網段是不容許更改的,可是假如由於前期規劃的問題分配的網段太小,實際能夠經過比較hack的運維手段擴容service網段。

問題五 service是否支持七層的負載均衡

service僅支持四層的負載均衡,七層的負載均衡須要使用ingress

參考文檔

  1. https://kubernetes.io/docs/concepts/services-networking/service/

做者簡介

李嵐清,網易杭州研究院雲計算技術中心容器編排團隊資深系統開發工程師,具備多年Kubernetes開發、運維經驗,主導實現了容器網絡管理、容器混部等生產級核心系統研發,推進網易集團內部電商、音樂、傳媒、教育等多個業務的容器化。

相關文章
相關標籤/搜索