03.07 深入解析Kubernetes service 概念

在Kubernetes平臺上,Pod是有生命週期,為了可以給客戶端一個固定的訪問端點,因此需要在客戶端和Pod之間添加一箇中間層,這個中間層稱之為Service

Service是什麼?

在Kubernetes中,每個節點都安裝了kube-proxy,kube-proxy通過kubernetes中固有的watch請求方法持續監聽apiserver。一旦有service資源發生變動(增刪改查)kube-proxy可以及時轉化為能夠調度到後端Pod節點上的規則,這個規則可以是iptables也可以是ipvs,取決於service實現方式

Kubernetes 三大IP

  • Node Network 節點網絡 節點網絡地址是配置在節點網絡之上
  • Pod Network Pod網絡 Pod網絡地址是配置在Pod網絡之上節點網絡和Pod網絡都是配置在某個設備之上,可以是硬件也可以是虛擬網絡
  • Cluster Network(svc network) virtual IP svc ip沒有配置在某個網絡接口上,它只是存在service的規則當中

Service 工作原理

k8s在創建Service時,會根據標籤選擇器selector(lable selector)來查找Pod,據此創建與Service同名的endpoint對象,當Pod 地址發生變化時,endpoint也會隨之發生變化,service接收前端client請求的時候,就會通過endpoint,找到轉發到那個Pod進行訪問的地址。(至於轉發到哪個節點的Pod,由負載均衡kube-proxy期初就決定好的)

<code>#查看endpoint可以通過下面的命令
[root@k8s-01 ~]# kubectl get ep
NAME             ENDPOINTS                                               AGE

fuseim.pri-ifs   <none>                                                  31d
kubernetes       192.168.0.10:6443,192.168.0.11:6443,192.168.0.12:6443   33d
myapp            172.30.168.6:80,172.30.192.7:80,172.30.216.7:80         7m56s
myapp-svc        172.30.168.4:80,172.30.192.5:80,172.30.216.6:80         18m/<none>/<code>

1.endpoint是k8s集群中的一個資源對象,存儲在etcd中,用來記錄一個service對應的所有pod的訪問地址2.只有當service配置selector(選擇器),endpoint controller才會自動創建對應的endpoint對象,否則,不會生成endpoint對象3.在k8s集群中創建webapp的service,就會生成一個同名的endpoint對象,endpoint就是service關聯的Pod的ip地址和端口

Service 工作模式

userpace在這種模式下,kube-proxy監視Kubernetes主服務器以添加和刪除Service和Endpoint對象。對於每個服務,它都會在本地節點上打開一個端口(隨機選擇)。與此代理端口的任何連接都代理到服務的後端Pod。SessionAffinity在決定使用哪個後端Pod時,kube-proxy會考慮服務的設置。最後,用戶控件代理安裝iptables規則,以獲取服務clusterIP和流量port。規則將流量重定向到代理後端Pod的端口。

簡單點來說Service的請求先從用戶空間進入內核iptables轉發到這個端口,然後再回到用戶空間,由kube-proxy完成後端endpoint的選擇和代理,這樣流量會有從用戶空間進入內核的過程,效率低,有明顯的性能瓶頸

深入解析Kubernetes service 概念

image_1e2i15977imggfddjb1dgsfbem.png-155.2kB

iptables

目前默認的方案,完全以內核iptables的nat方式實現service負載均衡。該方式在大規模情況下存在一些性能問題;首先,iptables沒有增量更新的功能,更新一條規則需要整體flush,更新時間長,這段時間之內流量會有不同程度的影響;此外,iptables規則串行匹配,沒有預料到Kubernetes這種在一個機器上會有很多規則的情況,流量需要經過所有規則的匹配後在進行轉發,對時間和內存都是極大的小號,尤其在大規模情況下對性能的影響十分明顯

深入解析Kubernetes service 概念

image_1e2i1b3cf1fja1kkrmravbtcms13.png-148.8kB

ipvs

與iptables、userspace 模式一樣,kube-proxy 依然監聽Service以及Endpoints對象的變化, 不過它並不創建反向代理, 也不創建大量的 iptables 規則, 而是通過netlink 創建ipvs規則,並使用k8s Service與Endpoints信息,對所在節點的ipvs規則進行定期同步; netlink 與 iptables 底層都是基於 netfilter 鉤子,但是 netlink 由於採用了 hash table 而且直接工作在內核態,在性能上比 iptables 更優

同時,ipvs負載均衡除了簡單rr規則還有很多選擇,適合在大型集群中使用,而缺點是帶來了額外的配置維護操作

深入解析Kubernetes service 概念

image_1e2i1j2661kavs7d13bg119g1hvm1g.png-113.3kB

簡單說一下kube-proxy和service之間的關係

訪問Service的請求,不論是Cluster IP還是TargerPort方式;還是NodePort方式,都被Node節點的Iptables規則重定向到kube-proxy監聽Service服務代理端口。kube-proxy接受Service的訪問請求後,根據負載策略轉發到後端的Pod

<code>->  kube-proxy其實就是管理service的訪問入口,包括集群內Pod到Service的訪問和集群外訪問service。
->  kube-proxy管理sevice的Endpoints,該service對外暴露一個Virtual IP,也成為Cluster IP, 集群內通過訪問這個Cluster IP:Port就能訪問到集群內對應的serivce下的Pod。
->  service是通過Selector選擇的一組Pods的服務抽象,其實就是一個微服務,提供了服務的LB和反向代理的能力,而kube-proxy的主要作用就是負責service的實現。
->  service另外一個重要作用是,一個服務後端的Pods可能會隨著生存滅亡而發生IP的改變,service的出現,給服務提供了一個固定的IP,而無視後端Endpoint的變化。/<code>

kube-proxy負責為Service提供cluster內部的服務發現和負載均衡,它運行在每個Node計算節點上,負責Pod網絡代理, 它會定時從etcd服務獲取到service信息來做相應的策略,維護網絡規則和四層負載均衡工作。在K8s集群中微服務的負載均衡是由Kube-proxy實現的,它是K8s集群內部的負載均衡器,也是一個分佈式代理服務器,在K8s的每個節點上都有一個,這一設計體現了它的伸縮性優勢,需要訪問服務的節點越多,提供負載均衡能力的Kube-proxy就越多,高可用節點也隨之增多。

Service 服務發現

https://i4t.com/4478.html

Service 集群服務暴露

服務發現解決了集群內部訪問Pod問題,但很多時候,Pod提供的服務也要對集群外部來暴露訪問。

ExternalName

簡單的來說ExternalName代理外部的請求,讓Pod訪問svc實際上訪問的就是集群外部的服務,讓Pod訪問集群外部的服務像集群內部服務一樣方便

<code>apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: default
spec:
  type: ExternalName
  externalName: my.database.example.com/<code>

這裡externalName實際上就是cname的域名,前提需要節點可以訪問到,或者映射到公網上

集群內部可以通過my-service.default.svc.cluster.local訪問,請求會被cname映射到my.database.example.com

ClusterIP

clusterIP又可以分為普通Service和Headless Service兩類

  • 普通Service 通過Kubernetes的Service分配一個集群內部可訪問的固定虛擬IP(Cluster IP),也可以自己指定clusterIP,從而實現集群內部的訪問
  • Headless Service 無頭服務 該服務不會分配ClusterIP,也不會通過kube-proxy做反向代理和負載均衡。而是通過DNS提供穩定的網絡ID來訪問,DNS會將headless service的後端直接解析為PodIP列表,主要提供StatefulSet使用

普通Cluster IP演示

這裡我們創建一個svc和deployment,具體參數不在解釋,如有不懂可以參考 Kubernetes Service

<code>apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: myapp-svc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-svc
  template:
    metadata:
      labels:
        app: myapp-svc
    spec:
      containers:
      - name: myapp-svc
        image: ikubernetes/myapp:v1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  namespace: default
spec:
  selector:
    app: myapp-svc
  type: ClusterIP

  ports:
  - port: 80
    targetPort: 80/<code>

創建完畢後,service會隨機代理pod。 普通service也是比較常見的svc

<code>[root@k8s-01 ~]# kubectl get pod,svc
NAME                             READY   STATUS    RESTARTS   AGE
pod/myapp-svc-5f8fc8545f-6svln   1/1     Running   0          79s
pod/myapp-svc-5f8fc8545f-kwc7x   1/1     Running   0          79s
pod/myapp-svc-5f8fc8545f-rp8vg   1/1     Running   0          79s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.254.0.1      <none>        443/TCP   33d
service/myapp-svc    ClusterIP   10.254.223.73   <none>        80/TCP    81s
[root@k8s-01 ~]#
[root@k8s-01 ~]# curl 10.254.223.73/hostname.html
myapp-svc-5f8fc8545f-kwc7x/<none>/<none>/<code>

Headkess Service演示

如果我們上面創建了clusterIP模式,需要修改svc模式,需要將之前svc刪除從新創建,如果使用apply是無法刷新svc配置的

<code>[root@k8s-01 ~]# cat k8s-svc.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: myapp-svc
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp-svc
  template:
    metadata:
      labels:
        app: myapp-svc
    spec:
      containers:
      - name: myapp-svc
        image: ikubernetes/myapp:v1

        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  namespace: default
spec:
  clusterIP: "None"
  selector:
    app: myapp-svc
  ports:
  - port: 80
    targetPort: 80/<code>

在無頭模式中,clusterip為none,因為不需要clusterip所以這裡是沒有cluster ip

<code>[root@k8s-01 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.254.0.1   <none>        443/TCP   33d
myapp-svc    ClusterIP   None         <none>        80/TCP    4m33s/<none>/<none>/<code>

這裡通過dig命令進行查看

參數需要說明,myapp-svc為svc的名稱,default為命名空間, 後面@10.254.0.2我這裡使用的是coredns,同樣也是coredns svc ip

<code>## 查看svc及Pod名稱,一會需要對比
[root@k8s-01 ~]# kubectl get pod,svc -o wide
NAME                             READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE   READINESS GATES
pod/myapp-svc-5f8fc8545f-26c8x   1/1     Running   0          7m22s   172.30.168.4   k8s-03   <none>           <none>
pod/myapp-svc-5f8fc8545f-hd7pp   1/1     Running   0          7m22s   172.30.216.6   k8s-01   <none>           <none>
pod/myapp-svc-5f8fc8545f-kd6xl   1/1     Running   0          7m22s   172.30.192.5   k8s-02   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE     SELECTOR
service/kubernetes   ClusterIP   10.254.0.1   <none>        443/TCP   33d     <none>
service/myapp-svc    ClusterIP   None         <none>        80/TCP    7m22s   app=myapp-svc

## 查看dns svc ip
[root@k8s-01 ~]# kubectl get svc -n kube-system|grep dns
kube-dns                  ClusterIP   10.254.0.2       <none>        53/UDP,53/TCP,9153/TCP        33d

##查看dns解析是否為3個pod,以及對應的IP
[root@k8s-01 ~]# dig -t A myapp-svc.default.svc.cluster.local. @10.254.0.2

...
;; ANSWER SECTION:
myapp-svc.default.svc.cluster.local. 5 IN A    172.30.168.4
myapp-svc.default.svc.cluster.local. 5 IN A    172.30.216.6
myapp-svc.default.svc.cluster.local. 5 IN A    172.30.192.5
.../<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<none>/<code>

如果我們使用普通service,這裡dig只會出現一個pod名稱


NodePort

除了cluster ip之外,還可以通過service的port映射到集群內每個節點的相同端口,實現通過nodeIP:nodePort 從集群外訪問服務

在NodePort中,client請求先到達NodeIP+NodePort-->ClusterIP:ServicePort-->PodIP:ContainerPort

這裡我們繼續創建webapp

<code>apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: myapp
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: my-nginx
        image: ikubernetes/myapp:v1
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:

  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080/<code>

我們映射nodeport端口30080,type類型為Nodeport,也是我們之前常用的

<code>[root@k8s-01 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.254.0.1      <none>        443/TCP        33d
myapp        NodePort    10.254.233.67   <none>        80:30080/TCP   33s/<none>/<none>/<code>

接下來我們就可以在任意節點訪問Pod

<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html; sleep 1; done
myapp-xr9m2
myapp-gbcvh
myapp-6gwh9
myapp-xr9m2
myapp-gbcvh/<code>

LoadBalancer

和nodeport類似,不過除了使用一個Clusterip和nodeport之外,還會向所使用的公有云申請一個負載均衡器,實現從集群外通過LB訪問服務

更多參數可以參考官方文檔https://kubernetes.io/docs/concepts/services-networking/service/#externalname

Service Session會話保持

session affinity會話保持,又稱為會話親和性,常用於Web Service的微服務部署中。就和我們nginx中的會話保持一樣

可以將來自同一個客戶端的請求始終轉發至同一個後端的Pod對象,這意味著它會影響調度算法的流量分發功用,進而降低其負載均衡的效果。因此,當客戶端訪問Pod中的應用程序時,如果有基於客戶端身份保存某些私有信息,並基於這些私有信息追蹤用戶的活動等一類的需求時,那麼應該啟用session affinity機制。

  Service affinity的效果僅僅在一段時間內生效,默認值為10800秒,超出時長,客戶端再次訪問會重新調度。該機制僅能基於客戶端IP地址識別客戶端身份,它會將經由同一個NAT服務器進行原地址轉換的所有客戶端識別為同一個客戶端,由此可知,其調度的效果並不理想。Service 資源 通過. spec. sessionAffinity 和. spec. sessionAffinityConfig 兩個字段配置粘性會話。 spec. sessionAffinity 字段用於定義要使用的粘性會話的類型,它僅支持使用“ None” 和“ ClientIP” 兩種屬性值。如下:

<code>[root@k8s-01 ~]# kubectl explain svc.spec.sessionAffinity
KIND:     Service
VERSION:  v1

FIELD:    sessionAffinity <string>

DESCRIPTION:
     Supports "ClientIP" and "None". Used to maintain session affinity. Enable
     client IP based session affinity. Must be ClientIP or None. Defaults to
     None. More info:
     https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
[root@k8s-01 ~]#/<string>/<code>

sessionAffinity支持ClientIP和None 兩種方式,默認是None(隨機調度) ClientIP是來自於同一個客戶端的請求調度到同一個pod中

<code>[root@k8s-01 ~]# cat webapp.yml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: myapp
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
spec:
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: my-nginx
        image: ikubernetes/myapp:v1
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
  type: NodePort
  sessionAffinity: ClientIP    ##參數位置
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080/<code>

此時我們通過curl請求svc時,只會返回一個節點的pod

<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html;sleep 1;done
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq
myapp-cg4lq/<code>

也可以使用打補丁的方式進行修改yaml內的內容,如下:

<code>kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"ClusterIP"}}'  #session保持,同一ip訪問同一個pod

kubectl patch svc myapp -p '{"spec":{"sessionAffinity":"None"}}'    #取消session /<code>

如果我們取消session配置,那麼請求svc時會隨機調度給pod相當於sessionAffinity=None

<code>[root@k8s-01 ~]# while true;do curl http://192.168.0.10:30080/hostname.html;sleep 1;done
myapp-pplfn
myapp-wkfqz
myapp-cg4lq
myapp-pplfn
myapp-wkfqz/<code>

說明一點Service屬於四層調度,無論是iptables還是ipvs都是無法實現https請求或者7層的操作

原文:https://i4t.com/4567.html


分享到:


相關文章: