Kubernetes之(十)服務發現Service


Kubernetes之(十)服務發現Service

理解

Service是對一組提供相同功能的Pods的抽象,並為它們提供一個統一的入口。借助Service,應用可以方便的實現服務發現與負載均衡,並實現應用的零宕機升級。Service通過標簽來選取服務后端,一般配合Replication Controller或者Deployment來保證后端容器的正常運行。這些匹配標簽的Pod IP和端口列表組成endpoints,由kubeproxy負責將服務IP負載均衡到這些endpoints上。
Service有四種類型:

  • ClusterIP:默認類型,自動分配一個僅cluster內部可以訪問的虛擬IP
  • NodePort:在ClusterIP基礎上為Service在每台機器上綁定一個端口,這樣就可以通過 :NodePort 來訪問該服務
  • LoadBalancer:在NodePort的基礎上,借助cloud provider創建一個外部的負載均衡器,並將請求轉發到 :NodePort
  • ExternalName:將服務通過DNS CNAME記錄方式轉發到指定的域名(通過 spec.externlName 設定) 。需要kube-dns版本在1.7以上。

另外,也可以將已有的服務以Service的形式加入到Kubernetes集群中來,只需要在創建
Service的時候不指定Label selector,而是在Service創建好后手動為其添加endpoint。

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監視,一旦有service資源相關的變動和創建,kube-proxy都要轉換為當前節點上的能夠實現資源調度規則(例如:iptables、ipvs)。

userspace代理模式

當客戶端Pod請求內核空間的service iptables后,把請求轉到給用戶空間監聽的kube-proxy 的端口,由kube-proxy來處理后,再由kube-proxy將請求轉給內核空間的 service ip,再由service iptalbes根據請求轉給各節點中的的service pod。
這個模式有很大的問題,由客戶端請求先進入內核空間的,又進去用戶空間訪問kube-proxy,由kube-proxy封裝完成后再進去內核空間的iptables,再根據iptables的規則分發給各節點的用戶空間的pod。這樣流量從用戶空間進出內核帶來的性能損耗是不可接受的。在Kubernetes 1.1版本之前,userspace是默認的代理模型。

iptables代理模式

客戶端IP請求時,直接請求本地內核service ip,根據iptables的規則直接將請求轉發到到各pod上,因為使用iptable NAT來完成轉發,也存在不可忽視的性能損耗。另外,如果集群中存在上萬的Service/Endpoint,那么Node上的iptables rules將會非常龐大,性能還會再打折扣。iptables代理模式由Kubernetes 1.1版本引入,自1.2版本開始成為默認類型。

ipvs代理模式

Kubernetes自1.9-alpha版本引入了ipvs代理模式,自1.11版本開始成為默認設置。客戶端IP請求時到達內核空間時,根據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到etc中的信息變化,而將它立即轉為ipvs或者iptables中的規則,這一切都是動態和實時的,刪除一個pod也是同樣的原理。

Service定義

Service配置清單重要字段

apiVersion:
kind:
metadata:
spec:
  clusterIP: 可以自定義,也可以動態分配
  ports:(與后端容器端口關聯)
  selector:(關聯到哪些pod資源上)
  type:服務類型

Service的服務類型
對一些應用(如 Frontend)的某些部分,可能希望通過外部(Kubernetes 集群外部)IP 地址暴露 Service。
Kubernetes ServiceTypes 允許指定一個需要的類型的 Service,默認是 ClusterIP 類型。
Type 的取值以及行為如下:

  • ClusterIP:通過集群的內部 IP 暴露服務,選擇該值,服務只能夠在集群內部可以訪問,這也是默認的 ServiceType。
  • NodePort:通過每個 Node 上的 IP 和靜態端口(NodePort)暴露服務。NodePort 服務會路由到 ClusterIP 服務,這個 ClusterIP 服務會自動創建。通過請求 : ,可以從集群的外部訪問一個 NodePort 服務。
  • LoadBalancer:使用雲提供商的負載均衡器,可以向外部暴露服務。外部的負載均衡器可以路由到 NodePort 服務和 ClusterIP 服務。
  • ExternalName:通過返回 CNAME 和它的值,可以將服務映射到 externalName 字段的內容(例如, foo.bar.example.com)。 沒有任何類型代理被創建,這只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

