【解構雲原生】初識Kubernetes Service

網易雲發表於2020-05-26

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

本文由作者授權釋出,未經許可,請勿轉載。

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

為什麼引入service

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

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

因此,kubernetes社群就抽象出了service這個資源物件或者說邏輯概念。

什麼是service

service是kubernetes中最核心的資源物件之一,kubernetes中的每個service其實就是我們經常提到的“微服務”。

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

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

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:

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

參考文件

 

作者簡介

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

相關文章