創建ClusterIP類型Service

[root@master manifests]# vim redis-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
spec:
  selector:
    app: redis
    role: logstore
  clusterIP: 10.97.97.97
  type: ClusterIP
  ports:
  - port: 6379    #容器端口
    targetPort: 6379   #Pod端口

創建並查看svc:

[root@master manifests]# kubectl apply -f redis-svc.yaml 
service/redis created
[root@master manifests]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP    4d22h
redis        ClusterIP   10.97.97.97   <none>        6379/TCP   4s

查看redis服務詳細信息

[root@master manifests]# kubectl describe svc redis
Name:              redis
Namespace:         default
Labels:            <none>
Annotations:       kubectl.kubernetes.io/last-applied-configuration:
                     {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"redis","namespace":"default"},"spec":{"clusterIP":"10.97.97.97","...
Selector:          app=redis,role=logstore
Type:              ClusterIP
IP:                10.97.97.97   #service ip
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         10.244.2.43:6379   #此處的ip+端口就是pod的ip+端口
Session Affinity:  None
Events:            <none>

[root@master manifests]# kubectl get pods  -o wide
NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
filebeat-ds-h8rwk        1/1     Running   0          8m36s   10.244.1.39   node01   <none>           <none>
filebeat-ds-kzhxw        1/1     Running   0          8m36s   10.244.2.44   node02   <none>           <none>
readiness-httpget-pod    1/1     Running   0          2d21h   10.244.2.18   node02   <none>           <none>
redis-76c85b5744-94djm   1/1     Running   0          8m36s   10.244.2.43   node02   <none>           <none>

總結:

  • service不會直接到pod,service是直接到endpoint資源,就是地址加端口,再由endpoint再關聯到pod。
  • service只要創建完,就會在dns中添加一個資源記錄進行解析,添加完成即可進行解析。資源記錄的格式為:SVC_NAME.NS_NAME.DOMAIN.LTD.
  • 默認的集群service 的A記錄:svc.cluster.local.
  • redis服務創建的A記錄:redis.default.svc.cluster.local.

創建NodePort類型Service

將myapp-deploy發布出去,並可以通過集群外部進行訪問

[root@master manifests]# kubectl get pods --show-labels
NAME                            READY   STATUS    RESTARTS   AGE     LABELS
filebeat-ds-h8rwk               1/1     Running   0          12m     app=filebeat,controller-revision-hash=7f59445876,pod-template-generation=1,release=stable
filebeat-ds-kzhxw               1/1     Running   0          12m     app=filebeat,controller-revision-hash=7f59445876,pod-template-generation=1,release=stable
myapp-deploy-65df64765c-257gl   1/1     Running   0          26s     app=myapp,pod-template-hash=65df64765c,release=canary
myapp-deploy-65df64765c-czwkg   1/1     Running   0          26s     app=myapp,pod-template-hash=65df64765c,release=canary
myapp-deploy-65df64765c-hqmkd   1/1     Running   0          26s     app=myapp,pod-template-hash=65df64765c,release=canary
myapp-deploy-65df64765c-kvj92   1/1     Running   0          26s     app=myapp,pod-template-hash=65df64765c,release=canary
readiness-httpget-pod           1/1     Running   0          2d22h   <none>
redis-76c85b5744-94djm          1/1     Running   0          12m     app=redis,pod-template-hash=76c85b5744,role=logstore

#編輯yaml文件
[root@master manifests]# vim myapp-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: 10.99.99.99
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 31111    #節點端口設置非常用端口

創建並查看svc

[root@master manifests]# kubectl apply -f myapp-svc.yaml 
service/myapp created
[root@master manifests]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP        4d22h
myapp        NodePort    10.99.99.99   <none>        80:31111/TCP   8s
redis        ClusterIP   10.97.97.97   <none>        6379/TCP       10m

此時可以在集群外使用31111端口進行訪問

[root@nfs ~]# while true;do curl http://10.0.0.11:31111/hostname.html;sleep 1;done
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-hqmkd
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-czwkg
myapp-deploy-65df64765c-kvj92

總結:從以上例子,可以看到通過NodePort方式已經實現了從集群外部端口進行訪問,訪問鏈接如下:http://10.0.0.10:31111 。實踐中並不鼓勵用戶自定義使用節點的端口,因為容易和其他現存的Service沖突,建議留給系統自動配置。

Pod的會話保持

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

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

[root@master ~]# 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

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

[root@master manifests]# vim myapp-svc-clientip.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  sessionAffinity: ClientIP
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
    nodePort: 31111

[root@master manifests]# kubectl apply -f myapp-svc-clientip.yaml
service/myapp created

[root@master manifests]# kubectl describe svc myapp
Name:                     myapp
Namespace:                default
Labels:                   <none>
Annotations:              kubectl.kubernetes.io/last-applied-configuration:
                            {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"ports":[{"nodePort":31111,...
Selector:                 app=myapp,release=canary
Type:                     NodePort
IP:                       10.106.188.204
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  31111/TCP
Endpoints:                10.244.1.40:80,10.244.1.41:80,10.244.2.45:80 + 1 more...
Session Affinity:         ClientIP
External Traffic Policy:  Cluster
Events:                   <none>

[root@nfs ~]# while true;do curl http://10.0.0.11:31111/hostname.html;sleep 1;done
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl
myapp-deploy-65df64765c-257gl

也可以使用kubectl patch來動態修改

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

Headless無頭Service

有時不需要或不想要負載均衡,以及單獨的Service IP。 遇到這種情況,可以通過指定 Cluster IP(spec.clusterIP)的值為 "None" 來創建 Headless Service。

這個選項允許開發人員自由尋找他們自己的方式,從而降低與 Kubernetes 系統的耦合性。 應用仍然可以使用一種自注冊的模式和適配器,對其它需要發現機制的系統能夠很容易地基於這個 API 來構建。

對這類 Service 並不會分配 Cluster IP,kube-proxy 不會處理它們,而且平台也不會為它們進行負載均衡和路由。 DNS 如何實現自動配置,依賴於 Service 是否定義了 selector

[root@master manifests]# vim myapp-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: "None"
  ports:
  - port: 80
    targetPort: 80

部署並查看

[root@master manifests]#  kubectl apply -f myapp-svc-headless.yaml 
service/myapp-headless created
[root@master manifests]# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        4d23h
myapp            NodePort    10.106.188.204   <none>        80:31111/TCP   10m
myapp-headless   ClusterIP   None             <none>        80/TCP         2s
redis            ClusterIP   10.97.97.97      <none>        6379/TCP       68m

使用coredns進行解析驗證

[root@master manifests]# dig -t A myapp-svc.default.svc.cluster.local. @10.96.0.10

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

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

;; AUTHORITY SECTION:
cluster.local.          30      IN      SOA     ns.dns.cluster.local. hostmaster.cluster.local. 1554105626 7200 1800 86400 30

;; Query time: 2 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: 一 4月 01 16:03:40 CST 2019
;; MSG SIZE  rcvd: 157

#10.96.0.10是coredns服務的svc地址
[root@master manifests]# 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   5d

解析普通的svc 來對比查看區別

[root@master manifests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10

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

;; QUESTION SECTION:
;myapp.default.svc.cluster.local. IN    A

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

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: 一 4月 01 16:09:34 CST 2019
;; MSG SIZE  rcvd: 96

從以上的演示可以看到對比普通的service和headless service,headless service做dns解析是直接解析到pod的,而servcie是解析到ClusterIP的。
headless無頭服務主要用在statefulset中。

參考資料

https://www.cnblogs.com/linuxk
馬永亮. Kubernetes進階實戰 (雲計算與虛擬化技術叢書)
Kubernetes-handbook-jimmysong-20181218


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM